Detecting unexpected shutdown?

Is there any way for my onStartup handler to tell whether the browser is recovering from a crash?

I’m writing an add-on that will allow users to automatically delete cookies and site data only from the default tab container each startup; but, obviously, deleting that data in the case of a browser crash would be unexpected and harmful — even Firefox itself keeps tabs for users who said “don’t keep my tabs on startup” in that case, because semantically it’s not really a browser restart.

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")();
});
1 Like

Ouch. Thanks for the datapoint, anyway :eyes: