service worker not receiving message from content script

1,277 views
Skip to first unread message

joette cordsen

unread,
Nov 10, 2021, 7:50:41 AM11/10/21
to Chromium Extensions
I keep running into issues where a content script sends a message to the service worker ( used to be a background script) and the message is lost because the service worker isn't awake in time to receive the message. I can see the message is sent and it is never received. This is happening in multiple scenarios. The thought of having to completely redesign my system is overwhelming. Is anyone else having this problem? The fact that I can't make the service workers  console log persistent is making it worse. I have resorted to using local storage as a console/debug log - this works well btw. 

TIA

joette cordsen

unread,
Nov 10, 2021, 8:09:59 AM11/10/21
to Chromium Extensions, joette cordsen
ok - so maybe the issue is the time it takes to load the context, set the listener, in the background. The message is received, but never processed because the service worker is busy loading context before the listener is installed? idk. 

Erek Speed

unread,
Nov 10, 2021, 8:38:41 AM11/10/21
to joette cordsen, Chromium Extensions
Just making sure but did you ensure your event listeners are all registered at the top level? 

--
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/bc26fe85-5e84-4267-8424-db899b040290n%40chromium.org.

wOxxOm

unread,
Nov 10, 2021, 9:10:17 AM11/10/21
to Chromium Extensions, jill...@gmail.com
The service worker must always wake "in time" for any API event.

Maybe you're adding a DOM listener that contains sendMessage, then you reload the extension, which "orphanizes" the old content scripts so they no longer belong to the extension and can't connect to the current background script. This is true for both MV2 and MV3. The solution is to manually inform the orphaned DOM listeners that they should unregister, example: https://stackoverflow.com/a/57471345

This may be also a bug, which you can report on https://crbug.com but only if you can provide a reliable method to reproduce the problem and a test extension.

joette cordsen

unread,
Nov 11, 2021, 6:59:14 AM11/11/21
to Chromium Extensions, joette cordsen
The event listeners are at the top level. Most frequently I see this from a  content script that uses the beforeunload event to send back a message to the service worker as a result of some user action to unload/reload the page. I really need to be able to send a message to the service worker when the page is unloaded. The most common example is the user hits submit on the page. A new page will be loaded into the same tab, and I need to be able to keep track that this submit was done (my extension is a timer). My timer starts the clock when the page is loaded and stops the clock when the user submit is done and the service worker saves the time the user spent on the task. with mv2 I was using the onunload event in the content script, this was completely unreliable with mv3, so I switched to beforeunload. I do have have a tab listener in my service worker - it does not get any events when the tab is unloaded, but does get a complete when the new page is loaded. So I think what is happening is in the content script's beforeunload handler I send the message to the service worker and the content script/page is getting unloaded and the connection between the content script and the service worker is getting severed before the message is received by the service worker? Maybe? Its very intermittent. I'm not sure what to do next. I suspect that I'm using the beforeunload event in the content script in a way that is not safe. but I don't know how else to do this. And it worked very well with MV2. I have about 600 users of this private extension on many different platforms, its been around for more than a few years, and while this may have happened with MV2, I never heard any reports of it. Any ideas for another way to get this done would be greatly appreciated. I have to run as an extension - I don't own the webpage the content script runs on and have no access to any of its behind the scenes (server) workings. 

On Wednesday, November 10, 2021 at 5:50:41 AM UTC-7 joette cordsen wrote:

wOxxOm

unread,
Nov 11, 2021, 7:45:13 AM11/11/21
to Chromium Extensions, jill...@gmail.com
A reliable solution might be to keep a connected runtime port in the background script for each content script, no unload/beforeunload would be necessary because when the page is finally navigated away the port will automatically disconnect in the background script thus notifying it of the end-of-life for this page.

Some notes:
  1. Since timers set by a content script can be accidentally/intentionally cleared by the page, the example tracks the time in the background script.
  2. A bug or a poor design decision in MV3 causes runtime ports to disconnect after five minutes so the example reconnects the ports periodically.
  3. Not tested so maybe the added props on `port` won't persist and you'll need to use an additional Map().
// content script:
let port;
reconnect();

function reconnect(port) {
  port?.onDisconnect.removeListener(reconnect);
  port = chrome.runtime.connect({name: 'tracker'});
  port.onDisconnect.addListener(reconnect);
}

function onIdleDetected() {
  port.postMessage({idle: true});
}

function onActivityDetected() {
  port.postMessage({idle: false});
}

// background script:

chrome.runtime.onConnect.addListener(port => {
  if (port.name !== 'tracker') return;
  port._timer = setTimeout(forceReconnect, 270e3, port);
  port._timeSpent = 0;
  port._timeLastActive = 0;
  port.onDisconnect.addListener(onPortDisconnected);
  port.onMessage.addListener(onPortMessage);
});

function onPortMessage(msg, port) {
  const now = performance.now();
  if (msg.idle && port._timeLastActive) {
    port._timeSpent += now - port._timeLastActive;
    port._timeLastActive = 0;
  } else if (!msg.idle && !port._timeLastActive) {
    port._timeLastActive = now;
  }
}

function onPortDisconnected(port) {
  clearTimeout(port._timer);
  onPortMessage({idle: true}, port);
  console.log(port.sender.url, 'spent', (port._timeSpent / 1000).toFixed(1), 's');
}

function forceReconnect(port) {
  port.onDisconnect.removeListener(onPortDisconnected);
  port.onMessage.removeListener(onPortMessage);
  port.disconnect();

wOxxOm

unread,
Nov 11, 2021, 9:21:55 AM11/11/21
to Chromium Extensions, jill...@gmail.com
BTW, you were right in your initial assessment - "service worker isn't awake in time to receive the message" - because when the page goes away in beforeunload event its remaining lifetime is a couple of milliseconds. Sending a small message is fast, but it requires establishing a channel first, which is also asynchronous.

It succeeded with a ManifestV2 persistent background script because it was always ready and could reply within those several milliseconds.

It fails in ManifestV3 if the service worker wasn't already running BEFORE the message was sent because it takes at least 50ms of intensive CPU/memory/disk activity to start the worker - [rant starts] something that ManifestV3 propaganda conveniently doesn't mention when it promotes its unrealistic vision of an ideal extension architecture, which is only viable for app-like extensions invoked by the user explicitly and infrequently.

On Thursday, November 11, 2021 at 2:59:14 PM UTC+3 jill...@gmail.com wrote:
Reply all
Reply to author
Forward
Message has been deleted
0 new messages