WICG / storage-buckets

API proposal for managing multiple storage buckets
https://wicg.github.io/storage-buckets/explainer
Other
45 stars 20 forks source link

buckets and service workers #37

Open wanderview opened 3 years ago

wanderview commented 3 years ago

Previously I had been assuming service workers would not be part of non-default buckets. I didn't really understand the use case. Recently, however, it was mentioned developers want service workers in buckets so they can ensure the SW registration is evicted along with other related storage in the same bucket. Also, it seems like there could be eviction policies on the bucket that would be relevant to service workers; e.g. expire the bucket after a certain time period.

So the idea would be to associate service workers with buckets for the purposes of eviction and policy. Service workers in different buckets, however, would not be partitioned from one another. For any given scope there would still only be one registration for the entire origin, but it could be in any bucket.

What would the API shape of this be though?

One suggestion was to expose ServiceWorkerContainer on the bucket object just like any other storage type. If accessed that way you would get something that looked partitioned; e.g. getRegistration() would only return registrations in that bucket. If you called getRegistration() on the navigation.serviceWorker ServiceWorkerContainer, though, it would pull from all buckets. This would be done to maintain the behavior that getRegistration() returns the SW that would control the current page.

This proposal seems somewhat weird and magical to me, though. We expose ServiceWorkerContainer on the bucket endpoints as if they are partitioned, but they really are not partitioned.

An alternative would be to:

  1. Do not expose ServiceWorkerContainer on the bucket endpoints.
  2. Add ServiceWorkerRegistration attribute like bucket or policy-bucket that specifies the bucket name.
  3. Allow the attribute to be specified during registration via a dictionary option to register()

I think this would make it a bit clearer that the service workers are still all in the same unpartitioned pool of registrations, but that they are associated with buckets for the purposes of policy/eviction.

This alternative solution, though, does raise the question "can a registration move from one bucket to another"? The register() option approach supports this but we would have to define exactly what the semantics are.

wanderview commented 3 years ago

FYI @jakearchibald @asakusuma @asutherland @annevk as I'm not sure if you watch this repo.

asakusuma commented 3 years ago

Also, it seems like there could be eviction policies on the bucket that would be relevant to service workers; e.g. expire the bucket after a certain time period.

I like this idea. It would make client - worker interactions simpler if you knew you weren't going to have a really old service worker running. Any other use cases for needing the storage policy applied to the service worker itself?

We may want to revert this part of a recent PR of mine:

Storage policies do not affect the service worker registrations of a given bucket.

I haven't thought much about how to handle multiple service workers (mainly because it's not a tenable setup for us until scope pattern matching lands), but it's unclear to me why you would need to be able to identify the bucket given a ServiceWorkerRegistration

If you called getRegistration() on the navigation.serviceWorker ServiceWorkerContainer, though, it would pull from all buckets

what does "pull from all buckets" mean? is there a global default bucket?

wanderview commented 3 years ago

Right, the idea here is that all service worker registrations would still all be mixed together in a common pool even if they are assigned to different logical buckets. This is different than how every other storage type works with buckets. The motivation is so that when a navigation occurs and we need to match the request URL against a scope we need a single collection of registrations to consider. If we partitioned SW in buckets then you could have two registrations with the same scope in different buckets and it would not be clear which one to pick to control the new client.

asakusuma commented 3 years ago

The current proposal is to put a subset of ServiceWorkerContainer on the bucket object: https://github.com/WICG/storage-buckets/blob/gh-pages/explainer.md#storage-buckets-and-service-workers

const inboxBucket = await navigator.storage.buckets.open("inbox");
await inboxBucket.serviceWorker.register("/inbox-sw.js", { scope: "/inbox" });
const registrations = inboxBucket.serviceWorker.getRegistrations();

Sounds like the other option is:

await navigator.serviceWorker.register("/inbox-sw.js", {
  scope: "/inbox",
  bucket: "inbox"
});
const registrations = navigator.serviceWorker.getRegistrations({ bucket: "inbox" });

In a vacuum, I don't have a strong opinion here, but I think the existing proposal feels more consistent with how other bucket APIs work, where you access bucketed objects through the bucket JS object.

asakusuma commented 3 years ago

For instance, the cache api is:

const attachments = await navigator.storage.buckets.open("inbox").caches.open("attachments");

not

caches.open("attachments", {
  bucket: "inbox"
})
wanderview commented 3 years ago

I think the existing proposal feels more consistent with how other bucket APIs work, where you access bucketed objects through the bucket JS object.

I guess I find it confusing that the underlying semantics are different, but we try to make the API shape the same. It seems like a different API shape would help communicate the different semantics.

For example, with cache_storage you can do:

const inbox_attachments = await navigator.storage.buckets.open("inbox").caches.open("attachments");
const doc_attachments = await navigator.storage.buckets.open("documents").caches.open("attachments");

And have two different cache objects with the same name "attachments" in different buckets.

But for service workers you can't do this since we need a single global namespace for scopes.

asutherland commented 3 years ago

I don't have a strong opinion here. In initial discussions I think there was a shared understanding that there wasn't a perfect answer here because of the issues @wanderview raises of tensions between API consistency and the realities of ServiceWorkers residing in a global scope namespace.

I would tend to favor doing whatever is the least amount of work for whoever writes the spec changes for ServiceWorkers to pose it in terms of buckets and bottles! In the event we think it's pretty much the same either way, then whatever seems to be the least confusing when written up as a "Writing your first ServiceWorker that uses buckets" tutorial seems good.

But in the great spirit of "why not just do both?", how bad it would be to do what @wanderview proposes but also if the myBucket.serviceWorker.register(a, b) was just defined as a convenience method that amounts to navigator.serviceWorker.register(a, { bucket: myBucket.name, ...b}) and similarly for the getRegistration calls? It seems like this would be the least awkward to explain in documentation.

wanderview commented 3 years ago

But in the great spirit of "why not just do both?", how bad it would be to do what @wanderview proposes but also if the myBucket.serviceWorker.register(a, b) was just defined as a convenience method that amounts to navigator.serviceWorker.register(a, { bucket: myBucket.name, ...b}) and similarly for the getRegistration calls? It seems like this would be the least awkward to explain in documentation.

So I think the concern I have with this is that if you registered the same scope in a different bucket using the bucket endpoints it would appear to silently delete the registration in the other bucket. Its strikes me that this would be surprising, but maybe I'm wrong.