MV3: Second (excessive) service worker in "waiting" state after adding Options page

235 views
Skip to first unread message

Pawel Kacprzak

unread,
Jul 27, 2023, 1:09:21 PM7/27/23
to Chromium Extensions
I'm working on adding options page to my popular MV3 extension. 

After adding it and playing with the extension, I noticed that, after some time, a second service worker client is trying to activate, i.e. it's installed and waiting to replace the currently active one. 

As soon as this second worker appears, it becomes impossible to send messages from a content script to a service worker - messages are not being received in the service worker. 

Does anyone know what's happening there, or maybe even experienced this issue? I'm trying to find a way to reproduce it consistently but so far had no idea why it happens.

Is it somehow possible that adding the options page causes this issue? For example, if the options page staying open in a tab messes up the service worker lifetime?

For the context, I'm also using this method to re-register a service worker as a workaround to https://bugs.chromium.org/p/chromium/issues/detail?id=1316588

Simeon Vincent

unread,
Jul 27, 2023, 2:16:56 PM7/27/23
to Pawel Kacprzak, Chromium Extensions
The behavior you described sounds like it may be crbug.com/1271154. A fix for that bug should ship in Chrome 117.

In my testing, it seemed that this bug was caused by a mismatch between how service worker updates are handled on the web and in extensions. On the web, when the user first navigates to a page backed by a service worker, the browser will check the server for an updated version of the service worker and, if so, begin installing the update. While that makes sense for websites, it doesn't match the expectations of the extension system. In order to avoid this problem, the fix will skip this service worker update check for packaged extensions.

I have not tested this, but adding the following listener to an extension's service worker may help resolve the issue.

self.addEventListener("activate", (event) => {
  event.waitUntil(clients.claim());
});

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.
To view this discussion on the web visit https://groups.google.com/a/chromium.org/d/msgid/chromium-extensions/a206f760-2e1a-43c0-8a62-f7da38f2b5e2n%40chromium.org.

Pawel Kacprzak

unread,
Jul 27, 2023, 3:17:28 PM7/27/23
to Chromium Extensions, Simeon Vincent, Chromium Extensions, Pawel Kacprzak
Thanks, sounds like a promising explanation. A few questions:

1. Where should I get `clients` from? Are you referring to `event.target.clients`? or maybe to something else
2. Are we trying to listen for "activate" event of the attempts to activate subsequent service worker clients? 

because when I go to chrome://serviceworker-internals/ after the issue occurs, I see the following info:

   Active worker:
Installation Status: ACTIVATED
...
Waiting worker:
Installation Status: INSTALLED

so maybe we should try to block installations of any subsequent service worker clients?



Simeon Vincent

unread,
Jul 27, 2023, 6:14:27 PM7/27/23
to Pawel Kacprzak, Chromium Extensions
1. Where should I get `clients` from? Are you referring to `event.target.clients`? or maybe to something else

The clients property is exposed on service worker global scope. In other words, you don't have to do anything special to access it inside your extension service worker. See MDN's documentation on the Clients interface for more examples.

2. Are we trying to listen for "activate" event of the attempts to activate subsequent service worker clients? 

Whoops! I used the wrong event in that code snippet. I meant to use "install". Also, it would be best to skip the waiting phase after "install" completes, otherwise activation of the new service worker will be delayed until all pages loaded with the old service worker are closed. Here's an updated snippet that should better accomplish its intended purpose. 

self.addEventListener("install", (event) => self.skipWaiting());

If you try this, I'd be very curious to hear if it helps.

Simeon - @dotproto

Pawel Kacprzak

unread,
Jul 27, 2023, 7:02:15 PM7/27/23
to Chromium Extensions, Simeon Vincent, Chromium Extensions, Pawel Kacprzak
This seems to work flawless! 

Do you know if there any potential disadvantages of 'skipWaiting' in the context of service workers in extensions? For the context, my service worker uses fetch to communicate with servers, listens to both internal and external messages, uses storage.local and storage.session.

Also, I found a method to reproduce the behaviour I described in my first message in this thread, not sure if it's the minimal one though:

1. Install the extension
2. Open extension's options page and keep its tab opened - this seems to be crucial
3. Open a new tab and trigger sendMessage to the service worker (in my case it's clicking on extension's icon. to inject html into the page, and I do it using popup method described here: https://groups.google.com/a/chromium.org/g/chromium-extensions/c/MesMv9ugQIQ/m/0UbTZFwGBAAJ
4. Make any change to the service worker source (e.g. add line const foo = 42;), to cause update of the service worker - I'm using webpack if that matters
5. Go to the tab from step 3. and again trigger sendMessage
6. Open another new tab, and again trigger sendMessage to the service worker - now service worker does not listen to sent messages.

Simeon Vincent

unread,
Jul 27, 2023, 7:58:19 PM7/27/23
to Pawel Kacprzak, Chromium Extensions
Do you know if there any potential disadvantages of 'skipWaiting' in the context of service workers in extensions?

For non-extension service workers, skipWaiting can lead to unexpected behavior. From https://web.dev/service-worker-lifecycle:

Caution
skipWaiting() means that your new service worker is likely controlling pages that were loaded with an older version. This means some of your page's fetches will have been handled by your old service worker, but your new service worker will be handling subsequent fetches. If this might break things, don't use skipWaiting().

It's uncommon to handle fetch events (self.addEventListener("fetch", (event) => { ... })) in an extension's service worker, so that warning is not directly applicable to most extensions. That said, your individual situation may be different.

Also, I found a method to reproduce the behaviour I described in my first message in this thread, not sure if it's the minimal one though:

Yep, that's very similar to the repro case I shared in comment 105 on issue 1271154. A fix for that has been merged and should go out with Chrome 107.

Simeon - @dotproto

Pawel Kacprzak

unread,
Jul 27, 2023, 8:07:41 PM7/27/23
to Simeon Vincent, Chromium Extensions
Awesome, thank you so much! 

My service worker doesn't have listeners for fetch events, it uses Fetch API to perform web requests, so I assume that should be safe.


Yep, that's very similar to the repro case I shared in comment 105 on issue 1271154. A fix for that has been merged and should go out with Chrome 107.

It's indeed very similar to this case. Btw you probably meant that it should go out with Chrome 117 if I understand correctly.   
Reply all
Reply to author
Forward
0 new messages