Persistent Service Worker in Chrome Extension
P粉323224129
P粉323224129 2023-08-24 18:56:27
0
2
649
<p>I need to define my Service Worker as persistent in a Chrome extension because I'm using the webRequest API to intercept some data being passed in the form for a specific request, but I don't know how to do that. I've tried everything but my Service Worker keeps uninstalling. </p> <p>How do I keep it loading and wait until the request is intercepted? </p>
P粉323224129
P粉323224129

reply all(2)
P粉034571623

Unlike chrome.webRequest API, chrome.webNavigation API works perfectly because chrome.webNavigation API can wake up Service Worker, now you can try to put the chrome.webRequest API API into chrome.webNavigation.

chrome.webNavigation.onBeforeNavigate.addListener(function(){

   chrome.webRequest.onResponseStarted.addListener(function(details){

      //.............
      
      //.............

   },{urls: ["*://domain/*"],types: ["main_frame"]});


},{
    url: [{hostContains:"domain"}]
});
P粉386318086

Table of contents

  • Problem Description

  • Solution:

    • Exploit
    Off-screen API
    nativeMessaging API
    WebSocket API
    chrome Messaging API
    • Dedicated tab

  • Notice

By definition, a Service Worker (SW) cannot be persisted and the browser must forcefully terminate all its activities/requests after a certain time, which in Chrome is 5 minutes. The inactivity timer (i.e. when there is no such activity going on) is even shorter: 30 seconds.

The Chromium Team currently considers this behavior to be good (the team occasionally relaxes certain aspects, e.g. Chrome 114 extends chrome.runtime port after each message), but this only applies if observations are not Extensions to frequent events that run only a few times a day, thus reducing the browser memory footprint between runs (e.g. webRequest/webNavigation events with url > filtering for rarely visited sites). These extensions can be redesigned to maintain state, Example. Unfortunately, such an idyll is unsustainable in many cases.

Known issues

  • Issue 1: Chrome 106 and earlier does not wake software for webRequest events. p>

    Although you could try subscribing to an API like chrome.webNavigation as shown in other answers, it will only help for events that occur after the worker thread has been started.

  • Issue 2: Staff randomly stop waking up due to events.

    The solutionmay be to call chrome.runtime.reload().

  • Issue 3: Chrome 109 and earlier cannot extend the software lifecycle of new chrome API events from background scripts that are already running. This means that your code will not be able to reliably run anything asynchronously when an event occurs within the last few milliseconds of the 30 second inactivity timeout. This means users will think your extension is unreliable.

  • Question 4: Performance will be worse than MV2 if the extension maintains remote connections or state (variables) takes a long time to rebuild, or if you observe frequent events like the following:

    • chrome.tabs.onUpdated/onActivated,
    • chrome.webNavigation If the scope is not limited to rare URLs,
    • chrome.webRequest If the scope is not limited to rare URLs or types,
    • chrome.runtime.onMessage/onConnect Messages for content scripts in all tabs.

    Launching SW for a new event is essentially like opening a new tab. It takes about 50 ms to create the environment, maybe 100 ms (or even 1000 ms, depending on the amount of code) to run the entire SW script, and maybe 1 ms (or 1000 ms, depending on the data) to read the state from storage and rebuild/hydrate. Complexity) . Even with an almost empty script, it takes at least 50 milliseconds, which is quite a lot of overhead to call an event listener, which only takes 1 millisecond.

    SW may be restarted hundreds of times per day because such events are generated in response to user actions with natural gaps, such as clicking a tab and then writing something, during which the software is terminated and restart again for new events, thus consuming CPU, disk, battery, and generally introducing frequent perceptible lags in scaling response.

Exploiting "persistent" service workers via errors

Chrome 110 introduced a bug: calling any asynchronous chrome API will cause the worker thread to run for an additional 30 seconds. This bug has not been fixed yet.

//Background.js

const keepAlive = () => setInterval(chrome.runtime.getPlatformInfo, 20e3);
chrome.runtime.onStartup.addListener(keepAlive);
keepAlive();

"Persistent" service workers with off-screen API

Contributed by Kevin Augusto.

In Chrome 109 and above, you can use the offscreen API to create an off-screen document and send some messages from it every 30 seconds or less to keep the Service Worker running. Currently the document's lifetime is unrestricted (only audio playback is restricted, which we don't use), but this may change in the future.

  • manifest.json

      "permissions": ["offscreen"]
    
  • offscreen.html

    <script src="offscreen.js"></script>
    
  • offscreen.js

    setInterval(async () => {
      (await navigator.serviceWorker.ready).active.postMessage('keepAlive');
    }, 20e3);
    
  • Background.js

    async function createOffscreen() {
      await chrome.offscreen.createDocument({
        url: 'offscreen.html',
        reasons: ['BLOBS'],
        justification: 'keep service worker running',
      }).catch(() => {});
    }
    chrome.runtime.onStartup.addListener(createOffscreen);
    self.onmessage = e => {}; // keepAlive
    createOffscreen();
    

Connection nativeMessaging Host-time "persistent" service worker thread

In Chrome 105 and above, just pass chrome.runtime.connectNative. If the host process terminates due to a crash or user action, the port will be closed and the software will terminate as usual. You can prevent it from calling chrome.runtime.connectNative again by listening to the port's onDisconnect event.

"Persistent" service worker thread while WebSocket is active

Chrome 116 and above: Exchange WebSocket messages every 30 seconds to keep it alive, for example every 25 seconds.

"Persistent" service worker while connectable tab exists

shortcoming:

  • Requires an open web tab
  • Broad host permissions for content scripts (e.g. or *://*/*), which will put most extensions into slow review by web stores In queue.

warn! If you have a connected port, do not use this workaround, use another workaround for the port below.

warn! If you use sendMessage, you can also implement a workaround for sendMessage (below).

  • manifest.json, relevant parts:

      "permissions": ["scripting"],
      "host_permissions": ["<all_urls>"],
      "background": {"service_worker": "bg.js"}
    
    
  • Background service worker bg.js:

    const onUpdate = (tabId, info, tab) => /^https?:/.test(info.url) && findTab([tab]);
    findTab();
    chrome.runtime.onConnect.addListener(port => {
      if (port.name === 'keepAlive') {
        setTimeout(() => port.disconnect(), 250e3);
        port.onDisconnect.addListener(() => findTab());
      }
    });
    async function findTab(tabs) {
      if (chrome.runtime.lastError) { /* tab was closed before setTimeout ran */ }
      for (const {id: tabId} of tabs || await chrome.tabs.query({url: '*://*/*'})) {
        try {
          await chrome.scripting.executeScript({target: {tabId}, func: connect});
          chrome.tabs.onUpdated.removeListener(onUpdate);
          return;
        } catch (e) {}
      }
      chrome.tabs.onUpdated.addListener(onUpdate);
    }
    function connect() {
      chrome.runtime.connect({name: 'keepAlive'})
        .onDisconnect.addListener(connect);
    }
    
  • All other extension pages, such as popups or options:

    ;(function connect() {
      chrome.runtime.connect({name: 'keepAlive'})
        .onDisconnect.addListener(connect);
    })();
    

If you also use sendMessage

In Chrome 99-101, you need to always call sendResponse() in the chrome.runtime.onMessage listener even if a response is not required. This is a bug in MV3. Also, make sure you do this within 5 minutes, otherwise call sendResponse immediately and send a new message via chrome.tabs.sendMessage (to the tab) or chrome.runtime.sendMessage (to the popup) when the work is done.

If you are already using a port such as chrome.runtime.connect

warn! If you also connect more ports to the service worker, you will need to reconnect each port before 5 minutes elapses, for example, within 295 seconds. This was critical in Chrome versions prior to 104, which would kill SW no matter how many extra connection ports there were. In Chrome 104 and above this bug is fixed, but you still need to reconnect them as their 5 minute lifecycle has not changed, so the easiest solution is to reconnect them the same way in all versions of Chrome Connection: for example every 295 seconds.

  • Backend script example:

    chrome.runtime.onConnect.addListener(port => {
      if (port.name !== 'foo') return;
      port.onMessage.addListener(onMessage);
      port.onDisconnect.addListener(deleteTimer);
      port._timer = setTimeout(forceReconnect, 250e3, port);
    });
    function onMessage(msg, port) {
      console.log('received', msg, 'from', port.sender);
    }
    function forceReconnect(port) {
      deleteTimer(port);
      port.disconnect();
    }
    function deleteTimer(port) {
      if (port._timer) {
        clearTimeout(port._timer);
        delete port._timer;
      }
    }
  • Client script example, such as content script:

    let port;
    function connect() {
      port = chrome.runtime.connect({name: 'foo'});
      port.onDisconnect.addListener(connect);
      port.onMessage.addListener(msg => {
        console.log('received', msg, 'from bg');
      });
    }
    connect();
    

"Always", via dedicated tab, when the tab is open

Instead of using software, open a new tab with the extension page inside, so this page will act as a "visible background page", i.e. the only thing the software has to do is open this tab. You can also open it from the action popup.

chrome.tabs.create({url: 'bg.html'})

It will have the same functionality as ManifestV2's persistent background page, but a) it will be visible and b) will not be accessible via chrome.extension.getBackgroundPage (can be replaced by chrome.extension .getViews).

shortcoming:

  • Consume more memory,
  • Waste of space in the tab bar,
  • Distract the user’s attention,
  • When multiple extensions open a tab like this, the disadvantages can snowball and become a real PITA.

You can make it easier for users by adding info/logs/charts/dashboard to the page, and also add a beforeunload listener to prevent the tab from being closed accidentally. p>

Warning about persistence

You still need to save/restore the state (variables) as there is no such thing as a persistent service worker and these workarounds have limitations as mentioned above so the worker can terminate. You can maintain state in storage, Example.

Please note that you should not make your worker threads persistent just to simplify state/variable management. This is only done to restore performance worsened by restarting worker threads, in case your state is very expensive to rebuild, or if you are hooked into the frequent events listed at the beginning of this answer.

Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template