How to debug offscreen document and its scripts?

939 views
Skip to first unread message

Kein Zantezuken

unread,
Jul 13, 2023, 2:46:26 PM7/13/23
to Chromium Extensions
`chrome://inspect/#other`  shows me it exist, but I cannot inspect it, it just opens background script/worker domain. I cannot have a reference to the created document either.

Another issue, if offscreen document and its included scripts are created inside background-service worker, why can't I use `chrome.tabs.onRemoved` API inside it?

Patrick Kettner

unread,
Jul 13, 2023, 2:51:26 PM7/13/23
to Kein Zantezuken, Chromium Extensions
Hey Kein!

There is a bug with offscreen documents not showing up in devtools, it was fixed in chrome 116 (I believe). Using canary or chromium nightly will get you sorted. Alternatively, go to chrome://flags/#devtools-tab-target and disable that feature (Its a good feature, but when it is enabled before the bug fix, you get that behavior).

> why can't I use `chrome.tabs.onRemoved` API inside it

chrome apis are not available inside of offscreen documents.

On Thu, Jul 13, 2023 at 2:46 PM Kein Zantezuken <zante...@gmail.com> wrote:
`chrome://inspect/#other`  shows me it exist, but I cannot inspect it, it just opens background script/worker domain. I cannot have a reference to the created document either.

Another issue, if offscreen document and its included scripts are created inside background-service worker, why can't I use `chrome.tabs.onRemoved` API inside it?

--
You received this message because you are subscribed to the Google Groups "Chromium Extensions" group.
To unsubscribe from this group and stop receiving emails from it, send an email to chromium-extens...@chromium.org.
To view this discussion on the web visit https://groups.google.com/a/chromium.org/d/msgid/chromium-extensions/b939fb7f-328d-4b88-b643-6a52656bf866n%40chromium.org.

Kein Zantezuken

unread,
Jul 13, 2023, 4:17:18 PM7/13/23
to Chromium Extensions, Patrick Kettner, Chromium Extensions, Kein Zantezuken
>  chrome apis are not available inside of offscreen documents.

How am I supposed to use `tabs.OnRemoved` in any sane and reliable way? If I put it in a service_worker script, chrome will hibernate it and until it wakes up I will miss every single `tabremoved` message. I need to keep tabs on, well, tabs, 1:1, tracking all closed to clean up data.

Simeon Vincent

unread,
Jul 13, 2023, 5:40:55 PM7/13/23
to Kein Zantezuken, Chromium Extensions, Patrick Kettner
How am I supposed to use `tabs.OnRemoved` in any sane and reliable way? If I put it in a service_worker script, chrome will hibernate it and until it wakes up I will miss every single `tabremoved` message.

Extension service workers shouldn't behave this way. The expected behavior is that an extension's service worker will be stopped when it is idle and started again in response to events for which it has registered listeners.

If your extension is not receiving some events, make sure that the relevant event listener is registered on the first execution of your service worker script. If you register a listener asynchronously, it's possible that Chrome will start the service worker, check for currently registered listeners, find none, drop the event, and some time later your event listener registration occurs. In order to dispatch the event correctly, the listener registration must happen as soon as possible.

If you can reliably reproduce a situation where you have a synchronously registered listener and Chrome still drops tabs.onRemoved events, please open a bug report at crbug.com/new

Simeon - @dotproto


Browser Extenstion

unread,
Jul 13, 2023, 6:02:57 PM7/13/23
to Chromium Extensions, Simeon Vincent, Chromium Extensions, Patrick Kettner, Kein Zantezuken
You don't need open new bug.
The bug has existed since the appearance of the service worker: crbug.com/1337294
No one even tries to fix it, so yes, all the above events will not work.

Kein Zantezuken

unread,
Jul 14, 2023, 3:24:57 AM7/14/23
to Chromium Extensions, Simeon Vincent, Chromium Extensions, Patrick Kettner, Kein Zantezuken
It is a top statement in my servce-worker, it can't be any more simpler:
`chrome.tabs.onRemoved.addListener(OnAnyTabRemoved);`

The behavior Io observe is that while service worked is active, it works as expected and messages are dispatched, but once it gets hibernated, the next event message gets "eaten", then,after worker is resumed again, everything works, until it is suspended. The cycle repeats.

I suppose I simply can't rely or use ` onRemoved  ` event (or an other events for that matter) in any meaningful way - the way services_workers implemented in MV3 is simply useless and pointless.

Oliver Dunk

unread,
Jul 14, 2023, 4:51:15 AM7/14/23
to Kein Zantezuken, Chromium Extensions, Simeon Vincent, Patrick Kettner
Hi Kein,

Are you able to share the code for your service worker/is it available on the store?

One thing that I'd be interested to rule out is how your listeners are registered. There are some requirements in service workers about registering synchronously and any issues there would lead to behaviour very similar to what you're describing.
Oliver Dunk | DevRel, Chrome Extensions | https://developer.chrome.com/ | London, GB


Max Nikulin

unread,
Jul 14, 2023, 6:58:40 AM7/14/23
to chromium-...@chromium.org
On 14/07/2023 05:02, Browser Extenstion wrote:
> The bug has existed since the appearance of the service worker:
> crbug.com/1337294
> No one even tries to fix it, so yes, all the
> above events will not work.
On 14/07/2023 03:17, Kein Zantezuken wrote:
> How am I supposed to use `tabs.OnRemoved` in any sane and
> reliable way?

Is it specific to tabs.onRemoved or other events like action.onClicked
are affected as well? It might be
https://crbug.com/1271154 "MV3 service worker broken after auto-update
and manual refresh"

Simeon Vincent

unread,
Jul 14, 2023, 12:48:00 PM7/14/23
to Max Nikulin, chromium-...@chromium.org
Kein, I just ran a minimal test with the below extension and was not able to reproduce the behavior you described. In my testing the extnesion's service worker was restarted in response to the onRemoved event and logged a message to the console as expected. At this point I suspect that the issue is likely related to how your extension is regeistering it's listenter.

manifest.json
{
"name": "tabs.onRemoved test",
"version": "1",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"permissions": ["tabs"]
}


background.js
chrome.tabs.onRemoved.addListener(function (tabId, removeInfo) {
console.log(tabId, removeInfo);
});

Simeon - @dotproto


--
You received this message because you are subscribed to the Google Groups "Chromium Extensions" group.
To unsubscribe from this group and stop receiving emails from it, send an email to chromium-extens...@chromium.org.

Kein Zantezuken

unread,
Jul 14, 2023, 2:52:44 PM7/14/23
to Chromium Extensions, Oliver Dunk, Chromium Extensions, Simeon Vincent, Patrick Kettner, Kein Zantezuken
Not sure what do you want to find, here is test code:

Max Nikulin

unread,
Jul 14, 2023, 10:55:15 PM7/14/23
to chromium-...@chromium.org

On 15/07/2023 01:52, Kein Zantezuken wrote:
> Not sure what do you want to find, here is test code:
> https://gist.github.com/Kein/4c1a8cb20b246047c9fbc8e35ec2ead1

It is unrelated to the tabs.onRemoved issue, but it might be affected by
an offscreen issue https://crbug.com/1451659

On 06/06/2023 08:32, Jackie Han wrote:
> Update: I found this bug only happens when you put the
> `createOffscreen()` at service worker startup. If you use it latter,
> `clients.matchAll()` can return offscreen doc, but when it runs at
> startup, it doesn't return offscreen doc.

https://groups.google.com/a/chromium.org/d/msgid/chromium-extensions/CAAgdh1J-7UYMTf6YNBfyqSyJLO3V9MuoM7SF8vJ3FVhC6sSB8w%40mail.gmail.com?utm_medium=email&utm_source=footer

Jackie Han

unread,
Jul 15, 2023, 12:42:06 AM7/15/23
to Max Nikulin, chromium-...@chromium.org
That code doesn't use `clients.matchAll()` to find the offscreen doc, it just calls `createDocument()` every time on startup that should not be a problem.

I simplified that code (see below) and tested it, but found no problems.

chrome.tabs.onRemoved.addListener(onRemove);
(async () => {
    try { await chrome.offscreen.createDocument({……}); }
    catch (error) { console.log(error); }
})();
function onRemove() {
  chrome.runtime.sendMessage('msg'); // 
check your message receiver
  // or use below code to see a visible effect
  // chrome.tabs.create({url:'https://www.google.com'}); 
}


I know only one situation where tabs.onRemoved is unreliable: when closing the last tab(window) and the browser quits completely.

--
You received this message because you are subscribed to the Google Groups "Chromium Extensions" group.
To unsubscribe from this group and stop receiving emails from it, send an email to chromium-extens...@chromium.org.

Max Nikulin

unread,
Jul 15, 2023, 11:00:11 AM7/15/23
to chromium-...@chromium.org
On 15/07/2023 11:41, Jackie Han wrote:
> That code doesn't use `clients.matchAll()` to find the offscreen doc, it
> just calls `createDocument()` every time on startup that should not be a
> problem.

It was just a guess that creating an offscreen document before
activation of service worker might affect more than `clients.matchAll()`.

I have already posted the link https://crbug.com/1271154 "MV3 service
worker broken after auto-update and manual refresh". I have another
hypothesis why it may be relevant. A tab with an extension page may
block activation of new version of service worker. An offscreen document
might have similar effect.

Kein, when an onRemoved even is missed, is there single service worker
for the extension reported by chrome://serviceworker-internals/ ? When
the .js file is modified, the new one may wait for activation.

Kein Zantezuken

unread,
Jul 15, 2023, 1:10:55 PM7/15/23
to Chromium Extensions, Max Nikulin
No, this happens without reloading extension, even if I do cold start with current version and test it,

Max Nikulin

unread,
Jul 16, 2023, 7:58:17 AM7/16/23
to chromium-...@chromium.org
On 16/07/2023 00:10, Kein Zantezuken wrote:
> No, this happens without reloading extension, even if I do cold start
> with current version and test it,

Kein, I have tried to add missed files to your gist. I see no issue with
calling of tabs.onRemoved (I have added console.log to the listener).
However I get "Could not establish connection. Receiving end does not
exist." It seems, the offscreen document is created too early. The error
disappears when I add another runtime.onMessage listener from the
offscreen page console.

While I was converting .ts code to .js, I have seen 2 handlers of
"tabs.onRemoved" in chrome://extensions-internals/. I did not check how
many service workers the extension had that moment accordingly to
chrome://serviceworker-internals/

So simplify you example and make it complete. Try to distinguish
runtime.onMessage and tabs.onRemoved issues. To be on the safe side I
would create the offscreen document after the service worker is activated.

Jackie Han

unread,
Jul 16, 2023, 8:57:08 AM7/16/23
to Max Nikulin, chromium-...@chromium.org
> "Error: Could not establish connection. Receiving end does not exist."

This may be another issue. On Chrome Canary, I found when the browser cold start (not reload), there is only a service worker, no offscreen doc, then the first of sending a message will meet this error.


Anyway, I suggest creating the offscreen doc when you need it, like below:

chrome.tabs.onRemoved.addListener(async (tabId, removeInfo) {
  try { await chrome.offscreen.createDocument({……}); }
  catch (error) { console.log(error); }
  chrome.runtime.sendMessage('msg');
});



--
You received this message because you are subscribed to the Google Groups "Chromium Extensions" group.
To unsubscribe from this group and stop receiving emails from it, send an email to chromium-extens...@chromium.org.

Kein Zantezuken

unread,
Jul 16, 2023, 11:02:02 AM7/16/23
to Chromium Extensions, Max Nikulin
> To be on the safe side I would create the offscreen document after the service worker is activated.

This suggestion makes no sense to me, the code is inside service-worker, in its body, it is being ran when it is activated/loaded.

Kein Zantezuken

unread,
Jul 16, 2023, 5:49:55 PM7/16/23
to Chromium Extensions, Jackie Han, chromium-...@chromium.org
@Jackie Han

Oh, this made me realize that I have another bug - I do not destroy the offscreen page on extension reload. Can this also cause any issues?

Kein Zantezuken

unread,
Jul 16, 2023, 5:57:37 PM7/16/23
to Chromium Extensions, Jackie Han
> Anyway, I suggest creating the offscreen doc when you need it, like below:

What happens to previously registered listener? Is it removed somehow automagically, like, it is nulled by GC and then removed by event dispatcher during dispatch check? Otherwise I would need to cleanup/remove it on worker suspension.

Jackie Han

unread,
Jul 17, 2023, 3:19:45 AM7/17/23
to Kein Zantezuken, Chromium Extensions
I do not destroy the offscreen page on extension reload. Can this also cause any issues?
It should not be a problem. when reloading the extension, the browser will close all opened extension documents first, then start the extension.

What happens to previously registered listener?
When the service worker terminates (I mean suspend or inactive), the listener is also removed (here is the tabs.onRemoved listener). But don't worry, the browser remembers that event type. When an event is triggered (a tab is closed), the service worker wakes up, so the listener is added again (if it is registered in the top level or first event loop). And the browser calls the listener.

Max Nikulin

unread,
Jul 17, 2023, 6:51:43 AM7/17/23
to chromium-...@chromium.org
On 16/07/2023 22:02, Kein Zantezuken wrote:
> > To be on the safe side I would create the offscreen document after
> the service worker is activated.
>
> This suggestion makes no sense to me, the code is inside service-worker,
> in its body, it is being ran when it is activated/loaded.

Jackie posted an example how to postpone creation of the offscreen
document till invocation of event handler. Maybe I am too paranoid
trying to avoid creation of the offscreen document when new version of
service worker code is loading, but the service worker has not been
activated (or even installed accordingly to its lifecycle).

Kein, your code has a race conditions. Creation of the offscreen
document and the onRemoved handler are running concurrently. So there is
a chance that runtime.sendMessage in the service worker is called before
runtime.onMessage.addListener for the offscreen document. So wait till a
createDocument promise is resolved or rejected before sending a message.

I find a typo in my offscreen.html. Now I see "Could not establish
connection. Receiving end does not exist." only in the cold start
scenario. tabs.onRemoved is called reliably, but first call after
starting browser can not pass message to the offscreen document. There
is no such problem when the service worker is just stopped due to
timeout when the offscreen document exists. Currently I have no
evidences that Chromium-114 may have a bug related to this use case.

Kein Zantezuken

unread,
Jul 17, 2023, 11:14:02 AM7/17/23
to Chromium Extensions, Max Nikulin
Max, but wouldn't this race condition only be relevant during the very first -- cold -- startup OR full extension reload? I mean, afterwards, the offscreen is persistent and exist.

Max Nikulin

unread,
Jul 17, 2023, 12:30:35 PM7/17/23
to chromium-...@chromium.org
On 17/07/2023 22:14, Kein Zantezuken wrote:
> Max, but wouldn't this race condition only be relevant during the very
> first -- cold -- startup

I hope so. However this is the only case when I see an issue with code
based on your gist. Jackie confirmed the same. Simeon have not observed
any trouble with pure tabs.onRemoved. On the other hand there is
https://crbug.com/1337294

If the issue is reproducible for you then it might be related with some
other code.

Anyway, I think if you goal is reliably working extension than you
should handle cold start as well.

> OR full extension reload?

It was my fault that I missed an error preventing loading of offscreen
JS, Since onClosed usually happens with some interval after reloading,
the race should be a rare trouble. During development you may be
affected by automatic service worker reloading in response to
modification of its .js file, see chrome://serviceworker-internals/

> I mean, afterwards,
> the offscreen is persistent and exist.

I consider "Not currently limited" in
https://developer.chrome.com/docs/extensions/reference/offscreen/#reasons
as a rather weak promise. From my point of view it might be changed to
arbitrary timeout any time. That is why I would not rely on its persistence.

On 16/07/2023 00:10, Kein Zantezuken wrote:
> No, this happens without reloading extension, even if I do cold start
> with current version and test it,

If you mean just restarting the browser when writing "cold start" then I
would not consider it as a clear case. Additional requirement is that
the service worker .js file has not changed since activation. I would
prefer:

- Remove the extension
- Load the extension
- Restart the browser

without modification on the code as a really pure case for reproducing.

Kein Zantezuken

unread,
Jul 17, 2023, 2:59:12 PM7/17/23
to Chromium Extensions, Max Nikulin

@Max

I see. The issue with this:

> Jackie posted an example how to postpone creation of the offscreen
document till invocation of event handler. 

is that because offscreen page and, therefore, its script(s) are the only close-to-persistent thing we can have, I use it as a "service" essentially (basically, thanks for Chromium team, we now have to do mental gymnastics to get old good persistent background_page), service that has persistent (runtime persistent) data and handles all the dispatch calls. So I NEED it to be created at once when service_worker starts, not before any of the tabs are closed and the relevant event is dispatched (because it may not happen but offscreen-service functionality is already needed).

> Since onClosed 

Can you elaborate on this? I can't find it in API, is there a way to know when service_worker is being/about to reloaded via extension page?

Max Nikulin

unread,
Jul 18, 2023, 10:57:58 AM7/18/23
to chromium-...@chromium.org
On 18/07/2023 01:59, Kein Zantezuken wrote:
>
> is that because offscreen page and, therefore, its script(s) are the
> only close-to-persistent thing we can have,

I am curious why offscreen is a better storage than `storage.session`
https://developer.chrome.com/docs/extensions/reference/storage/#storage-areas
It is asynchronous and JSON serialization is involved due to messaging.

> So I NEED it to be created at once when service_worker
> starts, not before any of the tabs are closed and the relevant event is
> dispatched

Sorry, I do not know you use case. From my point of view it should be
possible to implement lazy initialization with a global variable holding
a `Promise` to the result of the initialization function. With such
approach it should not matter whether the code is executed on loading of
the service worker or in response to event handler. Even if you still
prefer to call a function at the loading time then save the returned
promise to a variable and `await` for it in event handlers.

> > Since onClosed
>
> Can you elaborate on this? I can't find it in API, is there a way to
> know when service_worker is being/about to reloaded via extension page?

My point was that you should not experience any issues during manual
testing. When you click on the extension reloading button, there is some
time interval between service worker loading and closing a tab. It
should be enough to create the offscreen document. When the service
worker is stopped, the offscreen document persists and ready to react to
messages. The only exception is cold start.

I do not think there is API that allows to execute some code before
reloading, but during loading you may test whether it is
installation/update or resuming, see
- serviceWorker.state
- self.addEventListener("activate", /*...*/)
Reply all
Reply to author
Forward
0 new messages