I need to define my Service Worker as persistent in my Chrome extension because I'm using the webRequest API to intercept some data passed in a form for a specific request, but I don't know how I can do that. I've tried everything, but my Service Worker keeps unloading.
How can I keep it loaded and waiting until the request is intercepted?
As stated in Service Workers: an Introduction, a "service worker is a script that your browser runs in the background, separate from a web page, opening the door to features that don't need a web page or user interaction." This is the technology that enables native-like experiences such as push notifications, rich ...
Using web_accessible_resources This prevents websites from fingerprinting a browser by examining the extensions it has installed. Note: In Chrome in Manifest V2, an extension's ID is fixed.
Service workers are specialized JavaScript assets that act as proxies between web browsers and web servers. They aim to improve reliability by providing offline access, as well as boost page performance.
You can look at Service Worker Detector, a Chrome extension that detects if a website registers a Service Worker by reading the navigator. serviceWorker. controller property. It might also work in other browsers supporting Web Extensions, but it looks like it is not yet distributed as such.
Service worker (SW) can't be persistent by definition and the browser must forcibly terminate all of SW connections such as network requests or runtime ports after a certain time, which in Chrome is 5 minutes. The inactivity timer when no such requests or ports are open is even shorter: 30 seconds.
Chromium team currently considers this behavior intentional and good, however this only applies to extensions that observe infrequent events, so they'll run just a few times a day thus reducing browser memory footprint between the runs e.g. webRequest/webNavigation events with urls
filter for a rarely visited site. These extensions can be reworked to maintain the state, example.
Such an idyll is unsustainable in many cases.
Problem 1: Chrome 106 and older doesn't wake up SW for webRequest events.
Although you can try to subscribe to an API like chrome.webNavigation
as shown in the other answers, but it helps only with events that occur after the worker starts.
Problem 2: the worker is randomly broken after an update.
The tentative workaround may be to add self.oninstall = () => skipWaiting();
Problem 3: SW inactivity timer isn't prolonged for a new chrome
API event in an already running background script. It means that when the event occurred in the last milliseconds of the 30-second inactivity timeout your code won't be able to run anything asynchronous reliably. It means that your extension will be perceived as unreliable by the user.
Problem 4: worse performance than MV2 in case the extension maintains a socket connection or the state (variables) takes a long time to rebuild or you observe frequent events like these:
Starting SW for a new event is essentially like opening a new tab. Creating the environment takes ~50ms, running the entire SW script may take 100ms (or even 1000ms depending on the amount of code), reading the state from storage and rebuilding/hydrating it may take 1ms (or 1000ms depending on the complexity of data). Even with an almost empty script it'd be at least 50ms, which is quite a huge overhead to call the event listener, which takes only 1ms.
SW may restart hundreds of times a day, because such events are generated in response to user actions that have natural gaps in them e.g. clicked a tab then wrote something, during which the SW is terminated and restarted again for a new event thus wearing down CPU, disk, battery, often introducing a frequent perceivable lag of the extension's reaction.
nativeMessaging
host is connectedIn Chrome 105 and newer the service worker will run as long as it's connected to a nativeMessaging host via chrome.runtime.connectNative. If the host process is terminated due to a crash or user action, the port will be closed, and the SW will terminate as usual. You can guard against it by listening to port's onDisconnect event and call chrome.runtime.connectNative again.
Downsides:
<all_urls>
or *://*/*
) for content scripts which puts most extensions into the slow review queue in the web store.Warning! If you already connect ports, don't use this workaround, use another one for ports below.
Warning! Also implement the workaround for sendMessage (below) if you use sendMessage.
manifest.json, the relevant part:
"permissions": ["scripting"],
"host_permissions": ["<all_urls>"],
"background": {"service_worker": "bg.js"}
background service worker bg.js:
let lifeline;
keepAlive();
chrome.runtime.onConnect.addListener(port => {
if (port.name === 'keepAlive') {
lifeline = port;
setTimeout(keepAliveForced, 295e3); // 5 minutes minus 5 seconds
port.onDisconnect.addListener(keepAliveForced);
}
});
function keepAliveForced() {
lifeline?.disconnect();
lifeline = null;
keepAlive();
}
async function keepAlive() {
if (lifeline) return;
for (const tab of await chrome.tabs.query({ url: '*://*/*' })) {
try {
await chrome.scripting.executeScript({
target: { tabId: tab.id },
function: () => chrome.runtime.connect({ name: 'keepAlive' }),
// `function` will become `func` in Chrome 93+
});
chrome.tabs.onUpdated.removeListener(retryOnTabUpdate);
return;
} catch (e) {}
}
chrome.tabs.onUpdated.addListener(retryOnTabUpdate);
}
async function retryOnTabUpdate(tabId, info, tab) {
if (info.url && /^(file|https?):/.test(info.url)) {
keepAlive();
}
}
In Chrome 99-101 you need to always call sendResponse() in your chrome.runtime.onMessage listener even if you don't need the response. This is a bug in MV3. Also, make sure you do it in less than 5 minutes time, otherwise call sendResponse immediately and send a new message back via chrome.tabs.sendMessage (to the tab) or chrome.runtime.sendMessage (to the popup) after the work is done.
Warning! If you also connect more ports to the service worker you need to reconnect each one before its 5 minutes elapse e.g. in 295 seconds. This is crucial in Chrome versions before 104, which killed SW regardless of additional connected ports. In Chrome 104 and newer this bug is fixed but you'll still need to reconnect them, because their 5-minute lifetime hasn't changed, so the easiest solution is to reconnect the same way in all versions of Chrome: e.g. every 295 seconds.
background 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 e.g. a 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();
Open a new tab with an extension page inside e.g. chrome.tabs.create({url: 'bg.html'})
.
It'll have the same abilities as the persistent background page of ManifestV2 but a) it's visible and b) not accessible via chrome.extension.getBackgroundPage
(which can be replaced with chrome.extension.getViews).
Downsides:
You can make it a little more bearable for your users by adding info/logs/charts/dashboard to the page and also add a beforeunload
listener to prevent the tab from being accidentally closed.
You still need to save/restore the state (variables) because there's no such thing as a persistent service worker and those workarounds have limits as described above, so the worker can terminate. You can maintain the state in a storage, example.
Note that you shouldn't make your worker persistent just to simplify state/variable management. Do it only to restore the performance worsened by restarting the worker in case your state is very expensive to rebuild or if you hook into frequent events listed in the beginning of this answer.
Let's hope Chromium will provide an API to control this behavior without the need to resort to such dirty hacks and pathetic workarounds. Meanwhile describe your use case in crbug.com/1152255 if it isn't already described there to help Chromium team become aware of the established fact that many extensions may need a persistent background script for an arbitrary duration of time and that at least one such extension may be installed by the majority of extension users.
If i understand correct you can wake up service worker (background.js) by alerts. Look at below example:
"permissions": [
"alarms"
],
chrome.alarms.create({ periodInMinutes: 4.9 })
chrome.alarms.onAlarm.addListener(() => {
console.log('log for debug')
});
Unfortunately this is not my problem and may be you have different problem too. When i refresh dev extension or stop and run prod extension some time service worker die at all. When i close and open browser worker doesn't run and any listeners inside worker doesn't run it too. It tried register worker manually. Fore example:
// override.html
<!DOCTYPE html>
<html lang="en">
<head>...<head>
<body>
...
<script defer src="override.js"></script>
<body>
<html>
// override.js - this code is running in new tab page
navigator.serviceWorker.getRegistrations().then((res) => {
for (let worker of res) {
console.log(worker)
if (worker.active.scriptURL.includes('background.js')) {
return
}
}
navigator.serviceWorker
.register(chrome.runtime.getURL('background.js'))
.then((registration) => {
console.log('Service worker success:', registration)
}).catch((error) => {
console.log('Error service:', error)
})
})
This solution partially helped me but it does not matter because i have to register worker on different tabs. May be somebody know decision. I will pleasure.
unlike the chrome.webRequest API the chrome.webNavigation API works perfectly because the chrome.webNavigation API can wake up the service worker, for now you can try putting the chrome.webRequest API api inside the chrome.webNavigation.
chrome.webNavigation.onBeforeNavigate.addListener(function(){
chrome.webRequest.onResponseStarted.addListener(function(details){
//.............
//.............
},{urls: ["*://domain/*"],types: ["main_frame"]});
},{
url: [{hostContains:"domain"}]
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With