1

Last month, this bug-fix made its way to Chrome stable channel release.
This theoretically allows the lazy registration of non-blocking webRequest event listeners even when the service-worker is put into inactive state, like:

chrome.webRequest.onBeforeSendHeaders(myHandler);

/**
 * @arg {chrome.webRequest.WebRequestDetails} details 
 */
function myHandler(details)
{
    // processing request's details …
}

The extension I am working on listens to requests sent from any tab of the active window and spots specific requests based on some criteria. To avoid unnecessary work, the extension exposes a on/off button within the popup page which, when clicked, will send the appropriate signal via chrome.runtime.sendMessage to the web-worker which in turn will attach/detach a given handler for webRequest.onBeforeSendHeaders event. As a consequence, the registration doesn't occur at the service-worker top level but within a message response handler.
So basically, the request listener is turned off by default and it can be started/stopped when needed.

Currently this works but only requests fired during the first few seconds are being intercepted, after the start signal has been triggered (10sec on average).
If I click the start button again, the same scenario takes place: a listener is getting attached and new requests are being intercepted but only for few seconds.
Now if the service-worker inspect view (console) is opened, requests interceptions are never stopped and all works as expected ...

It looks like the event listener is being teared down once the service-worker is put into inactive state, which doesn't happen when the inspect view is open because it probably keeps the service-worker alive.

Wasn't the bug-fix supposed to address this issue in the first place?
Unfortunately I could not think of any alternative/workaround.
Has anybody faced this scenario lately?

Stphane
  • 3,368
  • 5
  • 32
  • 47

2 Answers2

3

The "lazy" in the fix means the entire context as opposed to an "eager" persistent background script in MV2 or an open extension tab with webRequest listeners. It's quite possible that your scenario wasn't accounted for, so I think you should comment in the original bug report or open a new issue on https://crbug.com. If you look in chrome://serviceworker-internals you should see that SW stops and that's when the listener stops working whereas having a devtools open for SW keeps it running forever.

One workaround is to always register the listener as shown in the other answer. The problem is that it wakes up the SW when the user doesn't need it, which is bad for performance as it may happen hundreds of times a day depending on the frequency of the event you observe.

You can improve it by using a named (global) function for the listener and unregister it after reading the option value from storage at SW startup. This will limit the wasteful wake-ups to just one per browser launch.

Another workaround might be to have two background scripts - one with the listener and one without - and register the appropriate one in the popup UI via navigator.serviceWorker then import the common code. It didn't work in my test though.

wOxxOm
  • 65,848
  • 11
  • 132
  • 136
  • Thank you wOxxOm. I fear I don't really understand how the SW wake-up process and appropriate listener invocation work. Let's say that an event listener has been registered, what happens sequentially once the SW has been put into inactive state and a new request is fired from the active tab? – Stphane Nov 21 '22 at 14:17
  • See [Why must MV3 chrome extensions(using service workers) "register listeners in the first turn of the event loop"?](https://stackoverflow.com/a/74516519) – wOxxOm Nov 21 '22 at 14:49
  • All right, I understand that my implementation prevents the listener from being correctly indexed within the internal API database. – Stphane Nov 21 '22 at 17:10
0

Stphane, some thoughts,

  1. We always register the listener in SW.
  2. Listener can choose to handle the event, or not, based on a variable that is set by the popup.
  3. In order to keep the variable (since it also will be destroyed when SW goes inactive), we need to keep the variable in local storage.

An example:

chrome.webRequest.onBeforeSendHeaders.addListener(async details => {
  let {shouldHandle} = await chrome.storage.local.get('shouldHandle');
  if (shouldHandle) { myHandler(details); }
}, {urls: ['*://*.example.com/*']}, ['requestHeaders']);

Hope this helps.

wOxxOm
  • 65,848
  • 11
  • 132
  • 136
Richard Zhang
  • 350
  • 1
  • 4
  • 1
    This is basically what I have implemented already as it doesn't seems to cost a lot of performance and because the extension is only meant for a narrow user range. – Stphane Dec 15 '22 at 10:01
  • 1
    This is terribly wasteful because it'll always spin up the SW. In case of observing all URLs, this will start the SW every minute, possibly hundreds of times a day. This approach can be made efficient if you use a named (global) function for the listener and unregister it after reading storage at SW startup so it will wastefully wake up just once per browser launch. – wOxxOm Dec 22 '22 at 20:01
  • Here's an example: https://crbug.com/1397879#c11 – wOxxOm Dec 22 '22 at 21:13
  • Note. To be precise, I have implemented what wOxxOm has described. – Stphane Mar 13 '23 at 08:15