Updating Web Extension When User Alters Preferences


(Matthew G. Saroff) #1

I’ve got my app up to what I would call a “twitchy alpha”, and I am looking at dealing with a minor annoyance.

A user can edit or create custom tags for a context menu, but they do not show up on that context menu until the extension is reloaded (currently doing this though the whole about:debugging thing).

Obviously, once it gets released on AMO, I could just tell the users that the modifications to custom tags require a restart, but that is a bit of a PITA.

Is there a function that can be called to regenerate the custom tags when the user hits save on new/modified tags?


How to Chain two Fetches Together
(jscher2000) #2

Do you mean, update individual context menu items so you don’t have to rebuild the entire (rather large) menu structure?

There is the following, but you need to know the id of the menu entry. Perhaps you can capture that as the user is changing it?


(Matthew G. Saroff) #3

Actually, I’m looking to update all the menus associated with my app.

All the ids for this app start with the same string, but if someone adds a custom menu, or activates or deactivates a menu, I want it updated.


(Juraj Masiar) #4

Well I suppose there is one easy way - create function that builds all context menu items and when user changes something, just remove all menus and recreate them again using your function.


(Matthew G. Saroff) #5

Good to know.

Does it make sense to pull the menu creation code out of my context menu script, and make it a library that I would load when the extension is first loaded (contextmenu.js) when the user changes the active menus (options.js) and when the user changes the custom menus (custom_tags.js), or should I just copy and paste into all three scripts?


(Juraj Masiar) #6

The DRY principle is clear on this :slight_smile: :

I would implement it in the background script and then control it by sending messages to your background script.
You can have it in the separate file - your background script can load multiple scripts just like any other page.


(Matthew G. Saroff) #7

The thing is that the script to delete and regenerate the menus will run three times:

  • At startup. (Runs in the background script ./context_menu.js now)
  • When changing the active menus on the ./settings/options.html page.
  • When editing custom tags on the ./settings/custom+tags.html page. (I keep them separate because I prefer the interface that way)

My ./context_menu.js already generates the menu upon extension loading and reloading.

So, could I simply put a menus.removeAll() before the menu generation code of my ./context_menu.js file, and then call it when the save button is pushed on the other pages? (Message passing reruns ./context_menu.js)


(Juraj Masiar) #8

It’s hard to say, but could be good enough.

But you could decouple it completely by using existing events.
For example, when user clicks save button, you store the config to the browser storage, right?
I would just register browser.storage.onChanged.addListener listener in your context menu builder module and let it rebuild it. That way, you don’t care who and where changed it, it will rebuild when saved settings changes.


(Matthew G. Saroff) #9

Woah. That is a lot simpler.

Thanks,


(Matthew G. Saroff) #10

OK, I’ve got it working, but I have a question

This works like a dream:

window.addEventListener("storage", generateMenu, false);

but this marginally simpler syntax doesn’t:

browser.storage.onChanged.addListener(generateMenu);

My reading of the docs is that both should work.

What am I missing?


(Juraj Masiar) #11

What exactly doesn’t work? The generateMenu is not being called? Also note that the parameter send to that function will be a object with changes.

I’m using the browser.storage.onChanged.addListener all over my apps and it works well, for example:

browser.storage.onChanged.addListener(changes => {
    if (changes.options) {
      reloadOptions();
    }
}

(Matthew G. Saroff) #12

The former invoked the function, and the latter did nothing, but this probably has something to do with my profound lack of knowledge in JavaScript.

For example, I know that the => used in your code above, is some sort of Java idiom, and I have actually used it in some (open source licensed) code that I’ve borrowed, but I do not quite know what it does.

The two snippets above were what I tried, and the former worked, and the latter did not.


(Martin Giger) #13

These event listeners are for two completely different storages. The first one is for window.localStorage and window.sessionStorage and the second one is for browser.storage.local, browser.storage.managed and browser.storage.sync.

It’s just function syntax with some special rules: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions


(Juraj Masiar) #14

The arrow is a lambda function. You should use them, they are very handy :slight_smile:.
It’s the same as if you wrote:

function (changes) {
  ...
}

(Juraj Masiar) #15

Regarding the storages - for your add-on data you may want to use browser.storage.local and not window.localStorage.

The window.localStorage is synchronous and it may get cleared:

Although this API is similar to Window.localStorage it is recommended that you don’t use Window.localStorage in extension code. Firefox will clear data stored by extensions using the localStorage API in various scenarios where users clear their browsing history and data for privacy reasons, while data saved using the storage.local API will be correctly persisted in these scenarios.


(Matthew G. Saroff) #16

OK, I just tried out something with the code, and

window.addEventListener("storage", generateMenu, false);

works, and

browser.addEventListener("storage", generateMenu, false);

does not until I manually reaload the extension through the about:debugging page.

In the options page, saving the updated data looks like this:

function saveMenus () {
console.log(JSON.stringify(activeMenus));
    for (i = 0; i < Object.keys(activeMenus).length; i++) {
        let crntMenu = Object.keys(activeMenus)[i]; //steps through object names
        let crntCheck = crntMenu+"CHK"; //steps through check box names
        let checkVal = document.getElementById(crntCheck).checked; //gives boolean value of check box
activeMenus[crntMenu] = checkVal; //gotta use this notation because I am using a variable for the name.
}
    localStorage.setItem('activeMenus',JSON.stringify(activeMenus)); //store default in local menu
console.log("Menu Settings Saved");
}

It appears the listener picks up when listening to the window, but not for the browser.


(Martin Giger) #17

That seems like a thing that should never work. browser only has the webextension APIs, So the browser.storage.* APIs. No DOM API like localStorage or addEventListener

Important: localStorage and browser.storage.local are two very different things.


(Matthew G. Saroff) #18

So, I’m trying to square the circle on the statement earlier in this discussion that window.localStorage should not be used, and that browser.storage.* will not work.

Or am I reading this wrong?


(Martin Giger) #19

Let me break this down:

window.localStorage:

  • avoid usage in extensions, since data in it can be deleted by privacy features
  • changes trigger window.addEventListener("storage", listener)

browser.storage.local:

  • use in extensions
  • changes trigger browser.storage.onChanged

The “doesn’t work” was that the event listener you were using does not fire for that storage type.


Passing browser.storage.local.get to a Global Variable
(Matthew G. Saroff) #20

So, when I am saving to local storage with localStorage.setItem(); is this being set in window.localStorage , or to the more permanent browser.storage.local ?

It appears that you are saying to me that to save locally stored data, I should probably use
browser.localStorage.setItem(); as opposed to localStorage.setItem(); , and user browser.localStorage.getItem(); as well, correct?

If so, what is the proper listener for this data?