Saving to LocalStorage on Window Close

I have a color selection in an extension popup window, and I have the following code to save to localStorage upon windows close.

The window closes upon clicking “OK”, or “Cancel”:

window.addEventListener("unload", function(event) { //when the window is closed
	localStorage.setItem("pickColor", "meep"); // save selected color to localStorage
	let sending = browser.runtime.sendMessage({
		message: "color set done"
	});
	console.log("message sent");
	});

The message is sent, and the console.log fires, but saving to localStorage does not work, I get null rather than my test value "meep", which indicates that the value is not being saved to localStorage.

I am using localStorage rather than browser.storage.local since this is used for only for the duration of the click.

1 Like

The simple answer is that you can’t really do this reliably. The only reliable way is to save the color continuously. Or to have an explicit save button.

1 Like

I do have a save button, which also chooses the window.

I need the listener for the message, which serves to make the menu wait for user input, but I can save the value off of the user button click, as opposed to the listener.

Posted via mobile

Would using beforeunload be possible? In some instances, I was able to capture certain data elements with that event that unload did not capture. Perhaps, it will provide more time for the storage to complete.

OK, I put some test code into my popup page:

localStorage.setItem("pickColor", "nocolor"); // save localStorage to noselection state
console.log(localStorage.getItem("pickColor"));

The log shows that the value has been saved to local storage, but the content script is still pulling null from localStorage.getItem('pickColor');

I assume therefore that somehow or other, the storage space for the popup is separate from the storage space for the content script.

Yes, popups get the localStorage of the extension context, content scripts get the one of the page context. That’s one of the reasons why storage.local is useful.

Great, that means that I need to figure out a way to make storage.local behave in something approximating a synchronous manner.

Is there a standard JavaScript idiom for doing this?

There is async function as mentioned before, however you can not use it in this case. What you can do is have the bg page read localStorage for your content script and send messages. Or send this info via messaging to start with?

Actually, I was looking to replace the localStorage API with the storage.local API to communicate between the popup and the content script.

This sounds a bit wrong. If you want to communicate with some other part of your add-on and not storing a value, then just use messaging.

You can send direct message to your content script (running in a tab) or to all your content scripts - and you are already doing it in your first example:

let sending = browser.runtime.sendMessage({

Note that the “sending” here is a Promise that will resolve once the receiver receives it - it can even be an actual value - response from a content script if it returns a promise from the message handler.
Also this is a nice place to “const” instead of “let” :slight_smile: .

Using storage API for this can also cause a number of other issues, for example Firefox (or Chrome?) won’t fire an event when you store the same value as the one that is already there (because it compares it and sees that it’s the same so it won’t fire).

There is already a message coming from the background script, and I’ve still not grokked how to handle different messages from different sources.

What I have in the popup:
browser.storage.local.set({"pickColor": selColor});

And what I have in the content script:

    	let {pickColor: colorPicked}  = await browser.storage.local.get("pickColor");
    	console.log("color picked", await colorPicked);
    	.....
    }

It passes the color picked to where it can be processed, and works for me.

You can use simple switch to handle messages, for example:

browser.runtime.onMessage.addListener(data => {    // main communication point with background script
  switch (data.type) {
    case 'newColor': colorPicked(data.color); break; 
    case 'getColor': return Promise.resolve(getColorSync());
    case 'getRemoteColor': return getColorAsync();
  }
});

Just keep it simple, no logic there, just call your functions and return Promise if you need to send a response.

So, you have one listener for all messages, and you put the logic inside the listener.

Did not occur to me.

Yes, but the logic should not be in the listener, it should only call the appropriate function / module. If you keep it simple (one case, one line), it’s very easy to read and extend. You can have this listener in every part of your add-on - popup, content script, background script and even options page.