-
Notifications
You must be signed in to change notification settings - Fork 312
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
API Generality #445
Comments
Thanks for starting this conversation. I've felt the same discomfort about the registration API from the beginning, but hasn't been able to put a finger on what exactly bothered me until recently. Currently ServiceWorkers suffers from a couple of problems related to the registration API. Apps with URLs in multiple directoriesFirst of all, a ServiceWorker currently can only service a single URI pattern. This means that if your application lives under "/calendar/" and under "/meetings/", you are left with mainly bad choices. You could change your URLs, but that means breaking any existing links. You could register for "/". But that means that if you have two applications on the same server with the same problem, you now have to use the same serviceworker for two separate applications. It also means that a lot of URLs that are unrelated to the application You could use create two separate registrations. But that looses some of the nice atomic update behavior that ServiceWorkers provide. It would also mean that we're now running two worker threads rather than one, which costs resources, especially on mobile. Additionally, this would require that manifests support multiple service worker registrations in a single manifest. You could use two separate registrations, but use the same service worker URL. This could in theory enable the implementation to do a single download for both service workers, so that part would still be atomic. However the install steps might still fail for one but not the other. It also adds additional complexity to avoid having the two service worker instances stomp on each other's caches or download the same resources twice. And it has the same two-threads and manifest-complexity problems as the two-separate-registrations solution. You could simply choose not to cache one of the two paths. This is the option I'm most worried developers will choose since it means that the app won't be as fast, and might not work offline. But it's also the simplest solution which means that there's a big risk that developers will choose it. Http-intercept registration differs from other featuresThe second problem with the registration API is that the "intercept http requests" feature uses a different registration mechanism than things like "register push notifications" or "register geofencing notification". This isn't a huge problem, but is an unfortunate inconsistency. A slightly bigger problem is that it means that in order to use other service worker features, you have to register for a scope at which to intercept http requests, even if you're not interested in that ability. To handle that we have added the "look for event handlers in the global scope" thing, and use that to effectively unregister for the "intercept http requests" feature. Proposed solutionA solution to both of these problems would be to separate installing a service worker from registering for http intercepts. This way we could enable websites to make multiple calls to "register for http intercepts" in order to make a service worker cover multiple http scopes. This could look something like this:
However a question is what will now be the identifier for a SW given that we currently use the http intercept scope as the identifier. The simplest answer seems to be to simply enable service workers to have a name. So registration would look something like:
This has the following benefits over the current API:
The main disadvantage that I can see is that registering for http interception now requires slightly more code. This seems ok to me, but if we want to address it, we could as sugar enable passing in a scope during registration:
This is almost exactly what the API looks like today, the main difference is that if you don't pass in a scope at registration, that no scope gets registered, rather than that |
I should note that this still gives the http-intercept feature somewhat of a special status. Scopes still get a predominant position in the API, and so far scopes are specific to http intercepts. This affects things like However I think this is ok. First of all http-intercepts is one of the more important features of ServiceWorkers, which is why I also think it's ok to have syntax for it in the registration API. But second, the concept of a scope could very well come in handy for other features. |
Despite this being a initial motivation for the feature as a whole, I think that greater generality is still valuable here. First use not conferring any sort of special status. To that end, here's my alternative approach. It's more disruptive, but it has some encapsulation benefits over both the current and proposed alternatives. It retains the general concept of "scope", but generalizes that concept more. URL-space scope, as used in the fetch/caching cases is largely the same, but it has a different entry point. The outer interface ServiceWorkerContainer {
Promise<ServiceWorkerRegistration> register(ScalarValueString script, DOMString name = "");
Promise<ServiceWorkerRegistration> getRegistration(optional DOMString name = "");
...
} This is no different to using names for identification as @sicking proposes. What changes is the internals and how events are surfaced to workers. During the this.addEventListener("install", e => {
e.waitUntil(Promise.all(
this.webPush.register().then(push => {
push.addEventListener("push", handlePush);
});
this.geolocation.register(geolocationOptions).then(geo => {
geo.addEventListener("enter", handleGeoEnter);
geo.addEventListener("leave", handleGeoLeave);
});
this.fetch.register(scope).then(...);
this.cache.register(scope).then(...);
));
}); The trick here being that each different module (or whatever you want to call a logical function that we might use SW for) has particular needs for determining how to pre-filter the events it might generate. Relying on a single string value (or set thereof) isn't sufficient for the new geolocation features (see proposals here and here) and we can't possibly anticipate the needs that a future API might generate. On the other hand web push has no need for any such affordance, at least as far as current plans indicate, though that could change (and likely will over time). The idea that apps might need the same fetch access for multiple path prefixes is clearly another example of how different use cases evolve to encompass new needs. interface ServiceWorkerGlobalScope {
GeolocationWorkerContainer geolocation;
WebpushWorker push;
FetchWorker fetch;
...
}
interface GeolocationWorkerContainer {
Promise<GeolocationWorker> register(optional GeolocationWorkerOptions options, optional DOMString name = "");
Promise<GeolocationWorker> getRegistration(optional DOMString name = "");
}
interface GeolocationWorker : EventTarget {
readonly attribute DOMString name;
attribute EventHandler? onleave;
attribute EventHandler? onleave;
} Arguably, the The advantage of an approach like this is that the worker itself determines what it needs. That provides content authors better encapsulation. I think that this also provides the browser with more determinism regarding what events need to hit the service worker. If the set of event registrations has to be complete by the time that I can imagine surfacing information on |
My vision has been to not have the SW "filter" events. Instead, at the time when you register for some particular feature, you indicate which events you want fired, and which SW to fire the events at. So Likewise And So no filtering is happening. And each API can have entirely different patterns of events that are fired. "scope" is really just a "set of URLs in which to invoke the http-intercept feature". Technically we could call I think the main difference between our proposals is that I've stuck the registration APIs on the |
I like it! |
Do you have any examples of this?
So now I can have multiple registrations claiming the same scopes? What happens then?
Nah, you can't register for these things within the serviceworker, they need to be done from the window so we have somewhere relevant to show permission dialogs. Also, you're adding listeners within the install event, those will be lost once the serviceworker terminates, you'll most likely never get any geo events. Scope isn't just about fetch interception, it's part of the lifecycle, it's how we control upgrades. If we see sites wanting '/blah/' and '/whatever/' to be controlled by the same worker, but a scope of '/' doesn't work for them, we can look at ways of expanding 'scope' to an array while retaining it's primary key nature. The proposals here feel like huge changes & complication for very little benefit. |
Not off the top of my head, but I'd be shocked if this isn't common among sites that host multiple "apps" on the same domain. I.e. among sites that need scopes at all. But I'll look for examples.
How so? I.e. how does the scope affect the lifecycle and upgrades? Not doubting you, I might very well be missing pieces. I agree that it's a big change. Though with the proposed sugar it comes out as very small code changes for the website. But I think the spec right now is making a very big assumption that websites are generally structured such that each "app" has its own directory. If we're betting wrong on that the result will likely be that websites won't work as well offline, and will be slower online, which is our main goal to avoid. |
We don't promote a "waiting" worker to "active" until all clients have disconnected. Clients are windows that have opted to use a registration as their controller, which is done based on scope. Also installEvent.replace() makes all within-scope clients use the registration as their controller. I guess the clients API itself is also very scope-driven. |
Yes, browser filters, not the SW. I apologize for accidentally implying otherwise.
Is the intent here to ask a series of questions? "Do you want this site to be available offline?" "Do you want this site to be able to track your location when you aren't visiting it?" "Do you want to receive notifications from this site?" Having had more time to think about it, I think that my own lack of understanding about the lifecycle made my alternative seem plausible, but it really isn't. The fact that scopes are still special bothers me some, but not enough to get excited about. |
We're not changing the permission model of the web here, the idea is to request permission at a moment that makes sense to the user, either after an interaction "enable push messaging", or without interaction if the intent is clear to the user, eg gmaps asking for location permission. |
https://www.google.com/calendar. :) It accesses www.google.com/csi among other things. |
FWIW I like @sicking's proposal here too. |
What is http://www.google.com/csi? It doesn't appear to be another place /calendar/ lives (it's an empty page for me).
But liking it isn't enough. The scope is more than just for determining which fetches are captured (see my comment above). Closing this. Feel free to reopen if there's a desire (and a solution) to separating fetch without breaking the other things that depend on scope. |
Client Side Instrumentation - it's not serving anything, just a place to report to about e.g. how long it took to load the page, how long to render some component, etc. |
Ah ok, not a relevant example then. |
Assuming that nothing has changed as the result of the discussion => will be adding no impact label. |
Here is another one: http://www.bbc.co.uk/persian/. It loads content from /persian, /worldservice, etc. I am really surprised that it's hard for you to believe that there are web properties that live on multiple URL paths. Can you please reopen the issue? I don't think that we have addressed it at all, and I don't seem to have access to reopen it myself. |
You're misunderstanding what @sicking was referring to. He was talking about a web property that exists across multiple paths where a origin-scoped serviceworker would be harmful. http://www.bbc.co.uk/persian/ loads content from /worldservice, yes, but it's static assets. That's not the point. https://jakearchibald.github.io/trained-to-thrill/ loads content from flickr, doesn't mean they should share a serviceworker.
Yet everyone here has been unable to come up with an example. I'm not saying it doesn't happen, but I'd like to see a site that would have this problem before we consider solving it. Also, this can be solved with less severe changes such as allowing scope to be an array. Have reopened, but will close again soon if there isn't a good example. |
My problem with the API remains that selection is based on concepts that aren't universally applicable. Web Push doesn't need to concern itself with scope of control in the same way that fetch might. Nor does geolocation. The geolocation case at least benefits from access to a parallel means of determining what events a SW is configured to handle, but using that same mechanism as a basis for SW selection would not work. (I do ultimately think a similar event determination process applies to push as well, but that group has decided to simplify so much that that isn't really an option.) It's that conflation that is the issue here, not the multiple scopes thing, which is a separable issue. I'm happy to defer to others on this. Though I'll point that you are creating a forcing function by limiting scopes this way, which I don't think is wise. If the part of the web you look at happens to form into neat compartments, that's great, but I've learned not to make assumptions along those lines. |
@jakearchibald I pinged you on IRC to get a better understanding of what examples you find acceptable. My basic assumption is that a good example would be an application living under the same origin with other independent applications across multiple URL scopes. If you're looking for something else, please be more specific. Here is another example: https://www.facebook.com/events. The UI to create a new event loads from https://www.facebook.com/ajax/plans/create/dialog.php. Browsing Facebook under the network panel in the Firefox devtools, it seems like /ajax is the URL scope for several helper scripts, such as this one. |
I don't think that example is a problem either. /events/ requests data from /ajax/ - that's fine. You can think of /ajax/ Compare to my trains demo again. I request data from Flickr but my app What Jonas is talking about is an app that doesn't have a root path. The Maybe that's something we should be worrying about, but I'd like to see a
|
The problem statement as such is tortured. Many plugin features benefit from scoping in the same way that interception does. Won'tfixing this unless we get a better argument. |
What plugin features benefit from scoping in this manner? These do not:
It does seem like a better design to install a service worker for an origin and then associate scope(s) with it. That way we also solve the issue with whether or not we dispatch events inside the service worker. Adding scopes is opting into the fetch event. Registering for plugin features opts into their events. These seems like a vastly better design and the changes for everyone involved are minimal. |
@ehsan : your example doesn't fit. It's not the same app (as covered at some length in my previous comment). Without compelling data, this change appears to be motivated entirely be esthetic concerns, to which, at this late date, I'm not inclined to bend. If we don't get better data or arguments in the next day or so, I'm going to wontfix this with prejudice. To your last comment, this isn't about a willingness to change the design. It's about a willingness to change the design without a compelling argument. |
Well, we did list a number of arguments why we wanted to change this and we do think they are compelling enough to change the design.
We listed these in the second comment of this issue. We are unlikely to agree to the current approach. |
@annevk: I appreciate that you have become convinced that this is worth making a breaking change for. Others are not convinced (myself included), not least of all because every time we poke at the evidence for the request, there's clearly a way to handle it without making this change (e.g., a global registration that delegates). I'll take your points in order, though, and appreciate you summarizing.
Regards |
Drive-by comment: One thing that confused the hell out of me reading this thread was Alex's seeming dodging of the issue of "but why do you need a scope to listen to geofence events?". I had assumed that a scope's only purpose was to specify which fetches you want to intercept; that is, it was only relevant for a fetch-intercepting SW, not any of the others. Alex clarified to me privately just now that it's more meaningful than that - for anything that might need a permission grant, you need to make the request within a page, not in the SW, and the scope gives you a page->SW mapping, so you can tell which SW the permission grant is applying to. This is only necessary because of the "let's pretend that multiple apps on the same origin aren't a terrible, terrible idea" thing that we're doing for convenience. If it was one-app-per-origin (and if you really want to do multiple, you can handle negotiation and dispatch yourself), then we wouldn't need a scope unless you really were intercepting fetches. If anyone else was confused about this, hopefully this helps. |
Right, what @tabatkins said. This is a debate about what is an app. Apps are what request permissions and where events for things need to be directed back to. Scoping as it currently stands ergonomically connects the surface that requests a service to the handler for the service (the SW script & version). A SW is the service bus for an app. In general, apps are single origins. If we're going to make any change here, I'd rather we remove scopes entirely in the short run and see how far we get. The advice is already going to be that sites shouldn't host more than one app. Scopes as spec'd so far are only a way to deal with some painful corner cases. Imbuing them with more meaning is ok, but only to the extent that we don't break the notion that a SW registration represents a conceptual app. Honestly do what to get to a resolution here. And I'm grateful to @tabatkins for pulling the assumed context out of my head. |
+1! Combined with a resolution for
I would be very happy. |
@tabatkins wrote:
Technically, permissions are still granted to origins, not to SWs. The relevance of scopes to other APIs is that other APIs might want to associate themselves with the active SW for the current page's scope. For example for Push, calling navigator.push.register used to implicitly tie the push registration to whatever the active page's SW was (via scope lookup). In the end we decided to make it explicit instead by moving the PushManager onto ServiceWorkerRegistration instances. @annevk wrote:
That's already true: permissions are granted to origins, so you can unregister a SW, then register a replacement SW, and re-register for push/notifications without any permission prompts. |
@slightlyoff In the Mozilla MarketPlace for Firefox OS, we have also limited each app to its own origin, and we have received a ton of negative feedback from developers on that decision. So I think your assumption that all apps are only hosted in one origin is incorrect. I am worried that with the current design, apps that do not adhere to your suggestion will have difficulty for adopting service workers. |
As a data point: trained-to-thrill, and isserviceworkerready both live on the same origin (jakearchibald.github.io) and make use of scoping to work independently. |
Sure, but my point is, not all apps are written in the future and according to the assumption that the SW spec is making. |
Right, that's what I meant, sorry for implying otherwise. |
I would be super happy with one service worker per origin and using a potential future suborigins to solve the scenario of having more. Combined with a way to opt into |
Ok, so there's a couple of things people have asked for, so I'll try to address each one separately. Examples of websites that need thisThis is what has kept me the longest. I hope to get more examples from other mozilla teams, but in the meantime I only have one example which is google maps. Google maps is currently hosted under https://www.google.com/maps, however preferences for maps are located under https://www.google.com/preferences. (Help and history for maps are also located outside of /maps. But they are on entirely separate origins which means that there's little we can do to handle them in the same SW). We certainly could ask maps to rewrite their URL structures, but that means breaking any links/bookmarks. Probably not something that they are willing to do. They could set up a redirector, but that redirector would need to have its own SW if you want users to be able to use maps offline. Also, redirectors of course cost performance, no matter if SW are involved or not. As for moving content around so that there's "one app per origin", does anyone here really expect the various "apps" hosted under www.google.com to do that? I.e. does anyone here really expect all apps except search to move to other domains? Scopes are used for updatesIt's certainly true that the spec does its best to ensure that a SW never "gets stuck" and doesn't get updated. However as far as I can tell it only succeeds in doing that for http-intercepting SW. If a website doesn't want to use the http-intercept feature and just wants to use SW to receive push messages, it could very well use "/whatever/foopy/garbage" as scope. There's nothing ensuring that the SW does get updated. However I agree that there's nothing in my proposal that address when we should check for updates of a scope-less SW. What seems most simple to do is to define that if a SW doesn't have a scope, that we check for updates any time the user visits any URL from that origin. Scopes are useful for other features tooThis keeps being claimed, but I just don't see it. The way that you register, for example, push messages is that you grab a The only thing that's different is that if you're in scope of the I.e. the difference is just 12 characters, and the fact that Interaction with appsSomething else that we haven't talked about so far is the fact that the plan is that service workers is going to play an even more important role once we integrate manifests and service workers. My hope had been that once the user performs a "bookmark to homescreen" action for a page with a manifest, that the manifest will point to a SW which is installed and given special status. I.e. once the user creates an "app" using a manifest, that information in the manifest enables us to register a SW which is then given a special status within that "app". For example the SW will receive update checks even if the user doesn't run the app, we will give that SW unlimited amounts of storage, enable it to register push registrations without a prompt, etc. However with the limitation that SWs can only handle a single URL prefix, will this lead to that the app can only handle one URL prefix? Or at least do so well? Another way to look at this is that people are constantly up against the same-origin limitation. What's effectively a single website is often spread out over multiple servers. We're making this issue worse by forcing people to not just stay within a server, but within one directory. Proposed solution Alt 1This is essentially the same API that was proposed in my first comment in this issue, with some minor tweaks.
This means that for the common use-case of "service worker services whole origin", the two differences in code will be:
See also the mainly unrelated issue #534 that should make this difference be even smaller. So to address the cases where we use scope that @jakearchibald mention in #445 (comment)
This continues unchanged.
I would actually argue that this part remains unchanged as well. A SW shouldn't actually become active "when all tabs within a scope are gone", but rather "when the SW is no longer the So effectively a SW that doesn't have a scope is never prevented from becoming active.
I agree that my initial proposal didn't address this. For Service Workers with a scope this remains unchanged. I.e. as soon as the user visits any page within the SW's scope we check for updates. For SWs without a scope we check for update any time the user visits a page from the SW's origin. That leaves one problem that I can't think of a good solution for still. Which is what to do if two SWs claim the same scope. Hopefully this should be really rare given that it's effectively a developer bug. I'd say that we reject the setScope call (or registration if it happens as part of registration) and put an error in the developer console. Alternatively we can let the registration go through in the hope that the scope of the other SW will be reduced shortly after, still with plenty of warnings in the developer console. Proposed solution Alt 2This proposal is trying to solve fewer of the problems mentioned in #445 (comment). It focuses only on solving the multi-directory problem. (You could say that this proposal is.. narrower in scope. Aw yeah) This proposal still forces all service workers to have a scope, just like the spec does today. However it stops using the scope as identifier for the service worker which means that the scope can be modified without creating a new registration. The only changes compared to what we have now are
This means that we still have to do the trick with looking for event handlers for "fetch" events during SW installation (Sorry Anne). And we'd still have to handle overlapping scopes. Same proposed solution as for Alt 1. |
@sicking - regarding the "Examples of websites that need this" section, I am not sure the /preferences example is relevant. Unless I am mistaken, the preferences scope (page, or section, or whatever) seems to be a different application (Google Web Search, or Google Search Preferences). If you look at the preferences it provides, they are actually related to search in general and are not specific to maps. |
The reason I like having one service worker per origin is that it simplifies the model and aligns the actual security guarantees. E.g. cookies have this |
Those aren't really preferences for maps, but preferences for the whole of google.com.
Agreed. We really need scoping.
I'm pretty happy with #514 (comment). Updating on navigation works well, it's a great indicator of usage. Checking for updates when the SW spins up & hasn't been checked within 24hrs stops the SW going stale if it's only used for push etc etc. If a SW isn't used (no visits within scope, no push message), we're not wasting data/cpu checking for updates. I agree it should be in the developers power to check for updates per received push message, I'll pick this up in #534. To be specific: We've fixed the bits that make updates reliant on scopes.
If that makes them think "hmm, maybe I shouldn't hack this, and instead make the result of my push message work (and fast) despite connectivity" then I'm pretty happy that we've created this hoop to jump through.
These aren't equivalent,
I don't think I really get this. If I add-to-homescreen jakearchibald.com/space-game and jakearchibald.com/drawing-app, each can have its own SW with different scopes. If I open space-game and navigate within its scope via links, I'd expect that to happen within the same window. If I open space-game and navigate to a url in drawing-app's scope, I'd expect the OS to transition from one "app" to the other as it would with native apps. If I open space-game and navigate to a url that isn't in scope of an added-to-homescreen app, I'd expect it to launch the browser app for it. Scopes are really useful here.
This is the currently specced behaviour, my wording was off.
That's fair (it should wait until any
Does this work for Mozilla? If so I'd like to give it more thought. Maybe |
I am convinced by @slightlyoff that we should not pretend there are boundaries where there are none. And that therefore origin-bound SWs make the most sense. To get boundaries within an origin, suborigins seem promising. To address complex cross-origin structures such as Wikipedia we've yet to come up with something. I would vastly prefer origin-bound SWs over Alt 2 + nullable scope. |
So, Jonas suggests in emails that, while the permission request is made from a page, it's made using a specific Service Worker object already, so there's no need for the url -> SW mapping that scope provides. Thoughts? |
Sure. But the relevant question is: If you were using offline maps, would you expect to be able to click the "settings" button in the maps UI and then configure your home address? And if maps was updated to gain more preferences, would you expect that preference UI to be updated atomically together with the maps UI which takes advantage of those preferences? I'm not saying that all of /preferences should be handled by the maps app, but it seems sensible that ones that are specifically for maps are. See also below regarding UI.
Yup. Sounds good. I left some comments in #514, but they are just minor tweaks. I agree that this part is solved. Mainly we need to make the agreed stuff normative in spec.
Agreed. I'm definitely not arguing that scopes aren't useful. Quite the opposite. I agree with all of the flows that you mention above. So a question is what you expect to happen in the maps example? Would you expect that clicking "settings" in google maps would bring you to the browser to set your home address, or would you expect to remain inside of the maps app? Likewise for @LarsKemmann's example. Would users expect the UI for I agree that in that case you wouldn't want the SW for
If we agree that "scope less" workers don't pose a problem for updates, then maybe we can default the scope to "/" but allow it to be explicitly set to null. That way I think we still solve the same problems as Alt 1, including no longer needing the look-for-eventhandlers pattern. |
I think it'd launch a separate preferences app, since Google has bundled all preference into one app. As a user flow, it isn't a disaster, but hopefully it'd persuade Google to make their preferences more contextual.
@annevk is. Can you two fight it out? Let us know what bits Mozilla is blocking on. |
I think that the use of scopes was covering a several somewhat distinct things:
I've always regarded the multiple scope thing as orthogonal to the above. I do think that it's a virtue of @sicking's proposal that it's moved from exactly one scope to any number, but I do want to ensure that this separation is precise. |
Let me try to present the case for a scopeless version of SW. The API would look something like this:
No need for naming, no need for scopes. This does make it harder for It is hard to quantify now, but I think just as with cookies, we'll find that scoping lacking an actual boundary is problematic and wish we had designed it differently. And if it turns out that once deployed this is not working, it will be easy to add some kind of scoping without security boundary later through an opt-in API. If @slightlyoff is no longer interested in something like this I guess I'll cast my vote for Alt #2 + nullable scopes, but I thought it was worth a try. |
And github pages, and @LarsKemmann's example. I don't think pretending this is a Google-only problem is helpful. |
@jsbell mentioned yesterday that he suspected that the reason Google Maps If that's the case then it having google use suborigins to enable separate / Jonas |
Issue #468 is a perfect example why we need actual security boundaries here. Everyone tries to come up with ways to have As for I don't think demos on GitHub should trump actual security issues. |
This is now #566. |
Apologies if this was raised previously. I couldn't see anything discussion on the topic, though I'm told that at least some people have been thinking about this already.
One thing I get reading the service worker spec is the sense that the first use case (offline) dominates the form of the API.
The scope attribute is the only real problem in this regard. ServiceWorkerContainer.register takes the RegistrationOptionList, which currently only has a single argument:
Limiting the scope (in the more general sense) of a registration seems to be the right idea. However, ad hoc limiting based on a bag of arguments with different purposes doesn't seem like a good way to build the basis of a new generic platform feature. A more modular approach might ensure service workers are suited to use for offline apps, web push, geolocation and other features.
I have a few ideas. @sicking tells me he has some thoughts too. I'll share mine in separate comments.
The text was updated successfully, but these errors were encountered: