Unfortunately I don’t know of any way to accomplish what you’re after. To the best of my knowledge there’s no public API that signals whether or not the browser cleanly shut down, and the only idea I had for a workaround didn’t pan out.
FWIW, the workaround I had in mind synchronously set a “running” flag in localStorage
using a runtime.onSuspend
listener. This listener is called “just before [the background context] is unloaded” during normal operation, but unfortunately it doesn’t seem to be called when the browser itself is shutting down.
Test extension
To use this extension, you’ll first need to follow the instructions in Testing persistent and restart features. Once the browser is correctly configured, create a zip archive that contains these extension files and load it on about:addons
as documented.
You will need to open a debugger session for the extension shortly after starting a profile with this extension installed in order to view the results of the test. If you the background terminates before you open a debugger session, the messages logged during initial startup will be lost.
manifest.json
{
"name": "Sync Listener Workaround",
"version": "1.0",
"manifest_version": 3,
"background": {
"scripts": ["background.js"]
},
"action": {},
"host_permissions": ["https://example.com/*"]
}
background.js
logPoint("init")();
assertNotRunning("init")();
setTimeout(() => {
logPoint("init-timeout-1")();
assertNotRunning("init-timeout-1")();
console.log(`init-timeout: Startup timer expired, force-setting running`);
setRunning(true);
logPoint("init-timeout-2")();
assertRunning("init-timeout-2")();
}, 10);
browser.runtime.onSuspend.addListener(() => {
logPoint("onSuspend")();
assertRunning("onSuspend")();
setRunning(false);
});
browser.runtime.onSuspendCanceled.addListener(() => {
logPoint("onSuspendCanceled")();
assertNotRunning("onSuspendCanceled")();
setRunning(true);
});
browser.runtime.onStartup.addListener(() => {
logPoint("onStartup")();
assertNotRunning("onStartup")();
});
function assertRunning(context) {
const isRunning = getRunning();
if (isRunning === undefined) { return }
return console.assert.bind(console, isRunning, `${context}: "running" is ${isRunning} (${typeof isRunning}), expected ${true} (${performance.now()})`);
}
function logPoint(...args) {
return console.log.bind(console, ...args, getRunning(), performance.now());
}
function assertNotRunning(context) {
const isRunning = getRunning();
if (isRunning === undefined) { return }
return console.assert.bind(console, !isRunning, `${context}: "running" is ${isRunning} (${typeof isRunning}), expected ${false} (${performance.now()})`);
}
function getRunning() {
return JSON.parse(localStorage.getItem("running"));
}
function setRunning(val) {
localStorage.setItem("running", val);
}
browser.action.onClicked.addListener(() => {
logPoint("onClicked")();
assertNotRunning("onClicked")();
});