MV3 event page behavior clarification

As I understand, MV3 drops persistent background pages. The only guide on background scripts I’ve found is this.

Curretnly I observe the following behavior (Nightly v102): the background script is evaluated every time when some event registered during its first evaluation is triggered. The events (runtime.onMessage) are registered not strictly in the top level, but in a regular (non async, non event-handler) function call in a ES6 module included in a background page (some events, such as runtime.onInstall do not fire being registered from ES6 modules).

Does this means that a script will register additional events on each wake (for example, alarms)?
Is it possible to perform some heavy processing only once on the add-on load, as it was possible with persistent background pages?

From what I’ve seen with service workers in Chrome, the background script will simply be re-executed every time it’s woken up (usually by one of the event handlers registered on top level).
That includes alarms - BUT creating the alarm with the same name will simply replace the old alarm, so if you call it on start, it may never run because it will be rescheduled on each wake.

I had a similar question. The advised solution is to use chrome.storage.session API as workaround, more info here: https://groups.google.com/a/chromium.org/g/chromium-extensions/c/QjuCv-LRK2c/m/qYx70RYhAwAJ
But this API is still missing in Firefox.

This sounds like a potential bug, have you already reported it to us on bugzilla?

if you could provide us a small test extension that can trigger consistently the issue as you observed it, we could use it as a base for a reduced test case and investigate what makes that use case to don’t work as expected.

Technically what happens internally is that the new event listeners registered on each wake are going to replace “placeholder listeners” that are registered by the WebExtensions internals while the event page isn’t running.

These “placeholder listeners” (technically they are called “persisted listener” internally) are:

  • registered based on the metadata of the listeners registered synchronously when the background page was previously executed.
  • are responsible for waking up the event page when the event is being emitted
  • once the event page has been woken up and it is running again, these placeholder listener may either:
    • be replaced by real event listeners registered again by the background script, in which case all the events queued in the meantime will be forwarded to them
    • be dropped if real event listeners are not be re-registered again by the background script, in which case all the events queued gets dropped because there wouldn’t be any event listener to handle them anymore

As juraj.masiar already mentioned, browser.storage.session will be implemented also in Firefox and at that point it will be definitely one of the APIs that an extension could use to store some session-related data.

In the meantime, at least for some of the possible use cases:

  • browser.storage.local may be an alternative WebExtensions API that is already available across all browsers vendors and manifest versions (but unlike browser.storage.session, the data is also stored on disk and available across browser restarts)

  • if the background script is running as an event page and there is a need to store a small amount of data and have it accessible synchronously, localStorage/sessionStorage WebAPI may be another alternative available (but these DOM WebAPI are not going to be available in a service worker global and so they are not an option in a manifest_version 3 extension running on Google Chrome).

2 Likes

Thanks for a thorough answer.

I’ve made an extension and tested it with FF 101 MV2 and FF 102 MV3, and it works, although previously I struggled with it and was forced to place event registration to a file loaded as “text/javascript”. It seems that this was fixed.

I think it’s meant to be solved like this:

browser.alarms.get("my-one-of-a-kind-alarm").then((exist) => {

    if (!exist) {
        browser.alarms.create("my-one-of-a-kind-alarm", {
            delayInMinutes: 1,
            periodInMinutes: 2,
        });
    }
});

And also alarms.onAlarm.hasListener() for the event listener itself.