w3c / ServiceWorker

Service Workers
https://w3c.github.io/ServiceWorker/
Other
3.63k stars 316 forks source link

Enabling multiple Service Workers for a single scope #921

Open yoavweiss opened 8 years ago

yoavweiss commented 8 years ago

Today, when a Web Performance service is interested in providing a Service Worker to help accelerate their customers’ sites, they have to make sure that their customers don’t already have a Service Worker that serves other use-cases on that same scope, or require their customers to modify their SW to include the acceleration service’s.

That may be tenable at the moment, but is likely to become less and less so, as more sites adopt Service Workers for flows that are more and more complex.

I’d love to see a way for such a service to install their own SW (which will be served from the customer’s domain), that would operate at a “lower layer” than the site’s SW, so that requests would hit the site’s SW first, then the service’s one, and responses would flow back up in the reverse order. The service’s SW would be closest to the network, as it may use proprietary protocols which require decoding before the response is sent to the site’s SW.

Libraries like ServiceWorkerWare seem to enable a similar flow, but they require all service workers to be written as middleware. Would be great to have a native way to do this, without requiring code changes to the site's SW.

What should the API enable?

I'm glad you asked ;)

Registration

Upon registration, SW will either opt-in to modular registration or not. If not, we can assume that order doesn’t matter for that SW and that it should override any other SW that didn’t opt-in to modular registration.

For SW that did opt-in to modular registration, they should be able to state if they want to run first (i.e. closest to the app), last (i.e. closest to the network) or (potentially?) somewhere in between.

Let's define "down the stack" as "closer to the network" for the explanations below.

Fetch flow

Fetch events should cascade between SWs where a fetch() call (or failure to call respondWith()) in one SW will trigger a fetch event in the one below it in the stack (or closer to the network). If there is none, the fetch should go to the network.

A respondWith() call in a SW will return the promise of the fetch call issued by the nearest SW above it in the stack (or closer to the app). If there is none, the resource should return to Fetch (and the browser’s resource handlers).

Events

For message, sync and push events that are registered from the page’s context, we need a way for the page to register them for a specific SW.

For sync and push events it seems simple as SyncManager/PushManager are available on ServiceWorkerRegistration. Maybe a similar mechanism can be adopted for messages.

/cc @jakearchibald @crdumoul

jakearchibald commented 8 years ago

Is this mainly for handling fetches? If so, it feels like a same-origin foreign fetch.

wanderview commented 8 years ago

I feel like there have been a number of folks at mozilla who wanted a more composable API for service workers. We've pretty much said that importScripts() is our method for composition, but it kind of sucks due to tight coupling in a single place.

On the other hand, though, I don't know if spinning up N worker threads for every network request is a good idea either.

NekR commented 8 years ago

On the other hand, though, I don't know if spinning up N worker threads for every network request is a good idea either.

Yeah, sounds like a big overhead, taking in account this issue: https://github.com/slightlyoff/ServiceWorker/issues/920

... unless everyone such worked will live in the same process and all will have the same active (SW is working) time. Which sounds weird too.

yoavweiss commented 8 years ago

Is this mainly for handling fetches? If so, it feels like a same-origin foreign fetch.

I guess you can think of it that way, yeah.

On the other hand, though, I don't know if spinning up N worker threads for every network request is a good idea either.

Can't we run all of them on the same worker thread? At least the use cases I can think of don't need multiple workers running in parallel. One SW yields (by calling fetch() or respondWith()) and another takes over.

NekR commented 8 years ago

Just for the info, if one SW is registered at / scope and second on /scripts/ scope. Then I fetch the /scripts/main.js file. Will browser spawn both SW in the same process or in separate processes?

On Jun 30, 2016 11:31 AM, "Yoav Weiss" notifications@github.com wrote:

Is this mainly for handling fetches? If so, it feels like a same-origin foreign fetch.

I guess you can think of it that way, yeah.

On the other hand, though, I don't know if spinning up N worker threads for every network request is a good idea either.

Can't we run all of them on the same worker thread? At least the use cases I can think of don't need multiple workers running in parallel. One SW yields (by calling fetch() or respondWith()) and another takes over.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/slightlyoff/ServiceWorker/issues/921#issuecomment-229596010, or mute the thread https://github.com/notifications/unsubscribe/ABIlkf8YYjKd2OaIhLKuF4shfKx_KYh3ks5qQ37ogaJpZM4JBS5x .

yoavweiss commented 8 years ago

@NekR - I assumed that since requests in different scopes are all in the same context, both will be handled by a single renderer, and therefore the same process. But I may be wrong, as there's a lot I don't know about how SW work.

wanderview commented 8 years ago

Can't we run all of them on the same worker thread? At least the use cases I can think of don't need multiple workers running in parallel. One SW yields (by calling fetch() or respondWith()) and another takes over.

Well, this is possible in script today as you noted. You just have to importScripts() the framework you want to use. Since what you are proposing is opt-in as well, I'm not sure how baking this in to the browser is better at this time.

Just for the info, if one SW is registered at / scope and second on /scripts/ scope. Then I fetch the /scripts/main.js file. Will browser spawn both SW in the same process or in separate processes?

This is completely implementation dependent and could change over time. Ideally you should not rely on details like this.

wanderview commented 8 years ago

I also think there are lots of unknowns that would have to be solved at the cost of increased complexity:

  1. What does updating a "second service worker" for the same scope look like? Can the primary service worker still be processing events while this happens? Or is it essentially treated as a single new service worker effectively? Running in the same thread implies a single new service worker.
  2. How would the separate service worker instances be exposed? Would registration.active by an array of ServiceWorker objects? How would the page know which one to postMessage() to?

I'm not saying that we should never do this, but I think its still pretty early days in terms of developers figuring out which patterns they want and need. My gut feeling is we should wait to see if a dominant design emerges in the framework ecosystem before baking something into the platform itself.

yoavweiss commented 8 years ago

Well, this is possible in script today as you noted. You just have to importScripts() the framework you want to use. Since what you are proposing is opt-in as well, I'm not sure how baking this in to the browser is better at this time.

The advantage of baking this in is that, at least for the optimization service scenario, only the service SW has to opt in, and the site can remain oblivious to the fact that a SW is running in a lower layer.

wanderview commented 8 years ago

The advantage of baking this in is that, at least for the optimization service scenario, only the service SW has to opt in, and the site can remain oblivious to the fact that a SW is running in a lower layer.

I don't understand what you mean here. The site can be oblivious to whether the SW script uses importScripts() as well. Did you mean the reverse? You can have SW scripts are oblivious to the fact they are running collaboratively with other SW scripts?

yoavweiss commented 8 years ago

Yeah. s/the site can remain oblivious/the site's SW can remain oblivious/

Basically, if I'm running such an optimization service, I want to be able to add my own optimization SW, yet I don't want my customers to have to change their existing code to make it work. In current script based solutions, I can potentially do that by overriding most SW related APIs, while hoping that sites won't register a SW using Link: headers or declarative markup.

Otherwise, I could use something like serviceworkerware and have to talk to customers and convince them to change their SW for my sake.

Neither is ideal.

I understand there's added complexity involved in baking this in. There's also a real use-case here, that cannot be entirely resolved in script.

wanderview commented 8 years ago

Basically, if I'm running such an optimization service, I want to be able to add my own optimization SW, yet I don't want my customers to have to change their existing code to make it work. In current script based solutions, I can potentially do that by overriding most SW related APIs, while hoping that sites won't register a SW using Link: headers or declarative markup.

How do you do this without asking your clients to change their site? Do you inject a script at the CDN level?

domenic commented 8 years ago

As far as I can tell, this proposal seems to be based on the belief that it's easier for sites/optimization services to drop in a new <script> tag to register a second service worker, than it is for those same sites/optimization services to drop in a new importScripts() call to work within the existing service worker? Why would that be the case?

NekR commented 8 years ago

How would the separate service worker instances be exposed? Would registration.active by an array of ServiceWorker objects? How would the page know which one to postMessage() to?

How having 2 SW on / is different from having one SW on / and second on /pages/, when serving /pages/article10.html? Will second SW "block" first SW and browser pretend that first SW doesn't exist?

... I am not saying you have to add or not this proposal, just trying to understand.

wanderview commented 8 years ago

How having 2 SW on / is different from having one SW on / and second on /pages/, when serving /pages/article10.html? Will second SW "block" first SW and browser pretend that first SW doesn't exist?

Currently the most specific (longest) scope wins. So /pages/article10.html is controlled by the /pages/ service worker. The / service worker is not invoked at all.

NekR commented 8 years ago

@wanderview I see, thanks. Then this proposal indeed introduces a lot of complexity.

crdumoul commented 8 years ago

As far as I can tell, this proposal seems to be based on the belief that it's easier for sites/optimization services to drop in a new Githubissues.

  • Githubissues is a development platform for aggregating issues.