Manifest V3 issues for my extension

I have converted my manifest v2 to a manifest v3 extension. I have a key

"background": {
       "scripts": ["background.js"]
   },

In my manifest. In background.js i have a function init(). I call this function with some of the first lines in background.js with simply

    init();

How can it be, that init() get automatically called again from time to time and not only after browser startup. How can i call init() only once after startup of the browser?

Using

    chrome.runtime.onStartup.addListener(async () => {
        init();
    });

does not work. It doesnt do anything. This seems to work only in Chromium bases browsers.

Does someone has a better idea?

Make sure to read the migration guide (ideally both)

Basically, you background page/script is automatically killed after 30s of inactivity.
So when there is an event that “wakes it up”, it’s not really being woken up, it’s just re-executed from the top.

In any case, if you have a piece of code that needs to be executed only ONCE per extension start, you should use storage.session - for storing some “started” flag boolean (or Date).

For example, this is my helper file for handling code that needs to run on start:

import {isRunningInBackground} from "../environment/context_detection";

browser.runtime.onStartup.addListener(() => {
  // NOTE: in MV3 the background script won't start without onStartup (or similar) listener
});

const KEY = '_ext_started';

// resolves to "TRUE" only if extension just booted. Resolves to "FALSE" if service worker just woke up OR if called from non-background script.
export const didExtensionStarted = (async () => {
  // only allowed in background:
  if (!isRunningInBackground) return false;
  // in MV3 we check runtime storage
  const {[KEY]: extensionStarted} = await browser.storage.session.get(KEY);
  if (extensionStarted) return false;
  // we save the time when extension started
  await browser.storage.session.set({[KEY]: Date.now()});
  return true;
})();

// resolved ONLY when extension starts - re-starting event page / service worker won't resolve!
export const extensionStarted = new Promise<void>(resolve => didExtensionStarted.then(b => b && resolve()));

export async function getExtensionStartTime(): Promise<number> {
  await didExtensionStarted;
  const {[KEY]: startTime} = await browser.storage.session.get(KEY);
  return startTime;
}
2 Likes

Do you want to support both Firefox and Chrome from same source?

Just want to add, that to use browser namespace instead of chrome name space, you might add something like this in top of your scripts:

globalThis.browser ??= chrome;

Also Chrome and Firefox supports backgroundscripts in different ways. But you can make a manifest with something like


  "background": {
    "scripts": ["backgroundscript.js"],
    "service_worker": "backgroundscript.js",
    "type": "module"
  },

and make sure your backgroundscript.js is a “module” that is able to work both as a “service_worker” (Chrome) and as a “classic” background script (Firefox). If a browser supports both ways, it will run it as a service worker.

2 Likes

Ahh the “module” part is new for me. Many thx for this tip.

This looks… complicated. But anyway, i will give this a try.

In the meantime, i figured out, that testing my extension with web-ext run --keep-changes seems to ignore chrome.runtime.onStartup.addListener(), thats why it wasnt executed.

One question to :

Basically, you background page/script is automatically killed after 30s of inactivity.
So when there is an event that “wakes it up”, it’s not really being woken up, it’s just re-executed from the top.

If i register something like chrome.bookmarks.onCreated.addListener() in the described init() function, does this listener survive killing the background script after 30s or must i re-register it with every wakeup?

The onStartup will be executed only when profile is started:

So it’s very likely it won’t run after installing/updating your extensions, so watch out! :slight_smile:

Regarding bookmarks.onCreated, yes, you need to register that handler on top of your background script. In the “init” function it’s also fine, BUT only if it’s executed when the background scrip is executed. Also, it can’t be async, I think…

Basically, when your extension is installed, browser will quickly run your background script, to see, what handlers it’s listening to. And those are then used to “wake it up”.
But if the handler is registered in some callback that runs “later” (async), then browser will not pick it up.

1 Like

The init process does not have to run immediately after installation or update, for this, i use chrome.runtime.onInstalled . It is only important that init() starts once when the browser is started, but not multiple times.

Thanks to the tips here, I have now modified the script and uploaded an updated version to AMO. As far as I can tell, everything has worked. If something doesn’t work, I’ll monitor it over the next few days and report back. Until then, I consider it solved for now.

2 Likes