Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use cases that are not well served by service workers #72

Open
dotproto opened this issue Sep 2, 2021 · 113 comments
Open

Use cases that are not well served by service workers #72

dotproto opened this issue Sep 2, 2021 · 113 comments
Labels
topic: service worker Related to service worker background scripts

Comments

@dotproto
Copy link
Member

dotproto commented Sep 2, 2021

Near the end of the 2021-08-19 meeting we briefly discussed (but didn't capture) the idea of collecting background page use cases and design patterns that are not well served by service workers. My hope is that by collecting these use cases in a well known location, community members and browser vendors we will be able to more concretely discuss the challenges posed by this new background context and explore potential solutions.

Please use this issue to link to use use cases that are impossible to accomplish with or not well supported by service workers. If you'd like to report a new use case, please create a new issue and reference this one.

Use cases

Specific ways that background pages are used that cannot be accomplished with a service worker.

Other discussions

@dotproto dotproto added the topic: service worker Related to service worker background scripts label Sep 2, 2021
@mingyaulee
Copy link

One additional use case for background service instead of service worker is the use of web assembly.
This is related to the issue I raised in #39.

@cuylerstuwe
Copy link

cuylerstuwe commented Sep 3, 2021

Not added here is extension-driven behavior from an external source.

This includes user-driven input coming from an outside (related) desktop/cloud app via WebSockets or Native Messaging Hosts.

Their connections need to persist indefinitely in order to work effectively.

@cohosh
Copy link

cohosh commented Sep 3, 2021

Hey! I've just shared a use-case where we need access to RTCPeerConnection for service workers in order to upgrade to using v3 manifests here: w3c/webrtc-extensions#77 (comment)

@avi12
Copy link

avi12 commented Sep 3, 2021

Another use-case is using FFmpeg via @ffmpeg/ffmpeg, which is using WebAssembly
To provide the best user experience using this module, you need to put it on the background page, which would make it accessible from all of the parts of the extension
Such a background page must also be persistent, as it takes a few seconds until FFmpeg gets initialized; if it isn't persistent, it might get terminated by the browser, thus requiring re-initializing FFmpeg for every user action that would result in FFmpeg processing

@dotproto
Copy link
Member Author

dotproto commented Sep 3, 2021

Such a background page must also be persistent, as it takes a few seconds until FFmpeg gets initialized; if it isn't persistent, it might get terminated by the browser, thus requiring re-initializing FFmpeg for every user action that would result in FFmpeg processing

@avi12, from the Chrome team's point of view terminating an unused worker is a feature, not a bug. Can you expand on why reinitializing FFmpeg is a problem? Adding latency to an uncommon operation is understandably undesirable, but so is keeping around an unused JS context.

Is there a specific workflow with an existing extension you can point to?

@dotproto
Copy link
Member Author

dotproto commented Sep 3, 2021

@mingyaulee Added a crbug link related to allowing Wasm in MV3 service workers.

@cuylerstuwe, I just added a thread you started on chromium-extensions related to WebSockets and a crbug issue tracking native messaging.

@cohosh Added your link and the related crbug issue

@avi12
Copy link

avi12 commented Sep 4, 2021

@dotproto

Adding latency to an uncommon operation is understandably undesirable, but so is keeping around an unused JS context.

That's true. However, in CLI apps like FFmpeg, which can only do one job at a time (which is particularly true for the WebAssembly version), if the job takes too much time (depending on the operation and the size of the files you work with), it might get terminated mid-way through, due to the limited 5-minute lifespan of an extension's Service Worker

Is there a specific workflow with an existing extension you can point to?

Right now, I'm working on this extension

@avi12
Copy link

avi12 commented Sep 5, 2021

Another use-case is when needing to download files, which, if need to be initiated in the current Service Worker, will have 2 issues:

  1. A Service Worker's lifespan is up to 5 minutes, If you need to download files that would take more than 5 minutes, it would be disrupted mid-way
  2. If you find yourself needing to use the Fetch API (like in my extension), making the download cancellable is important for a good user experience. The only way to cancel a Fetch process is by using AbortController's AbortSignal, which so happens to use the DOM

@Jack-Works
Copy link

A Service Worker's lifespan is up to 5 minutes, If you need to download files that would take more than 5 minutes, it would be disrupted mid-way

Try Background fetch?

@avi12
Copy link

avi12 commented Sep 6, 2021

That's an interesting option
However, my extension that I linked above also needs to run FFmpeg on the background, so I don't think the Background Fetch API will be enough for my use-case

@cuylerstuwe
Copy link

cuylerstuwe commented Sep 6, 2021 via email

@tregagnon
Copy link

@dotproto I think crbug 893175: Request for an API to specify Light and Dark Mode Icons should be added to the list

The Service Worker does not have window.matchMedia() which was used by Extensions to identify if the OS was in Dark/Light mode. We need the action.setIcon() API to be extended so Light/Dark icons can be set.

Defining those Light/Dark icons in the Manifest only is insufficient for extensions like Dashlane, which also uses the icon to indicates if the user is authenticated or not.

@ghostwords
Copy link

ghostwords commented Sep 16, 2021

What about the general mismatch of webpages having user-defined lifetimes with Service Worker-based extensions having arbitrarily limited lifetimes (five minutes in Chrome at this time)?

The user may be relying on extension-provided functionality on some webpage when the extension's SW gets shut down. Even if the SW were to get restarted in response to content script messages, shutting down and restarting a SW is inherently a waste of resources. The additional delay caused by SW startup will break use cases that depend on speedy messaging between the content script and the background page (for example, an extension that dynamically modifies the right-click menu based on type of clicked element). Furthermore, after a SW restart, content scripts may have stale configuration data and won't work properly without additional message passing or a page reload.

If content scripts were to also get reloaded along with the SW, this would introduce clear usability issues, where the user is in the middle of some extension-facilitated workflow that suddenly stops working.

@tophf
Copy link

tophf commented Sep 25, 2021

Another blocker: https://crbug.com/1219164 - ability to create nested workers.

BTW, the title should reflect that these use cases are currently impossible to implement in a service worker, please don't use the euphemistic language such as "are not well served".

@gildas-lormeau
Copy link

It's not possible to use chrome.i18n.getMessage to set the label of the action button or the entries in the context menu.

@capaj
Copy link

capaj commented Sep 28, 2021

Safely storing confidential information for the duration when browser is running.

Chrome docs on storage api explicitly say that:
image

Yet they explicitly advise to use it to store data which is needed in the service worker:
https://developer.chrome.com/docs/extensions/mv3/migrating_to_service_workers/#state

I think @bitwarden relies on this when they store the master password inside backgroundPage so that the user doesn't have to put it in every time when they open the Popup.

@gorhill
Copy link

gorhill commented Sep 28, 2021

Please use this issue to link to use use cases that are impossible to accomplish with or not well supported by service workers.

A use case not possible with a worker, "Bug 1580254: Support CSS selector validation API from a service worker":

Removing access to a DOM would currently break uBlock Origin (“uBO”). uBO uses a DOM element to validate plain cosmetic filters[4] – which are essentially CSS selectors. Having access to the DOM to validate plain cosmetic filters allow uBO to detect and discard or further process invalid cosmetic filters.

[...]

@ghostwords
Copy link

Requesting file:-scheme resources (aka file:// URLs)

The Fetch API is also unable to retrieve chrome:// resources apparently, something you can do now with XMLHttpRequest.

@tophf
Copy link

tophf commented Oct 10, 2021

Dynamic import of scripts in a service worker is only partially implemented and prohibitively convoluted.

https://crbug.com/1198822

  • No dynamic import(), also no script elements since there's no DOM.

  • Only importScripts is supported.

    • it imports solely into the global scope, which means you would need a module management system i.e. use webpack (and similar systems) or find some service-worker-compatible library or write one yourself;
    • it's synchronous, which adds considerable internal overhead to stop the execution environment for the duration of reading the file. It's essentially the same as the ancient synchronous blocking XMLhttpRequest, deprecated and strongly discouraged for many years.
  • We have to duplicate importScripts explicitly inside the install event listener of the service worker for every script we will ever import later. The same will be necessary for the dynamic import() when it's implemented.

And there's also a foundational issue of an understandable reluctance in the service worker spec group to provide WebExtensions-specific improvements, further exacerbated by an understandable reluctance of the browser developers (Chromium extensions team, specifically) to provide non-standard exceptions in a service worker as it would add maintenance burden on them and introduce fragmentation of otherwise solid technology. Allowing the dynamic import of scripts without the need to pre-import them in the install event listener (all importable scripts are already local in an extension) is an example of such problem as well as the ability to control the lifetime of the service worker or create blob: object URLs, and many more problems introduced by the premature decision to switch to service workers in ManifestV3.

@avi12
Copy link

avi12 commented Oct 10, 2021

from the Chrome team's point of view terminating an unused worker is a feature, not a bug. Can you expand on why reinitializing FFmpeg is a problem?

@dotproto Assuming the user is willing to wait a few seconds every time he wishes to use the FFmpeg functionality, the Service Worker will still get terminated after 5 minutes, making mv3 a huge limitation

@DavidJCobb
Copy link

DavidJCobb commented Dec 10, 2022

Service workers will lead to inconsiderate and wasteful bandwidth usage. Consider, for example, an extension that allows a user to manage or search through personal content or collections stored on a website (e.g. "favorites," "followers," "lists," etc.) in ways that a site does not normally offer -- perhaps a site with relatively minimal APIs, as opposed to the Internet's major corporate-funded platforms; in essence, a site that you have to scrape in order to extend.

The polite way to implement something like this would be to cache data in-memory whenever possible (possibly clearing that cache after enough time spent idle or off-site) to minimize how many redundant requests have to go over the network during a single browsing session. However, you can't cache anything using service workers. The 1MB storage in chrome.storage.session is tiny; it'd be pathetically small even if we could store space-efficient binary data e.g. DataView, which, of course, we can't. The other storage areas are inappropriate for caching during a single browsing session, and may have privacy implications (e.g. for incognito browsing) as well. (There would also be logistical difficulties in taking what's supposed to be a session-specific cache and placing it in persistent storage. At minimum, you'd have to go to the trouble of wiping your jury-rigged cache onStartup and rejecting/deferring all queries until that's complete. With how patchy WebExtensions APIs have been in my experience, I have zero confidence that trying this wouldn't lead to some huge unexpected obstacle.)

That leaves the impolite way: repeatedly bombard the website with requests for the same information you just asked for three minutes ago. Cache nothing; precompute nothing; optimize nothing; write rampantly wasteful code, because it's the only thing you're allowed to write.

The performance cost for the network requests can be larger than one might expect. I've been privately developing and testing some WebExtensions and working on getting them release-ready, and some of the sites I've been making add-ons for have anti-botting/anti-DDoS measures. Slamming them with tons of rapid-fire redundant requests is a great way to get rate-limited and receive a slew of 503s. If you need to scrape, say, a few dozen pages to gather up and preload metadata about a user's saved content, you might have to throttle those requests to ensure reliability. That's fine when it's information that you can query just once or twice, store in a background page, and then reuse throughout the rest of a browsing session (perhaps manually updating the in-memory copy without extra network requests if the user makes changes through the website). With service workers, however, you can't usefully cache anything, so that throttling ends up adding a substantial delay to a WebExtension's reactivity, because now the user has to wait on it every time the service worker wakes up.

@radiolondra
Copy link

radiolondra commented Jan 11, 2023

It seems to me (tested) that calling the function below when any service worker starts, keeps the service worker up forever (that is, it doesn't go to sleep anymore).
Question: Is this (another) Chrome bug or I found a way to keep a service worker alive forever?

-------------------------------------------->>> Code Description
The function (StayAlive), inside the service worker, connects to a named port and tries to send a message through it to a nonexistent listener (so it will generate errors) when the service worker starts.

While doing this, SW is active and running (ok, it has something to do, that is, it has to send a message through a port, but this is my doubt about a possible Chrome bug, because the connect() is started by the service worker itself, not by some external tab/app/extension/whatever... ok, lets go on with the code description).

Because noone is listening, it generates a (catched and logged) error (in "onDisconnect" port event) and terminates.

But after 25 secs it does the same iter from start, keeping SW active forever.

Note: To perform reliable tests remember to not open any DevTools page. Use chrome://serviceworker-internals instead and find the log (Scope) of your test extension ID.

This is the code:

// In ServiceWorker.js
// -------------------
// 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;
...
// Call the function at SW start
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);
}

@cacosandon
Copy link

If you came here looking for recording video/audio solution for MV3, you can use this example: https://github.com/wireworks-app/chrome-screen-recording

@Pauan
Copy link

Pauan commented Oct 14, 2023

I have created an extension which helps the user to manage tabs more easily.

It displays the user's tabs in a vertical list, it allows for organizing tabs with labels, it allows for searching through tabs, it allows for bulk operations on many tabs, etc.

It's designed for users who have a lot of tabs (hundreds, or potentially even thousands of tabs).

Because it needs to process a lot of tabs, and it needs to process those tabs quickly, it keeps the tab data alive in RAM with a persistent background page.

When a new tab event happens (because a tab was closed, or opened, or moved, etc.) it can very quickly process the event, because all of the tab data is kept in RAM.

This is a very natural and efficient design, and it mimics what the browser itself does (the browser has its own C++ representation of the tabs which is always kept available in RAM).

However, this is incompatible with service workers, because service workers can never be persistent.

Re-processing potentially thousands of tabs on every tab event is unacceptable for performance, it will slow down the browser and lead to a bad user experience.

@Pauan
Copy link

Pauan commented Oct 14, 2023

I have created a Chrome extension which automatically bets on the saltybet.com website (automatic betting bots are allowed by the SaltyBet rules).

In order to make the bets, it needs to look at past historical data. There is a LOT of historical data (258 MB of data).

This data is loaded and processed inside of a persistent background page. This processing takes about 39 seconds.

This is acceptable because the data only needs to be loaded one time, and then it can be reused over and over again for each match.

However, this is incompatible with service workers, because service workers cannot be persistent.

Re-loading the data over and over again is completely 100% unacceptable for performance.

Any use case that requires loading or processing large amounts of data is incompatible with service workers.

For example, if somebody wanted to create a folding@home extension for Chrome, it would not work because it needs to store and process large amounts of data.

@Pauan
Copy link

Pauan commented Oct 14, 2023

Cryptocurrency wallets are incompatible with service workers.

Cryptocurrency requires a lot of expensive computations (to generate keys, download the blockchain, etc.). This computation can potentially take several seconds, or even minutes.

Because the computation is so expensive, cryptocurrency extensions must cache the computation so that it only needs to be done one time.

However, because service workers cannot be persistent, that means it cannot cache the computation.

Here is an example:

https://chrome.google.com/webstore/detail/leo-wallet/nebnhfamliijlghikdgcigoebonmoibm

Because of the lack of persistent background pages, that extension opens a new tab and then does all of its computation inside of that tab.

So the user must keep that tab open at all times in order to use the extension.

This is obviously a completely unacceptable user experience.

Persistent background pages do not have that problem, because it can simply cache the computation inside of the (invisible) background page.

With persistent background pages, the extension could use a browser action popup for its UI.

But because service workers are not persistent, the extension cannot use a browser action popup, it must use a browser tab, which is an inferior user experience.

Any extension that requires in-memory caching of expensive computations is incompatible with service workers.

As time goes on, more extensions will start to abuse tabs as a substitute for persistent background pages, requiring the user to keep the tab open at all times in order for the extension to function.

@alanhkarp
Copy link

alanhkarp commented Oct 14, 2023 via email

@Pauan
Copy link

Pauan commented Oct 14, 2023

Can you use session storage? That's what I'm using for a very small amount of data.

No, cryptocurrency wallets take security VERY seriously, it is unacceptable to have even the tiniest chance of private secrets leaked. Very large amounts of money are involved.

Anything private must be kept exclusively in memory, which means the extension must use in-memory caching with persistent background pages (or abuse tabs as a substitute for persistent background pages).

Even if sessionStorage was guaranteed to be in-memory only, it can only store a small amount of data, so it cannot support every use case.

Also, sometimes the cryptocurrency calculations take longer than 5 minutes, which means Chrome will kill the service worker, which obviously breaks everything. Caching cannot solve that problem.

So any extension that needs to run a very expensive computation (which takes longer than 5 minutes) is incompatible with service workers.

@tophf
Copy link

tophf commented Oct 14, 2023

Note that session storage leaks into the content script context in Chrome, which can be hacked by a web page via a side channel attack as they share the same physical process.

@radiolondra
Copy link

radiolondra commented Oct 16, 2023

You could give a look to this:
https://stackoverflow.com/questions/66618136/persistent-service-worker-in-chrome-extension/75082732#75082732
https://github.com/radiolondra/ServiceWorker-Highlander
https://github.com/radiolondra/ServiceWorker-Highlander-DNA
I use Highlander to make background worker persistent forever in my MV3 browser extensions. It works.
I already have talked about it here: #72 (comment)
when I was starting to work on it.

@alanhkarp
Copy link

alanhkarp commented Oct 16, 2023 via email

@Pauan
Copy link

Pauan commented Oct 16, 2023

@alanhkarp Does that mean I'm safe?

Maybe, maybe not. It's possible for malicious code to gain access to the content script (either through a browser bug, or XSS, or something like Spectre).

So even if you don't set up an event listener, if the malicious code is able to set up an event listener then it is vulnerable.

@alanhkarp
Copy link

alanhkarp commented Oct 16, 2023 via email

@erosman
Copy link

erosman commented Dec 10, 2023

I came across a simple issue which has become complicated in the background service worker.

'prefers-color-scheme' detection in Chrome background service worker

window.matchMedia('(prefers-color-scheme: dark)').matches
// Uncaught (in promise) ReferenceError: window is not defined

The complicated chrome.offscreen option isn't optimal when window.matchMedia has to be performed repeatedly to set action.setBadgeBackgroundColor.

@hanguokai
Copy link
Member

'prefers-color-scheme' detection in Chrome background service worker ……

This has been discussed several times in Chromium forum, like this. Even in the event page, it also has a problem. That is why it requires a better solution from the browser #229 .

@visionarylab
Copy link

visionarylab commented Dec 16, 2023

The service worker seem to want to minimize battery usage with lesser cpu cycle (service worker die in 5 min) and memory holding.

Some browser vendor might argue, there is ton of other tool for "automation" on web, other than browser, because safety issue (Other UI tools)

  1. For memory, with api similar to localstorage, it can be solve by assign limited memory amount to every extension. (Or just hidden the complexity by following options permission.

  2. Since permission is the most successful stuff we had to protect everyone, from firefox to android era. Potentially there is a option list from 128MB to 1GB we willing to give to every extension we installed.

  • Most user dont use more than 30 extension, it should be ok to micro manage by people. Permission is the only successful way for teaching user's security (modern generation)

@ParticleCore
Copy link

Scary to see that even after all this time, the original major issues have yet to be addressed. I'm adding my issue into the mix as well in hopes that it contributes for something #518

@cedricalfonsi
Copy link

I would like to bring attention to a challenge we're encountering with service workers in Chrome, specifically their inability to fetch from servers presenting invalid certificates: Error: (failed)net::ERR_CERT_AUTHORITY_INVALID.

This issue is documented in the following Chromium tickets:

I want to verify with you if fetching from servers with invalid certificates via the extension service worker will be supported over the long term. Additionally, could employing the offscreen API be a viable temporary workaround for this problem? Any guidance on this approach would be appreciated.

@scholtzm
Copy link

scholtzm commented May 7, 2024

3 years later and we still can't use dynamic/postponed imports:

https://issues.chromium.org/issues/40760920

@JWorthe
Copy link

JWorthe commented May 15, 2024

Today I was trying to debug a performance issue in the service worker of our extension which has recently migrated to MV3.

In Chrome's Devtools, on the Performance tab, it tells me "Performance trace recording not supported for this type of target".

@tophf
Copy link

tophf commented May 15, 2024

Today I was trying to debug a performance issue in the service worker of our extension which has recently migrated to MV3.

There's a terrible but functioning workaround:

  1. open any page/file from your extension in a tab so that its URL is chrome-extension://id/....
  2. duplicate this tab
  3. open devtools in both tabs
  4. in tab A -> devtools -> Application -> Service worker -> click stop
  5. in tab B -> devtools -> Performance -> start recording
  6. in tab A -> devtools -> Application -> Service worker -> click start
  7. in tab B -> devtools -> Performance -> stop recording, drag the flamechart to reveal the service worker row, expand it

It can be simplified if your service worker has a listener that can be triggered externally e.g. chrome.action.onClicked, chrome.commands.onCommand, chrome.contextMenus.onClicked, or any other listener triggered in response to something like navigation in a tab:

  1. open any page/file from your extension in a tab so that its URL is chrome-extension://id/....
  2. devtools -> Application -> Service worker -> click stop
  3. devtools -> Performance -> start recording
  4. perform an action for which there is a listener in the service worker
  5. stop recording, drag the flamechart to reveal the service worker row, expand it

An alternative method for stopping/starting the service worker is chrome://serviceworker-internals page, which is less convenient, but it doesn't close when you reload the extension or call chrome.runtime.reload().

@JWorthe
Copy link

JWorthe commented May 15, 2024

Thank you so much for the workaround, @tophf!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic: service worker Related to service worker background scripts
Projects
None yet
Development

No branches or pull requests