Problem with the service worker or background [duplicate] - google-chrome-extension

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?

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. Unfortunately, such an idyll is unsustainable in many cases.
Known problems
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 randomly stops waking up for events.
The workaround may be to call chrome.runtime.reload().
Problem 3: Chrome 109 and older doesn't prolong SW lifetime 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:
chrome.tabs.onUpdated/onActivated,
chrome.webNavigation if not scoped to a rare url,
chrome.webRequest if not scoped to a rare url or type,
chrome.runtime.onMessage/onConnect for messages from content script in all tabs.
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.
"Persistent" service worker with offscreen API
Courtesy of Keven Augusto.
In Chrome 109 and newer you can use offscreen API to create an offscreen document and send some message from it every 30 second or less, to keep service worker running. Currently this document's lifetime is not limited (only audio playback is limited, which we don't use), but it's likely to change in the future.
manifest.json
"permissions": ["offscreen"]
offscreen.html
<script src="offscreen.js"></script>
offscreen.js
// send a message every 20 sec to service worker
setInterval(() => {
chrome.runtime.sendMessage({ keepAlive: true });
}, 20000);
background.js
// create the offscreen document if it doesn't already exist
async function createOffscreen() {
if (await chrome.offscreen.hasDocument?.()) return;
await chrome.offscreen.createDocument({
url: 'offscreen.html',
reasons: ['BLOBS'],
justification: 'keep service worker running',
});
}
chrome.runtime.onStartup.addListener(() => {
createOffscreen();
});
// a message from an offscreen document every 20 second resets the inactivity timer
chrome.runtime.onMessage.addListener(msg => {
if (msg.keepAlive) console.log('keepAlive');
});
"Persistent" service worker while nativeMessaging host is connected
In 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.
"Persistent" service worker while a connectable tab is present
Downsides:
The need for an open web page tab
Broad host permissions (like <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:
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 your other extension pages like the popup 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 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.
If you already use ports e.g. chrome.runtime.connect
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();
"Forever", via a dedicated tab, while the tab is open
Instead of using the SW, open a new tab with an extension page inside, so this page will act as a "visible background page" i.e. the only thing the SW would do is open this tab. You can also open it from the action popup.
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:
consumes more memory,
wastes space in the tab strip,
distracts the user,
when multiple extensions open such a tab, the downsides snowball and become a real PITA.
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.
Caution regarding persistence
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.
Future of ManifestV3
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.

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 i understand correct you can wake up service worker (background.js) by alerts. Look at below example:
manifest v3
"permissions": [
"alarms"
],
service worker background.js:
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.

I found a different solution to keeping the extension alive. It improves on wOxxOm's answer by using a secondary extension to open the connection port to our main extension. Then both extensions try to communicate with each other in the event that any disconnects, hence keeping both alive.
The reason this was needed was that according to another team in my company, wOxxOm's answer turned out to be unreliable. Reportedly, their SW would eventually fail in an nondeterministic manner.
Then again, my solution works for my company as we are deploying enterprise security software, and we will be force installing the extensions. Having the user install 2 extensions may still be undesirable in other use-cases.

As Clairzil Bawon samdi's answer that chrome.webNavigation could wake up the service worker in MV3, here are workaround in my case:
// manifest.json
...
"background": {
"service_worker": "background.js"
},
"host_permissions": ["https://example.com/api/*"],
"permissions": ["webRequest", "webNavigation"]
...
In my case it listens onHistoryStateUpdated event to wake up the service worker:
// background.js
chrome.webNavigation.onHistoryStateUpdated.addListener((details) => {
console.log('wake me up');
});
chrome.webRequest.onSendHeaders.addListener(
(details) => {
// code here
},
{
urls: ['https://example.com/api/*'],
types: ['xmlhttprequest'],
},
['requestHeaders']
);

IMHO (and direct experience) a well structured SW will work forever.
Obviously there are some particular cases, like uninterruptible connections, which may suffer a lot once SW falls asleep, but still if the code is not prepared to handle the specific behaviour.
It seems like a battle against windmills, punctually after 30 seconds SW stops doing anything, falls asleep, several events are not honored anymore and the problems start... if our SW has nothing else pressing to think about.
From "The art of War" (Sun Tzu): if you can't fight it, make friends with it.
so... ok, lets try to give something consistent to think about from time to time to our SW and put a "patch" (because this IS A PATCH!) to this issue.
Obviously I don't assure this solution will work for all of you, but it worked for me in the past, before I decided to review the whole logic and code of my SW.
So I decided to share it for your own tests.
This doesn't require any special permission in manifest V3.
Remember to call the StayAlive() function below at SW start.
To perform reliable tests remember to not open any DevTools page. Use chrome://serviceworker-internals instead and find the log (Scope) of your extension ID.
EDIT:
Since the logic of the code may not be clear to some, I will try to explain it to dispel doubts:
Any extension's SW can attempt to make a connection and send messages through a named port and, if something fails, generate an error.
The code below connects to a named port and tries to send a message through it to a nonexistent listener (so it will generate errors).
While doing this, SW is active and running (it has something to do, that is, it has to send a message through a port).
Because noone is listening, it generates a (catched and logged) error (in onDisconnect) and terminates (normal behaviour happening in whatever code).
But after 25 secs it does the same iter from start, thus keeping SW active forever.
It works fine. It is a simple trick to keep the service worker active.
// Forcing service worker to stay alive by sending a "ping" to a port where noone is listening
// Essentially it prevents SW to fall asleep after the first 30 secs of work.
const INTERNAL_STAYALIVE_PORT = "Whatever_Port_Name_You_Want"
var alivePort = null;
...
StayAlive();
...
async function StayAlive() {
var lastCall = Date.now();
var wakeup = setInterval( () => {
const now = Date.now();
const age = now - lastCall;
console.log(`(DEBUG StayAlive) ----------------------- time elapsed: ${age}`)
if (alivePort == null) {
alivePort = chrome.runtime.connect({name:INTERNAL_STAYALIVE_PORT})
alivePort.onDisconnect.addListener( (p) => {
if (chrome.runtime.lastError){
console.log(`(DEBUG StayAlive) Disconnected due to an error: ${chrome.runtime.lastError.message}`);
} else {
console.log(`(DEBUG StayAlive): port disconnected`);
}
alivePort = null;
});
}
if (alivePort) {
alivePort.postMessage({content: "ping"});
if (chrome.runtime.lastError) {
console.log(`(DEBUG StayAlive): postMessage error: ${chrome.runtime.lastError.message}`)
} else {
console.log(`(DEBUG StayAlive): "ping" sent through ${alivePort.name} port`)
}
}
//lastCall = Date.now();
}, 25000);
}
Hoping this will help someone.
Anyway, I still recommend, where possible, to review the logic and the code of your SW, because, as I mentioned at the beginning of this post, any well structured SW will work perfectly in MV3 even without tricks like this one.
EDIT (jan 17, 2023)
when you think you've hit bottom, watch out for the trapdoor that might suddenly open under your feet.
Sun Tzu
This revision of the StayAlive() function above still keeps the service worker active, but avoids calling the function every 25 seconds, so as not to burden it with unnecessary work.
In practice, it appears that by running the Highlander() function below at predefined intervals, the service worker will still live forever.
How it works
The first call of Highlander() is executed before the expiration of the fateful 30 seconds (here it is executed after 4 seconds from the start of the service worker).
Subsequent calls are performed before the expiration of the fateful 5 minutes (here they are executed every 270 seconds).
The service worker, in this way, will never go to sleep and will always respond to all events.
It thus appears that, per Chromium design, after the first Highlander() call within the first 30 seconds, the internal logic that manages the life of the (MV3) service worker extends the period of full activity until the next 5 minutes.
This is really really hilarious...
anyway... this is the ServiceWorker.js I used for my tests.
// -----------------
// SERVICEWORKER.JS
// -----------------
const INTERNAL_STAYALIVE_PORT = "CT_Internal_port_alive"
var alivePort = null;
const SECONDS = 1000;
var lastCall = Date.now();
var isFirstStart = true;
var timer = 4*SECONDS;
// -------------------------------------------------------
var wakeup = setInterval(Highlander, timer);
// -------------------------------------------------------
async function Highlander() {
const now = Date.now();
const age = now - lastCall;
console.log(`(DEBUG Highlander) ------------- time elapsed from first start: ${convertNoDate(age)}`)
if (alivePort == null) {
alivePort = chrome.runtime.connect({name:INTERNAL_STAYALIVE_PORT})
alivePort.onDisconnect.addListener( (p) => {
if (chrome.runtime.lastError){
console.log(`(DEBUG Highlander) Expected disconnect (on error). SW should be still running.`);
} else {
console.log(`(DEBUG Highlander): port disconnected`);
}
alivePort = null;
});
}
if (alivePort) {
alivePort.postMessage({content: "ping"});
if (chrome.runtime.lastError) {
console.log(`(DEBUG Highlander): postMessage error: ${chrome.runtime.lastError.message}`)
} else {
console.log(`(DEBUG Highlander): "ping" sent through ${alivePort.name} port`)
}
}
//lastCall = Date.now();
if (isFirstStart) {
isFirstStart = false;
clearInterval(wakeup);
timer = 270*SECONDS;
wakeup = setInterval(Highlander, timer);
}
}
function convertNoDate(long) {
var dt = new Date(long).toISOString()
return dt.slice(-13, -5) // HH:MM:SS only
}
EDIT (jan 20, 2023):
On Github, I created a repository for a practical example of how to properly use the Highlander function in a real world extension. For the implementation of this repo, I also took into account wOxxOm's comments to my post (many thanks to him).
Still on Github, I created another repository to demonstrate in another real world extension how a service worker can immediately start by itself (put itself in RUNNING status), without the aid of external content scripts, and how it can live on forever using the usual Highlander function.
This repository includes a local WebSocket Echo Test server used by the extension in its client communication sample and useful to externally debug the extension when the extension's host browser has been closed. That's right, because, depending on the type of configuration applied, when the host browser is closed Highlander-DNA can either shut down with the browser or continue to live forever, with all functionality connected and managed (e.g. the included WebSocket client/server communications test sample).
EDIT (jan 22, 2023)
I tested memory and CPU consumption while a Service Worker is always in RUNNING state due to the use of Highlander. The consumption to keep it running all the time is practically ZERO. I really don't understand why the Chromium team is persisting in wanting to unnecessarily complicate everyone's life.

WebSocket callbacks registered from within the chrome.runtime listener registrations of my extensions's service worker would not get invoked, which sounds like almost the same problem.
I approached this problem by making sure that my service worker never ends, by adding the following code to it:
function keepServiceRunning() {
setTimeout(keepServiceRunning, 2000);
}
keepServiceRunning()
After this, my callbacks now get invoked as expected.

Related

Chrome Extension with persistent background [duplicate]

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?
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. Unfortunately, such an idyll is unsustainable in many cases.
Known problems
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 randomly stops waking up for events.
The workaround may be to call chrome.runtime.reload().
Problem 3: Chrome 109 and older doesn't prolong SW lifetime 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:
chrome.tabs.onUpdated/onActivated,
chrome.webNavigation if not scoped to a rare url,
chrome.webRequest if not scoped to a rare url or type,
chrome.runtime.onMessage/onConnect for messages from content script in all tabs.
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.
"Persistent" service worker with offscreen API
Courtesy of Keven Augusto.
In Chrome 109 and newer you can use offscreen API to create an offscreen document and send some message from it every 30 second or less, to keep service worker running. Currently this document's lifetime is not limited (only audio playback is limited, which we don't use), but it's likely to change in the future.
manifest.json
"permissions": ["offscreen"]
offscreen.html
<script src="offscreen.js"></script>
offscreen.js
// send a message every 20 sec to service worker
setInterval(() => {
chrome.runtime.sendMessage({ keepAlive: true });
}, 20000);
background.js
// create the offscreen document if it doesn't already exist
async function createOffscreen() {
if (await chrome.offscreen.hasDocument?.()) return;
await chrome.offscreen.createDocument({
url: 'offscreen.html',
reasons: ['BLOBS'],
justification: 'keep service worker running',
});
}
chrome.runtime.onStartup.addListener(() => {
createOffscreen();
});
// a message from an offscreen document every 20 second resets the inactivity timer
chrome.runtime.onMessage.addListener(msg => {
if (msg.keepAlive) console.log('keepAlive');
});
"Persistent" service worker while nativeMessaging host is connected
In 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.
"Persistent" service worker while a connectable tab is present
Downsides:
The need for an open web page tab
Broad host permissions (like <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:
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 your other extension pages like the popup 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 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.
If you already use ports e.g. chrome.runtime.connect
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();
"Forever", via a dedicated tab, while the tab is open
Instead of using the SW, open a new tab with an extension page inside, so this page will act as a "visible background page" i.e. the only thing the SW would do is open this tab. You can also open it from the action popup.
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:
consumes more memory,
wastes space in the tab strip,
distracts the user,
when multiple extensions open such a tab, the downsides snowball and become a real PITA.
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.
Caution regarding persistence
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.
Future of ManifestV3
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.
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 i understand correct you can wake up service worker (background.js) by alerts. Look at below example:
manifest v3
"permissions": [
"alarms"
],
service worker background.js:
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.
I found a different solution to keeping the extension alive. It improves on wOxxOm's answer by using a secondary extension to open the connection port to our main extension. Then both extensions try to communicate with each other in the event that any disconnects, hence keeping both alive.
The reason this was needed was that according to another team in my company, wOxxOm's answer turned out to be unreliable. Reportedly, their SW would eventually fail in an nondeterministic manner.
Then again, my solution works for my company as we are deploying enterprise security software, and we will be force installing the extensions. Having the user install 2 extensions may still be undesirable in other use-cases.
As Clairzil Bawon samdi's answer that chrome.webNavigation could wake up the service worker in MV3, here are workaround in my case:
// manifest.json
...
"background": {
"service_worker": "background.js"
},
"host_permissions": ["https://example.com/api/*"],
"permissions": ["webRequest", "webNavigation"]
...
In my case it listens onHistoryStateUpdated event to wake up the service worker:
// background.js
chrome.webNavigation.onHistoryStateUpdated.addListener((details) => {
console.log('wake me up');
});
chrome.webRequest.onSendHeaders.addListener(
(details) => {
// code here
},
{
urls: ['https://example.com/api/*'],
types: ['xmlhttprequest'],
},
['requestHeaders']
);
IMHO (and direct experience) a well structured SW will work forever.
Obviously there are some particular cases, like uninterruptible connections, which may suffer a lot once SW falls asleep, but still if the code is not prepared to handle the specific behaviour.
It seems like a battle against windmills, punctually after 30 seconds SW stops doing anything, falls asleep, several events are not honored anymore and the problems start... if our SW has nothing else pressing to think about.
From "The art of War" (Sun Tzu): if you can't fight it, make friends with it.
so... ok, lets try to give something consistent to think about from time to time to our SW and put a "patch" (because this IS A PATCH!) to this issue.
Obviously I don't assure this solution will work for all of you, but it worked for me in the past, before I decided to review the whole logic and code of my SW.
So I decided to share it for your own tests.
This doesn't require any special permission in manifest V3.
Remember to call the StayAlive() function below at SW start.
To perform reliable tests remember to not open any DevTools page. Use chrome://serviceworker-internals instead and find the log (Scope) of your extension ID.
EDIT:
Since the logic of the code may not be clear to some, I will try to explain it to dispel doubts:
Any extension's SW can attempt to make a connection and send messages through a named port and, if something fails, generate an error.
The code below connects to a named port and tries to send a message through it to a nonexistent listener (so it will generate errors).
While doing this, SW is active and running (it has something to do, that is, it has to send a message through a port).
Because noone is listening, it generates a (catched and logged) error (in onDisconnect) and terminates (normal behaviour happening in whatever code).
But after 25 secs it does the same iter from start, thus keeping SW active forever.
It works fine. It is a simple trick to keep the service worker active.
// Forcing service worker to stay alive by sending a "ping" to a port where noone is listening
// Essentially it prevents SW to fall asleep after the first 30 secs of work.
const INTERNAL_STAYALIVE_PORT = "Whatever_Port_Name_You_Want"
var alivePort = null;
...
StayAlive();
...
async function StayAlive() {
var lastCall = Date.now();
var wakeup = setInterval( () => {
const now = Date.now();
const age = now - lastCall;
console.log(`(DEBUG StayAlive) ----------------------- time elapsed: ${age}`)
if (alivePort == null) {
alivePort = chrome.runtime.connect({name:INTERNAL_STAYALIVE_PORT})
alivePort.onDisconnect.addListener( (p) => {
if (chrome.runtime.lastError){
console.log(`(DEBUG StayAlive) Disconnected due to an error: ${chrome.runtime.lastError.message}`);
} else {
console.log(`(DEBUG StayAlive): port disconnected`);
}
alivePort = null;
});
}
if (alivePort) {
alivePort.postMessage({content: "ping"});
if (chrome.runtime.lastError) {
console.log(`(DEBUG StayAlive): postMessage error: ${chrome.runtime.lastError.message}`)
} else {
console.log(`(DEBUG StayAlive): "ping" sent through ${alivePort.name} port`)
}
}
//lastCall = Date.now();
}, 25000);
}
Hoping this will help someone.
Anyway, I still recommend, where possible, to review the logic and the code of your SW, because, as I mentioned at the beginning of this post, any well structured SW will work perfectly in MV3 even without tricks like this one.
EDIT (jan 17, 2023)
when you think you've hit bottom, watch out for the trapdoor that might suddenly open under your feet.
Sun Tzu
This revision of the StayAlive() function above still keeps the service worker active, but avoids calling the function every 25 seconds, so as not to burden it with unnecessary work.
In practice, it appears that by running the Highlander() function below at predefined intervals, the service worker will still live forever.
How it works
The first call of Highlander() is executed before the expiration of the fateful 30 seconds (here it is executed after 4 seconds from the start of the service worker).
Subsequent calls are performed before the expiration of the fateful 5 minutes (here they are executed every 270 seconds).
The service worker, in this way, will never go to sleep and will always respond to all events.
It thus appears that, per Chromium design, after the first Highlander() call within the first 30 seconds, the internal logic that manages the life of the (MV3) service worker extends the period of full activity until the next 5 minutes.
This is really really hilarious...
anyway... this is the ServiceWorker.js I used for my tests.
// -----------------
// SERVICEWORKER.JS
// -----------------
const INTERNAL_STAYALIVE_PORT = "CT_Internal_port_alive"
var alivePort = null;
const SECONDS = 1000;
var lastCall = Date.now();
var isFirstStart = true;
var timer = 4*SECONDS;
// -------------------------------------------------------
var wakeup = setInterval(Highlander, timer);
// -------------------------------------------------------
async function Highlander() {
const now = Date.now();
const age = now - lastCall;
console.log(`(DEBUG Highlander) ------------- time elapsed from first start: ${convertNoDate(age)}`)
if (alivePort == null) {
alivePort = chrome.runtime.connect({name:INTERNAL_STAYALIVE_PORT})
alivePort.onDisconnect.addListener( (p) => {
if (chrome.runtime.lastError){
console.log(`(DEBUG Highlander) Expected disconnect (on error). SW should be still running.`);
} else {
console.log(`(DEBUG Highlander): port disconnected`);
}
alivePort = null;
});
}
if (alivePort) {
alivePort.postMessage({content: "ping"});
if (chrome.runtime.lastError) {
console.log(`(DEBUG Highlander): postMessage error: ${chrome.runtime.lastError.message}`)
} else {
console.log(`(DEBUG Highlander): "ping" sent through ${alivePort.name} port`)
}
}
//lastCall = Date.now();
if (isFirstStart) {
isFirstStart = false;
clearInterval(wakeup);
timer = 270*SECONDS;
wakeup = setInterval(Highlander, timer);
}
}
function convertNoDate(long) {
var dt = new Date(long).toISOString()
return dt.slice(-13, -5) // HH:MM:SS only
}
EDIT (jan 20, 2023):
On Github, I created a repository for a practical example of how to properly use the Highlander function in a real world extension. For the implementation of this repo, I also took into account wOxxOm's comments to my post (many thanks to him).
Still on Github, I created another repository to demonstrate in another real world extension how a service worker can immediately start by itself (put itself in RUNNING status), without the aid of external content scripts, and how it can live on forever using the usual Highlander function.
This repository includes a local WebSocket Echo Test server used by the extension in its client communication sample and useful to externally debug the extension when the extension's host browser has been closed. That's right, because, depending on the type of configuration applied, when the host browser is closed Highlander-DNA can either shut down with the browser or continue to live forever, with all functionality connected and managed (e.g. the included WebSocket client/server communications test sample).
EDIT (jan 22, 2023)
I tested memory and CPU consumption while a Service Worker is always in RUNNING state due to the use of Highlander. The consumption to keep it running all the time is practically ZERO. I really don't understand why the Chromium team is persisting in wanting to unnecessarily complicate everyone's life.
WebSocket callbacks registered from within the chrome.runtime listener registrations of my extensions's service worker would not get invoked, which sounds like almost the same problem.
I approached this problem by making sure that my service worker never ends, by adding the following code to it:
function keepServiceRunning() {
setTimeout(keepServiceRunning, 2000);
}
keepServiceRunning()
After this, my callbacks now get invoked as expected.

How to initialize Chrome extension `storage.local` under Manifest V3 service workers?

I have a Chrome extension where I want to be able to initialize settings if not already set (e.g., upon installation).
I want to do this in a way that the data can be accessed from content scripts and options pages under an assumption that the data has been initialized. Under Manifest V2, I used a background page and initialized the data synchronously with localStorage, which is no longer available under Manifest V3.
A candidate approach would be to run the following code from a service worker:
chrome.storage.local.get(['settings'], function(storage) {
if (!storage.settings) {
chrome.storage.local.set({settings: defaultSettings()});
}
});
However, that seems not guaranteed to work, similar to the example from the migration documentation, since the service worker could be terminated prior to the completion of the asynchronous handling. Additionally, even if the service worker is not terminated, it seems to not be guaranteed that the data would be initialized by time the content script executes.
I would like to initialize the data in a way that it would be guaranteed to be set and available by time a content script executes. A workaround could be to check if the data has been properly initialized in the content script, and use a fallback default value otherwise. Is there some alternative approach that could avoid this extra handling?
since the service worker could be terminated prior to the completion of the asynchronous handling
SW doesn't terminate at any time, there are certain rules, the simplest rule of thumb is that it lives for 30 seconds, the time is auto-prolonged by 30 seconds whenever a subscribed chrome API event is triggered, and by 5 minutes when you have an open chrome.runtime messaging channel/port (currently such ports are auto-closed after 5 minutes in MV3).
So, assuming your example code runs right away, not after a timeout of 29.999 seconds, SW won't terminate during the API call as the storage API takes just a few milliseconds to complete, not 30 seconds. The documentation on ManifestV3 probably tried too hard to sell the non-existent benefits of switching extensions to service workers, so fact-checking was secondary when the article was written.
even if the service worker is not terminated, it seems to not be guaranteed that the data would be initialized by time the content script executes.
Yes.
Some solutions:
Include the defaults in your content scripts and other places. It's the simplest solution that also works in case the user clears the storage via devtools (Storage Area Explorer extension or a console command).
Use chrome.scripting.registerContentScripts (instead of declaring content_scripts in manifest.json) after initializing the storage inside chrome.runtime.onInstalled.
Use messaging in content/options scripts instead of chrome.storage so that the background script is the sole source of truth. When the service worker is already running, messaging will be actually faster as chrome.storage is very slow in Chrome, both local and sync variants, so to make caching truly effective you can use chrome.runtime ports to prolong the lifetime of the service worker to 5 minutes or longer as shown here.
background script:
let settings;
let busy = chrome.storage.local.get('settings').then(r => {
busy = null;
settings = r.settings;
if (!settings) {
settings = defaultSettings();
chrome.storage.local.set({settings});
}
return settings;
});
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg === 'getSettings') {
if (busy) {
busy.then(sendResponse);
return true;
} else {
sendResponse(settings)
}
}
});
content/option script:
chrome.runtime.sendMessage('getSettings', settings => {
// use settings inside this callback
});

chrome extension Manifest v3 background.service_worker .js file lifecycle [duplicate]

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?
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. Unfortunately, such an idyll is unsustainable in many cases.
Known problems
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 randomly stops waking up for events.
The workaround may be to call chrome.runtime.reload().
Problem 3: Chrome 109 and older doesn't prolong SW lifetime 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:
chrome.tabs.onUpdated/onActivated,
chrome.webNavigation if not scoped to a rare url,
chrome.webRequest if not scoped to a rare url or type,
chrome.runtime.onMessage/onConnect for messages from content script in all tabs.
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.
"Persistent" service worker with offscreen API
Courtesy of Keven Augusto.
In Chrome 109 and newer you can use offscreen API to create an offscreen document and send some message from it every 30 second or less, to keep service worker running. Currently this document's lifetime is not limited (only audio playback is limited, which we don't use), but it's likely to change in the future.
manifest.json
"permissions": ["offscreen"]
offscreen.html
<script src="offscreen.js"></script>
offscreen.js
// send a message every 20 sec to service worker
setInterval(() => {
chrome.runtime.sendMessage({ keepAlive: true });
}, 20000);
background.js
// create the offscreen document if it doesn't already exist
async function createOffscreen() {
if (await chrome.offscreen.hasDocument?.()) return;
await chrome.offscreen.createDocument({
url: 'offscreen.html',
reasons: ['BLOBS'],
justification: 'keep service worker running',
});
}
chrome.runtime.onStartup.addListener(() => {
createOffscreen();
});
// a message from an offscreen document every 20 second resets the inactivity timer
chrome.runtime.onMessage.addListener(msg => {
if (msg.keepAlive) console.log('keepAlive');
});
"Persistent" service worker while nativeMessaging host is connected
In 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.
"Persistent" service worker while a connectable tab is present
Downsides:
The need for an open web page tab
Broad host permissions (like <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:
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 your other extension pages like the popup 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 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.
If you already use ports e.g. chrome.runtime.connect
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();
"Forever", via a dedicated tab, while the tab is open
Instead of using the SW, open a new tab with an extension page inside, so this page will act as a "visible background page" i.e. the only thing the SW would do is open this tab. You can also open it from the action popup.
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:
consumes more memory,
wastes space in the tab strip,
distracts the user,
when multiple extensions open such a tab, the downsides snowball and become a real PITA.
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.
Caution regarding persistence
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.
Future of ManifestV3
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.
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 i understand correct you can wake up service worker (background.js) by alerts. Look at below example:
manifest v3
"permissions": [
"alarms"
],
service worker background.js:
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.
I found a different solution to keeping the extension alive. It improves on wOxxOm's answer by using a secondary extension to open the connection port to our main extension. Then both extensions try to communicate with each other in the event that any disconnects, hence keeping both alive.
The reason this was needed was that according to another team in my company, wOxxOm's answer turned out to be unreliable. Reportedly, their SW would eventually fail in an nondeterministic manner.
Then again, my solution works for my company as we are deploying enterprise security software, and we will be force installing the extensions. Having the user install 2 extensions may still be undesirable in other use-cases.
As Clairzil Bawon samdi's answer that chrome.webNavigation could wake up the service worker in MV3, here are workaround in my case:
// manifest.json
...
"background": {
"service_worker": "background.js"
},
"host_permissions": ["https://example.com/api/*"],
"permissions": ["webRequest", "webNavigation"]
...
In my case it listens onHistoryStateUpdated event to wake up the service worker:
// background.js
chrome.webNavigation.onHistoryStateUpdated.addListener((details) => {
console.log('wake me up');
});
chrome.webRequest.onSendHeaders.addListener(
(details) => {
// code here
},
{
urls: ['https://example.com/api/*'],
types: ['xmlhttprequest'],
},
['requestHeaders']
);
IMHO (and direct experience) a well structured SW will work forever.
Obviously there are some particular cases, like uninterruptible connections, which may suffer a lot once SW falls asleep, but still if the code is not prepared to handle the specific behaviour.
It seems like a battle against windmills, punctually after 30 seconds SW stops doing anything, falls asleep, several events are not honored anymore and the problems start... if our SW has nothing else pressing to think about.
From "The art of War" (Sun Tzu): if you can't fight it, make friends with it.
so... ok, lets try to give something consistent to think about from time to time to our SW and put a "patch" (because this IS A PATCH!) to this issue.
Obviously I don't assure this solution will work for all of you, but it worked for me in the past, before I decided to review the whole logic and code of my SW.
So I decided to share it for your own tests.
This doesn't require any special permission in manifest V3.
Remember to call the StayAlive() function below at SW start.
To perform reliable tests remember to not open any DevTools page. Use chrome://serviceworker-internals instead and find the log (Scope) of your extension ID.
EDIT:
Since the logic of the code may not be clear to some, I will try to explain it to dispel doubts:
Any extension's SW can attempt to make a connection and send messages through a named port and, if something fails, generate an error.
The code below connects to a named port and tries to send a message through it to a nonexistent listener (so it will generate errors).
While doing this, SW is active and running (it has something to do, that is, it has to send a message through a port).
Because noone is listening, it generates a (catched and logged) error (in onDisconnect) and terminates (normal behaviour happening in whatever code).
But after 25 secs it does the same iter from start, thus keeping SW active forever.
It works fine. It is a simple trick to keep the service worker active.
// Forcing service worker to stay alive by sending a "ping" to a port where noone is listening
// Essentially it prevents SW to fall asleep after the first 30 secs of work.
const INTERNAL_STAYALIVE_PORT = "Whatever_Port_Name_You_Want"
var alivePort = null;
...
StayAlive();
...
async function StayAlive() {
var lastCall = Date.now();
var wakeup = setInterval( () => {
const now = Date.now();
const age = now - lastCall;
console.log(`(DEBUG StayAlive) ----------------------- time elapsed: ${age}`)
if (alivePort == null) {
alivePort = chrome.runtime.connect({name:INTERNAL_STAYALIVE_PORT})
alivePort.onDisconnect.addListener( (p) => {
if (chrome.runtime.lastError){
console.log(`(DEBUG StayAlive) Disconnected due to an error: ${chrome.runtime.lastError.message}`);
} else {
console.log(`(DEBUG StayAlive): port disconnected`);
}
alivePort = null;
});
}
if (alivePort) {
alivePort.postMessage({content: "ping"});
if (chrome.runtime.lastError) {
console.log(`(DEBUG StayAlive): postMessage error: ${chrome.runtime.lastError.message}`)
} else {
console.log(`(DEBUG StayAlive): "ping" sent through ${alivePort.name} port`)
}
}
//lastCall = Date.now();
}, 25000);
}
Hoping this will help someone.
Anyway, I still recommend, where possible, to review the logic and the code of your SW, because, as I mentioned at the beginning of this post, any well structured SW will work perfectly in MV3 even without tricks like this one.
EDIT (jan 17, 2023)
when you think you've hit bottom, watch out for the trapdoor that might suddenly open under your feet.
Sun Tzu
This revision of the StayAlive() function above still keeps the service worker active, but avoids calling the function every 25 seconds, so as not to burden it with unnecessary work.
In practice, it appears that by running the Highlander() function below at predefined intervals, the service worker will still live forever.
How it works
The first call of Highlander() is executed before the expiration of the fateful 30 seconds (here it is executed after 4 seconds from the start of the service worker).
Subsequent calls are performed before the expiration of the fateful 5 minutes (here they are executed every 270 seconds).
The service worker, in this way, will never go to sleep and will always respond to all events.
It thus appears that, per Chromium design, after the first Highlander() call within the first 30 seconds, the internal logic that manages the life of the (MV3) service worker extends the period of full activity until the next 5 minutes.
This is really really hilarious...
anyway... this is the ServiceWorker.js I used for my tests.
// -----------------
// SERVICEWORKER.JS
// -----------------
const INTERNAL_STAYALIVE_PORT = "CT_Internal_port_alive"
var alivePort = null;
const SECONDS = 1000;
var lastCall = Date.now();
var isFirstStart = true;
var timer = 4*SECONDS;
// -------------------------------------------------------
var wakeup = setInterval(Highlander, timer);
// -------------------------------------------------------
async function Highlander() {
const now = Date.now();
const age = now - lastCall;
console.log(`(DEBUG Highlander) ------------- time elapsed from first start: ${convertNoDate(age)}`)
if (alivePort == null) {
alivePort = chrome.runtime.connect({name:INTERNAL_STAYALIVE_PORT})
alivePort.onDisconnect.addListener( (p) => {
if (chrome.runtime.lastError){
console.log(`(DEBUG Highlander) Expected disconnect (on error). SW should be still running.`);
} else {
console.log(`(DEBUG Highlander): port disconnected`);
}
alivePort = null;
});
}
if (alivePort) {
alivePort.postMessage({content: "ping"});
if (chrome.runtime.lastError) {
console.log(`(DEBUG Highlander): postMessage error: ${chrome.runtime.lastError.message}`)
} else {
console.log(`(DEBUG Highlander): "ping" sent through ${alivePort.name} port`)
}
}
//lastCall = Date.now();
if (isFirstStart) {
isFirstStart = false;
clearInterval(wakeup);
timer = 270*SECONDS;
wakeup = setInterval(Highlander, timer);
}
}
function convertNoDate(long) {
var dt = new Date(long).toISOString()
return dt.slice(-13, -5) // HH:MM:SS only
}
EDIT (jan 20, 2023):
On Github, I created a repository for a practical example of how to properly use the Highlander function in a real world extension. For the implementation of this repo, I also took into account wOxxOm's comments to my post (many thanks to him).
Still on Github, I created another repository to demonstrate in another real world extension how a service worker can immediately start by itself (put itself in RUNNING status), without the aid of external content scripts, and how it can live on forever using the usual Highlander function.
This repository includes a local WebSocket Echo Test server used by the extension in its client communication sample and useful to externally debug the extension when the extension's host browser has been closed. That's right, because, depending on the type of configuration applied, when the host browser is closed Highlander-DNA can either shut down with the browser or continue to live forever, with all functionality connected and managed (e.g. the included WebSocket client/server communications test sample).
EDIT (jan 22, 2023)
I tested memory and CPU consumption while a Service Worker is always in RUNNING state due to the use of Highlander. The consumption to keep it running all the time is practically ZERO. I really don't understand why the Chromium team is persisting in wanting to unnecessarily complicate everyone's life.
WebSocket callbacks registered from within the chrome.runtime listener registrations of my extensions's service worker would not get invoked, which sounds like almost the same problem.
I approached this problem by making sure that my service worker never ends, by adding the following code to it:
function keepServiceRunning() {
setTimeout(keepServiceRunning, 2000);
}
keepServiceRunning()
After this, my callbacks now get invoked as expected.

Submitting data from Xamarin iOS when backgrounded

I have a weird problem in our current Xamarin project. As the app sends a larger chunk of data to the server, to protect it when app gets backgrounded, we're starting a long-running task (using the UIApplication.SharedApplication.BeginBackgroundTask / UIApplication.SharedApplication.EndBackgroundTask API). What's weird is that this works great when I build and run from my machine but several of my colleagues get a timeout error when running the exact same scenario when the app was build/deployed from their machines.
As far as I understand it running stuff in a long-running task like this should work. I should not have to specify backgrounding capabilities in info.plist. Also, as the HttpClient employ NSUrlSession for sending/receiving it should be protected from interruptions when the app gets backgrounded, right?
I can't figure out why the same code yields different behavior on the same device when built with different Macs. Could there be some setting somewhere in VS that could be local to the machine that would affect this behavior?
I'm out of ideas now so any hints would be greatly appreciated.
This is an example of code that works/fails depending on the Mac that built/deployed it:
public async Task Submit()
{
// ... some irrelevant code
BackgroundTask.Run(async () => await submitAsync()); // Form-level encapsulation of the UIApplication.SharedApplication.BeginBackgroundTask API
// ... more irrelevant code
}
private async Task submitAsync()
{
if (!IsSubmitAllowed)
return;
IsBusy = true;
IsGoBackAllowed = IsGoNextAllowed = false;
var statusIndicator = new StatusIndicator();
await PopupNavigation.Instance.PushAsync(statusIndicator);
try
{
statusIndicator.Status = "Saving TI";
statusIndicator.Progress = 0.1;
var submitted = await _service.SubmitAsync(Model); // ERROR! causes timeout exception for some
var submittedId = submitted.BackendId;
// ... etc.
Both of your assumptions seem to be wrong.
First, beginBackgroundTaskWithExpirationHandler: doesn't grant unlimited background activity. Most notably:
https://developer.apple.com/documentation/uikit/uiapplication/1623031-beginbackgroundtaskwithexpiratio?language=objc
Apps running background tasks have a finite amount of time in which to
run them
Second, NSURLSession isn't enabled by default in the HttpClient, and overal NSURLSession is not something that handles transfers in the background by default, that is just possibility and it would be natural that HttpClient doesn't use this mode. Again check the documentation: https://developer.apple.com/documentation/foundation/nsurlsession

Silverlight 4 Ria Services and multiple threads

I have a very long running query that takes too long to keep my client connected. I want to make a call into my DomainService, create a new worker thread, then return from the service so that my client can then begin polling to see if the long running query is complete.
The problem I am running into is that since my calling thread is exiting right away, I am getting exceptions thrown when my worker tries to access any entities since the ObjectContext gets disposed when the original thread ends.
Here is how I create the new context and call from my Silverlight client:
MyDomainContext context = new MyDomainContext();
context.SearchAndStore(_myParm, SearchQuery,
p => {
if (p.HasError) { // Do some work and return to start
} // polling the server for completion...
}, null);
The entry method on the server:
[Invoke]
public int SearchAndStore(object parm)
{
Thread t = new Thread(new ParameterizedThreadStart(SearchThread));
t.Start(parms);
return 0;
// Once this method returns, I get ObjectContext already Disposed Exceptions
}
Here is the WorkerProc method that gets called with the new Thread. As soon as I try to iterate through my query1 object, I get the ObjectContext already Disposed exception.
private void WorkerProc(object o)
{
HashSet<long> excludeList = new HashSet<long>();
var query1 = from doc in this.ObjectContext.Documents
join filters in this.ObjectContext.AppliedGlobalFilters
.Where(f => f.FilterId == 1)
on doc.FileExtension equals filters.FilterValue
select doc.FileId;
foreach (long fileId in query1) // Here occurs the exception because the
{ // Object Context is already disposed of.
excludeList.Add(fileId);
}
}
How can I prevent this from happening? Is there a way to create a new context for the new thread? I'm really stuck on this one.
Thanks.
Since you're using WCF RIA. I have to assume that you're implementing two parts:
A WCF Web Service
A Silverlight client which consumes the WCF Service.
So, this means that you have two applications. The service running on IIS, and the Silverlight running on the web browser. These applications have different life cycles.
The silverlight application starts living when it's loaded in the web page, and it dies when the page is closed (or an exception happens). On the other hand (at server side), the WCF Web Service life is quite sort. You application starts living when the service is requested and it dies once the request has finished.
In your case your the server request finishes when the SearchAndStore method finishes. Thus, when this particular method starts ,you create an Thread which starts running on background (in the server), and your method continues the execution, which is more likely to finishes in a couple of lines.
If I'm right, you don't need to do this. You can call your method without using a thread, in theory it does not matter if it takes awhile to respond. this is because the Silvelight application (on the client) won't be waiting. In Silverlight all the operations are asynchronous (this means that they're running in their own thread). Therefore, when you call the service method from the client, you only have to wait until the callback is invoked.
If it's really taking long time, you are more likely to look for a mechanism to keep the connection between your silverlight client and your web server alive for longer. I think by modifying the service configuration.
Here is a sample of what I'm saying:
https://github.com/hmadrigal/CodeSamples/tree/master/wcfria/SampleWebApplication01
In the sample you can see the different times on client and server side. You click the button and have to wait 30 seconds to receive a response from the server.
I hope this helps,
Best regards,
Herber

Resources