No way to detect that a window or tab is closing before it's closed

In the WebExtensions API, windows.onRemoved and tabs.onRemoved are only fired after the window or tab in question has already been removed and destroyed. As such, the ID provided to the event handlers is useless: you can’t retrieve the closing window or tab through windows.get or tabs.get, because it’s already been removed.

There should be an onRemoving event that’s fired before the tab or window object is destroyed, which waits until all listeners return, so that extensions can still access and manipulate them before they’re removed.

Does anybody know of any hacky workarounds for this problem?

I can’t think of anything better than the rather obvious solution to get the data you need on all open tabs/windows and keep it until they are closed. This won’t be a problem for windows, but a user can have quite a number of open tabs.

My use case here is specifically for pinned tabs. I was writing an extension that migrates pinned tabs from closed windows to another open window, so they won’t be lost.

Technically, keeping track of the addresses for each pinned tab is pretty low-effort, but the problem is that the tabs won’t actually be moved, I’ll be closing them and then opening new tabs with the same URL. This obviously makes it useless for maintaining the state of the page. You lose anything typed into forms, the state of any dynamic elements, etc. For web apps, it’s actually worse than suffering a browser crash. At least after a browser crash the session gets restored.

The ironic thing is, a web page here actually has more power than an “extension” made with the WebExtensions API. A web page can actually be notified when it’s about to be closed, and stop the window from closing, to prevent you from losing data. Says a lot about Mozilla’s decision to kill the old Firefox APIs in favour of WebExtensions.

You can install "beforeunload" listeners on content pages from content scripts. You’d probably be able to send a message to the background and then return a string (opening the prompt to the user).
When receiving the message, the background script could move all pinned tabs in the window (which isn’t closed yet due to the prompt) and close that window again (I think a second close on a tab/window will force-close the window even if a close prompt is still open).

I’m reasonably confident that this works.

Yeah, I’d considered something like that, but I was hoping to avoid modifying the pages themselves, if possible. I didn’t know about that force-close, thing, though. If I have to go that route, that’ll come in handy. Thanks!

It is what happens if you mouse-wheel-close a tab again that’s already prompting, anyway. I can’t promise Firefox will behave for extension-triggered closes.

Hello, this is indeed a good question. Because I’m facing the same issue with a add on, where I need to trigger a clean up action when the window is closed, in particular flagged for removal cookies, in order to provide the user with a clean state, the next time the domain is opened.

I tried using the “beforeunload” idea as suggested, but from within the background script or the default_popup loader, the script won’t hook into this event as it seems.

Also, registering the script as content script using “contentScripts.register()” does only provide a option to act on the document in particular using “run_at” parameter while loading is in progress or finished but all data already transmitted. Not when the window is closed and the document unloaded.

Is there any other chance to trigger a function of a background script using messaging or similar solution - which does work when the windows is being closed?

Help or ideas would be much appreciated.

Other then that, as for my case, maybe this would require to intercept the loading request of the particular domain/urls using “webrequests” permission, but if that’s in anyway avoidable, I rather would prefer another solution.

The original problem here was/is that you can’t use the id of an jaust closed tab to get information about that tab.

It seems you have your information (“flagged for removal cookies”). So knowing that a tab with a certain id was closed seems like it is enough for you.
You’d use the browser.tabs.onRemoved event for that.

Also. It seem that you got the different execution contexts in WebExtensions totally mixed up.

As for my case, I would require to know the exact tab domain/url the tab was pointing to, even when the window is closed, I would require to get this information for all related window tabs in order to run a cleanup function for each of them, which might or might not be affected by the add-on/user settings of the add-on.
So, the original problem also affects me in that case.

But when the window is closed, those information aren’t available anymore, as the initial problem description states.

I made a test, when I close a window with two domains, only the first tab seems to be affected by the close/removal event and the other stays untouched and the add-on does not trigger correctly. So it works only partially for me.

That would be really odd, concidering that the second argument to the event listener has a isWindowClosing boolean property.

I would require to know the exact tab domain/url […] in order to run a cleanup function for each of them, which might or might not be affected by the add-on/user settings

I see. I don’t think you can get around doing something like this:

const urls = new Map/*<id, url>*/;
function onCreated({ id, url, }) { urls.set(id, url); }
function onUpdated(id, { url, }) { url !== undefined && urls.set(id, url); }
function onRemoved(id) { try {
    if (careAboutUrl(urls.get(id)) { cleaup(urls.get(id)); }
} finally {
    urls.delete(id);
} }

And add those listeners. Doesn’t seem to bad in this case.

I need to keep track if a lot more tab properties, including e.g the .index (which is not included in onUpdated), in an extension because I need to access the information ASAP. That gets somewhat more difficult:
https://github.com/NiklasGollenstede/unload-tabs/blob/master/background/tabs.js

Im using several strategies now:

Using a “tabs.onRemoved” handler plus a “windows.onRemoved” handler.

The code is located here:

What it basically does, a timeout on “onCreated” (tab + windows) triggers a timeout - which reads out urls of the tabs after 5 seconds, so Firefox has a chance to provide the correct url instead of “about:blank”.

The tab is stored with this information:
[window.id][tab.id] = {“s”: cookieStoreId, “u”: url, “d”: domainName}

This does work, when checking the debug log of the add-on at window close / tab remove event - if thats a secondary window.

But on the main window, this fails.

I checked by:

  1. Logging into a website/service
  2. Using “global” to flag and remove all cookies on page action (reload, load and such)
  3. Opening a new tab (about:blank)
  4. Closing the window

What should happen, the action to clean the cookies should apply on window close/tab removal as for other windows.

After reopening Firefox, the about:blank tab is shown:

  1. Deactivate “global” (it will trigger on page load after init of Firefox)
  2. Switch to the particular tab
  3. Refresh the page
  4. Still logged in, even so cookies should have been removed.

As mentioned, on subwindows (every other then the last) this does seem to work. According to the logs and what I tested.
On the main window, which becomes closed, it doens’t apply.

Issuing a UI commend to close the last Firefox window doesn’t close the window but exits the Firefox application (which in turn closes the window).

So it is to be expected that you do not get tab and/or window close events for that. By the time that happens your extension is already unloaded.

What you might want to do is just clear all cookies on startup, or put the open tabs in non-volatile storage and process them at startup (maybe unless they were restored).

The tab is stored with this information:
[window.id][tab.id] = {“s”: cookieStoreId, “u”: url, “d”: domainName}

The windowId of tabs can change. But the tab.id is already unique across all windows. There is no need for a tow-level storage.

And I also verified what I said earlier: When a window is closed, you do get onRemoved events for all it’s tabs.

It simply doesn’t make sense. Why do you receive a close event and can trigger actions for sub windows, but not the “main window”?

I think that’s a bug. Not a feature.

Running cleanup on start is not an option, because most likely the tab has already loaded. And hence, the user cookies were visible at that time, which is not desired. They should have been cleaned on window close.

Why do you receive a close event and can trigger actions for sub windows, but not the “main window”?

There are no sub and main windows. You get close events for all windows that are being closed. When Firefox quits, it stops reporting events (performance). If you quit Firefox with 100 open windows, you shouldn’t get events for any of them.

Running cleanup on start is not an option, because most likely the tab has already loaded. And hence, the user cookies were visible at that time, which is not desired.

Maybe. Most stuff should actually be read from cache.

If you interpret quitting Firefox as closing all open tabs, and you only want to keep cookies of open tabs, then you don’t want to keep anything. Firefox already has the option to clear cookies on shutdown (and may even expose that to extensions).

Actually my phrasing is not right: In case you have two open windows, you close the second one, the “windows.onRemoved” is triggered. Also, “tabs.onRemoved” is triggered.
All as expected.

In case you close the last open window, no listener is triggered at all.

And that’s a difference, in my add-on I offer the option to specify what cookies to keep and which not by setting options.

By default and from what I know about the “clear history” option of Firefox - will erase all cookies, no matter what, but in this use case, the user wants to keep other cookies, while erasing others.

There’s just no fine grained selection on what is erased and what not.

And, the case, that no listener is triggered when closing Firefox is simply wrong in that use case - cause there’s no way to react to that accordingly.

I guess one can disagree on whether the current behavior is good. I think so, you don’t. Let’s leave it at that.

The only time when you can handle the the closing of the last window is on the next start.
You may want to have a look at https://github.com/Cookie-AutoDelete/Cookie-AutoDelete, maybe that extension already solves your problem.

No offense about another add-on. Just mine is different in usage and purpose.

It’s just, if one tries to guard against cookie tracking, its not feasible to have a Timeout “after the page loaded and read out all cookie information” to clean up the cookies after that has taken place. At this point, the user was tracked already.

I agree, using the “history delete” of Firefox is one way to avoid tracking at all through cookies, as every data gets wiped - but again, there is no control about particular cookies to keep, to have the comfort to stay logged in in some services, while removing just everything else.

I guess I will file a bug in the bugtracker and see how far this goes, because for me, the current behavior to not report a window close or tab removal or anything like that any add-on can react on, is simply an annoyance.

Performance wise, I’m talking about 1 window and not 100… its the same as if I have 2 windows with 200 tabs or one windows with 200 tabs, there is simply no difference that this should be treated differently. For the 2nd window with 200 tabs actions will trigger and perform, for the first window or last simply not - this doesn’t make sense.