MV3 and Customizable Action Icon and Title

I need to make the icon and title of the action button customizable on MV3. The icon and title are stored in the local storage. A user can edit them any time in the add-on options. In the MV2 version of the add-on they are customized at the top level of the background script. Since the event page script is not executed on the add-on start (the page is implemented through the background.page key in the manifest), icon and title are default after the browser is launched and some interaction with the add-on is required to change them.
The documentation mentions onInstall event for the menus, but as I understand, it works for the static menus (although, everything works when the add-on is tested through web-ext since it is installed every time when launched this way). Menus are also editable in the add-on, so the changes made by users on such menus, icon and title will not work as expected. Am I missing something, or this is some major blunder of MV3?

1 Like

Since it is very troublesome to sign add-ons now (they may be reviewed for a quite long time), I can provide for the reference one of my public add-ons built for MV2 and MV3 which (in a WIP MV3 port) uses all the features described above, although I do not know is it permissible to load/distribute XPI here. It’s GitHub repo is here.

The MV2 version works as expected - the action icon/title/context menu customized on the add-on load. The MV3 version requires some interaction with the add-on to make the customizations visible.

I haven’t found anything on Bugzilla by the query: manifest v3 background script add-on startup. Any chances that this could be fixed?

How are you testing your extension?

  • With web-ext, using the command:
    web-ext run
  • By loading it manually with:
    about:debugging > This Firefox > Load Temporary Add-On

I’ve attached a test extension that changes the action icon from the default red one to a blue one.
The extension doesn’t have a browser.runtime.onInstalled() handler.
It calls browser.action.setIcon() at the top level of the service worker.
In Firefox 110.0 (64-Bit) on Manjaro Linux, it changes the action icon without any user interaction.

I’ve probably misunderstood your question.

action_icon_and_title.zip (6.7 KB)

Yes, it’s a tricky bug. The problem is that the background script of a MV3 extension runs on startup every time if you run it using web-ext. But if you build an XPI and install it into a regular profile, some interaction will be required…

I still can’t reproduce the problem.

Firefox Developer Edition 110.0b9 (64-Bit) on Manjaro Linux:

As explained in Testing persistent and restart features, I have

  1. Put an add-on ID in manifest.json
  2. Zipped the extension with:
    web-ext build
  3. Set xpinstall.signatures.required to false in about:config
  4. Installed the extension from about:addons with:
    Cogwheel icon > Install add-on from file

It doesn’t matter if I check “Run this extension on private mode windows” or not, it changes the action icon to blue without any user interaction.

When I restart Firefox Developer Edition, it also changes the action icon to blue without any user interaction.
However, sometime there’s a noticeable delay, which I can’t reproduce reliably.

Updated test extension:
action_icon_and_title-0.0.1.zip (6.2 KB)

Here is XPI of the extension mentioned above (MultiButton, it’s a relatively simple extension).multibutton-0.3.4-mv3.xpi.zip (101.8 KB)
It loads the background script as a module which loads other modules and currently uses top-level await since it is necessary to load the icon and title from the local storage. The same thing happens when wrapping this into an async function. I suppose, that the asynchronous execution and the module is the problem. But this works in MV2…

Test extension still works when I call browser.storage.local.get() before browser.action.setIcon()
Maybe you’re right and the modules are the problem.

I’m afraid I can’t help you, because I don’t understand most of your extension…

Top level await is pretty new feature and not everyone is happy about it. It does make some things easier but it also brings many huge complications (I can’t remember the details, but it was a big discussion when TypeScript was introducing it).

Anyway, Chromium disabled it in Service workers completely:
https://chromium.googlesource.com/chromium/src/+/9c00256bc926a519e0e75b7e8efe35968e27e5f1

Although Firefox uses the event pages, not service workers, so it should work. But who knows.
It may be better to avoid top level await for now. You can still export a promise and then await the promise in some “async init() {}” function in a different module.

Well, your add-on works, at least on 112 Nightly, even if the background script is a module that imports other modules and uses top level await. My one is buggy on the regular Firefox v110. Will try to dig deeper. This is clearly some MV3 problem, because the MV2 version works as expected. I use modules in all my add-ons, so I thought that this is an obvious MV3 bug.

I’ve modified your add-on so it reliably exhibits the same behavior. I’m adding action onClicked listener at the top level of the module background script. The action itself is taken not directly from browser.action, but from a variable needed to distinguish between MV2 browserAction and MV3 action. Don’t know what exactly causes the bug though.action_icon_and_title-0.0.2.zip (7.2 KB)

This happens even when trying to add an onClicked listener in a non-module background script.action_icon_and_title-0.0.3.zip (6.4 KB)

This is clearly a bug in MV3 on Firefox.

If the service worker of an MV3 extension has a browser.action.onClicked() handler (but no other event handlers) then

  • Firefox runs the service worker when you install the extension from an XPI.
  • After installing the extension and restarting the browser, Firefox doesn’t run the service worker.
  • After installing the extension and restarting the browser, you need to click the action to run the service worker.

Workaround:
Add a browser.runtime.onStartup() listener to the service worker.
The listener function can be empty, it just needs to be present.
Tested in Firefox Developer Edition 110.0b9 (64-Bit) on Manjaro Linux.

action_icon_and_title-0.0.4.zip (6.3 KB)

Then it would be great if you could report the bug :slight_smile:
Firefox (and Chrome) have many annoying bugs like this, especially in MV3 extensions, and the developers need to be made aware of these bugs.

Here is the bug report for this bug on Bugzilla.

BTW: I’ve just learned that modules support in the background event pages is only gonna be added in Firefox 112:

I referred to background.js as a “service worker”.
But it’s actually a non-persistent background page (aka event page).
(in Chrome MV3 extensions, it’s a service worker, but not in Firefox MV3 extensions)
Sorry!

This is how I understand the comments in the bug report, please correct me if I’m wrong:
If you add an event listener at the top level of background.js, you can rely on this event listener being called when its event occurs.
But you can’t rely on any other code in background.js to be executed.
So if you have a free-floating browser.action.setIcon() in background.js, it might be executed when you start Firefox, or it might not (= undefined behavior).
And if you find a workaround for this problem, the workaround could stop functioning in future versions of Firefox.
So the only clean and future-proof way to change action icon when the extension starts, is to call browser.action.setIcon() in both of these listeners:

  • browser.runtime.onInstalled
  • browser.runtime.onStartup

action_icon_and_title-0.0.6.zip (6.4 KB)

Thanks. At lest shomehow it works.