-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
Proposal: beforePutToBFcache
and afterRestoreFromBFcache
events for DedicatedWorkerGlobalScope
#7216
Comments
This is unfortunately not true in the spec and in Firefox. As discussed in #6379, shared workers can own dedicated workers, and shared workers have no clear owner document. We might just say that such shared-worker-owned dedicated workers are out of scope for this proposal, but if so we'd need to be explicit, and ensure that that doesn't have any bad impacts. In particular it seems like some of your "non-goals" section is based specifically on this assumption, so might need to be re-thought.
Could you be a bit more concrete about what you are proposing? In particular since they are in different threads I don't think we can guarantee any order. I guess we would post a task from the main thread into the worker thread that fires the event? Is there any ordering with relation to other interesting worker lifecycle events, or posted messages? |
Thanks!
Would it makes sense to fire the events in a dedicated worker only when
?
Yes, I thought the events are fired from tasks that are posted from the main thread. I don't think there are any other lifecycle events in workers so far. So probably would the sentence "the events are fired from tasks that are ported from the main thread when |
It feels weird to me for the events to be tied to the existence of a document owner (direct or indirect). I see why "persisted" would only be true if there is a doc owner, but wouldn't we want to always fire them? Also, would these be fired for worker.terminate() or if the worker script calls self.close()? |
This doesn't work as stated because a dedicated worker can be owned by a dedicated worker which can be owned by a shared worker. But I assume you are trying to go for a scenario where there are no shared workers in the ancestor chain, which would work. It's just a little weird as @wanderview calls out.
No events, but there could be posted tasks. E.g. consider worker.postMessage("1");
window.onbeforeunload = () => worker.postMessage("2");
window.onpagehide = () => worker.postMessage("3");
window.onunload = () => worker.postMessage("4");
causePageToUnload(); Is there any ordering guarantee of the pagehide event inside the worker, versus the worker receiving messages 1-4? |
Do you mean that
I don't think the events should be called in this case, as those are not related to a document's lifecycle.
Ah yes, that's what I intended. The events are fired for a dedicated worker only when there is no shared worker in its ancestor chain.
I have never thought that... Hmm I feel like we should guarantee but I'm not familiar with |
Right.
It seems to me events fired in a worker should relate to the worker lifecycle, not to an owner that may or may not be present. Otherwise you cannot write worker script code that relies on these events without assumptions about who owns the worker. At the core we are adding freeze/thaw type concepts to the worker lifecycle. Workers already have the lifecycle concept of "creation" and "destruction". Do those not map to "pageshow" and "pagehide" here? If we aren't building worker lifecycle events, but just proxing document events into the worker, then what is the benefit of the platform providing that vs userland using postMessage() themselves? |
I think it's OK to fire events in a worker related to something else's lifecycle, especially if they are clearly named as such. The
I believe the proposal in the OP is indeed just proxying. It is indeed a good question why postMessage() doesn't work. My guess is because it creates a coordination problem where you need the page author to do the proxying, which makes it hard to rely on in reusable libraries or similar that want to work in a worker. But I'd love to hear more from @hajimehoshi in that regard. |
The benefit of Note that the navigation itself can be done immediately regardless of the decision of whether the page is cached or not. |
@domenic Ping |
Yes, this makes sense. |
So, with this proposal, we are just proxying the message from document events to workers for I'll update the proposal to make this point explicit. |
Updated the explainer. Please take a look. |
Understood. I guess it still feels weird to me that we don't have a "context closing" event for workers in general, but we do if the workers just happen to be owned by a document. I don't feel strongly enough to argue the point, though. So no objections from me. |
It seems like the introduction of a ServiceWorkers do provide a precedent for letting content continue to run JS after the user has navigated away, but arguably in that case maybe the relevant app logic should just be using a ServiceWorker in which case it wouldn't be under any time pressure to drop IDB connections, etc.? Maybe it's different for other browsers, but when Firefox freezes a page, the worker is interrupted mid-JS-execution and all content execution stops until thawed. This is based on the same mechanism for worker termination. |
For a worker not running a long task, giving the worker the chance to release resources that would block BFCaching is a win. For a worker running a long task that would block timely execution of pagehide, we need to consider 2 cases
So 2b here is tricky. Can we just freeze it anyway and let the pagehide run after it comes back out of BFCache and completes the long tasks? I can't think of a reason why that would be a problem. It seems odd to run pagehide after freezing and unfreezing but the worker won't know. |
To be clear, I'm on board with the potential benefits of letting workers clean up. My concern is how long the window for "timely execution" has to be before we start avoiding case 2b and what the performance implications of granting this grace period to every dedicated worker will be. Presumably the page the user is navigating to would benefit from having those resources for itself! If we're regularly going to be sending too-late pagehide events for non-idle Workers that aren't written to run in very tiny time-slices, maybe it would be better for the worker constructor to gain an option like Alternately, I suppose the spec could be written so that browsers could immediately freeze all the workers until the navigated page is sufficiently loaded and the latency pressure is off from their perspective. Then the browser could thaw the workers on its own schedule and give each a longer opportunity to get to process the |
Thinking about the long-task-worker problem, maybe instead of pagehide/pageshow, we should be doing prepareforbfcache/resumeafterbfcache (ignore the terrible names). We would only send them if the worker is blocking BFCache. If the worker is not blocking BFCache, we can just immediately freeze it. This removes 2 problems
It does not solve the fact that in 1 above we would still allow a worker to consume CPU for some grace period (but that's a problem with pagehide/pageshow too). I'm not sure that we need to spec the ordering of things. Does the current spec demand that the workers be frozen before the next page starts executing? I can imagine problems here if the user navigates to another page on the same origin and the worker is writing to shared storage but I think those problems already exist with freezing workers and resuming them after BFCache. |
There is a grace period for shared and service workers and I suppose that could be extended to dedicated workers, though given how prevalent they are that does seem worrisome. And if they have a long-running task, how much is a couple extra seconds going to help? One thing I wanted to ask is that when designing this feature we also keep shared and service workers in mind so that the solution can scale to them (as as well as any dedicated workers they might instantiate). |
@annevk can you elaborate on the grace period for shared and service workers? What are you waiting for during that grace period? Right now Chrome allows pages with service workers into the cache, it does not signal anything to the shared worker. If the worker asks for clients, it will not be told that the page exists and if it already had a handle to the page and tried to send it a message, it will be evicted. |
@fergald I meant that unrelated to bfcache shared/service workers get to exist beyond the lifetime of a document for x seconds, in the event that another document appears for which they can also be used. (Firefox's implementation of these things is a bit in flux still due to site isolation. Firefox also evicts bfcache documents that receive a message.) |
So, are we fine to have dedicated events for bfcache-features rather than pagehide/pageshow, like "beingPutToBFcache" "restoredFromBfcache"? As service workers don't prevent pages from being cached to BFcache (at least in Chrome), these new events are not needed for service workers. I'm not sure whether shared workers should be able to treat the events. Now pages using share workers are not cached (at least in Chrome). Should shared workers' feature usage (like IndexedDB) affect the page's eligibility for BFcache in the future? When should a shared worker be frozen? As there will be a lot of discussions about them, I'd like shared workers out of scope from my proposal. |
My concern isn't so much about the name, but the steps related to dispatching "pagehide" or "beingPutToBFcache". Maybe it would be good to sketch what the spec algorithm would be for dispatching the event?
Yes, it seems like ServiceWorkers would not be involved with the event.
Firefox freezes a shared worker when all of the documents in its owner set are frozen. It seems reasonable to me that a SharedWorker would receive the event when there is only one unfrozen document in its owner set and that owner is moving to be frozen. Should a frozen document in bfcache be messaged by the unfrozen SharedWorker, the document will be removed from bfcache/discarded. In Firefox (and presumably any multi-process browsers), everything involving SharedWorkers is inherently async, but should roughly look like the async handling of a dedicated worker, so I think it would be appropriate to consider SharedWorkers at the same time. |
Thanks (and sorry for terribly late reply).
I see, considering shared at the same time makes sense. Before updating the explainer, we have to consider how Chrome/Chromium caches pages with shared workers... CC @fergald |
@asutherland the distinction between "pagehide" or "beingPutToBFcache" is not just in the name. "beingPutToBFcache" means that we wouldn't fire these on first pageshow and we wouldn't fire them on pagehide if the page is not going into BFCache. They stop being about what the page is doing and instead are about what's about to happen to the worker (which is driven by what the page is doing but the distinction is far more important for shared workers). As for shared workers in Chrome, we don't cache them currently. They account for less than .01% (1/1000) of reasons we didn't use BFCache (about half of those are blocked by something else too). Handling them would be complex, so I doubt Chrome will ever implement support for bfcaching them. Webkit removed them, so I expect only FF has them as a practical concern. I do agree that we should sketch out the dispatch algorithm in the explainer but unless you think we are going to discover a reason to change approach entirely, I'd like to keep the scope to dedicated workers, unless someone from FF wants to collaborate. If firing an event in a shared worker is not going to work, I don't know what would, so I don't think we need to get the shared worker story correct before making progress on dedicated workers. |
I support the refined semantics for "beingPutToBFCache". And thank you for the explicit restatement of the semantics; those make sense and I benefited from the clarity. In terms of the SharedWorker and the event, it sounds like we all agree that the event would work for the SharedWorker if we wanted to generalize to that/support that, and that's mainly what I wanted consensus on. Given that only Firefox would support BFCaching of documents using SharedWorkers, I think it likely makes sense to just specify that SharedWorkers make a document ineligible for BFCaching in the interest of webcompat. We can always relax that in the future should there be interest in implementing it across browsers and a belief that it would meaningfully improve successful bfcaching. And in that case the explainer and subsequently spec only need to deal with dedicated workers. I very much look forward to the next steps of this, thank you! |
pagehide
and pageshow
events for DedicatedWorkerGlobalScopebeforePutToBFcache
and afterRestoreFromBFcache
events for DedicatedWorkerGlobalScope
I've updated the explainer based on the discussions. I've not come up with better event name... Please take a look, thanks! |
As there seem no objections against my proposal (except for the name?), I'll make a proposal for the spec. Thank you very much! |
What about Safari? Does anyone have insights? |
Now I'm trying to find how to patch the current spec (whatwg), and I'd appreciate if you could give insights. My idea is:
Thanks, |
I don't really understand why there are so many "mays". It sounds like that makes these events completely unreliable, and e.g. a browser that never sends them would be compliant with the spec. That doesn't seem like a useful feature to me. We would also be able to write zero web platform tests. The original semantics as I understood it was that, whenever we would send pagehide with persisted = true to a document, we would send this new event to all dedicated workers owned by the document. I think we should be able to queue such an event regardless of blocking tasks or optimizations, so that we can have a rigorous and testable feature. |
When there is a long-running task in a dedicated worker, the browser might decided not to put the page into the cache without sending the event to dedicated workers. So I used the word 'may'. I'm not sure we should have to use a 'queue' model. If the page decides not to put the page into back/forward cache, the queue is cleared in order not to send the events, right? As back/forward cache timings depends on browsers, the browser may not put the page into the cache and may clear the queue, but is this better than my suggestion (#7216 (comment))? |
Does it make sense for a browser that doesn't support BFCache to send these anyway? Could be a "yes". I was going to say it could also be a "realistically there is no such browser anymore" but I'm not sure whether Edge has enabled BFCache for real yet and it seems like FF's current strategy is to non-cooperatively freeze tasks (not sure about Safari) meaning, so they would be out of spec until they decide to enable this optimisation. Does it make sense to send this event to a worker after we have already been into and back out of BFCache? Maybe for consistency it does. If you think the answer to those is "yes" then I think it needs a clear explanation (that we should add to the explainer). I'm not sure I'm convinced on the testing argument. If you think of this as an optional feature, then we can write tests for Chrome that will show on WPT that we support it (and in our own CI we will fail if suddenly support is dropped) and other browsers will show in WPT that they do not support this optional feature. It is frustrating that BFCache is introducing a lot of optionality when there was very little in WP before but I'm not sure that that's wrong.
I think the original semantics didn't really account for the problem of long-running tasks in workers. |
OK, so the idea is:
and maybe a reason for ending up in case (2) is because of a long blocking event in workers. Is that correct? If so that sounds pretty good. We can still write tests in the "if-then" format, i.e. if
Yes, if you dispose of the document, then you also shut down all of its dedicated workers, and it's possible the event won't be fired, even though it is queued.
No, that wasn't what I was trying to imply. If you are in case (2) then there is no need to send the event.
Interesting case. So the sequence is:
Can we do a cross-thread check in this last step to see if the document is in bfcache or not? If so, then yeah, maybe avoiding firing the event would make sense in such cases. |
That description is a little off, I think. Chrome's v1 of supporting dedicated workers is going to be the same as FF (freeze workers mid-task, don't BFCache if they are doing something that blocks BFCache). Chrome's v2 would add the new event but still immediately freeze workers that are not blocking BFCache. So it would look like for worker in page.workers:
if HasBlockingResources(worker):
# Hopefully the event will be delivered to the worker and it will release resources.
QueueEventAndCooperativelyFreeze(worker)
else:
# Stop it using CPU right now, making the navigation to the new page smoother and giving us a better chance
# of releasing resources in other workers
ImmediatelyFreeze(worker)
wait(SOME_TIMEOUT)
for worker in page.workers:
if HasBlockingResources(worker):
# The task still has blocking resources.
MarkNonSalvageable(page)
if not IsFrozen(worker):
# The task did not execute the event handler and freeze within the timeout.
MarkNonSalvageable(page) I think this allows for best caching and user experience but should we should spec that? FF and Safari don't currently do this and may not ever add the event. Safari currently has a bug where it doesn't freeze long-running workers (bug filed) and it looks like the intention is to just not cache (at least as the immediate fix). @cdumez. The simplest way to express that FF, Safari and Chrome are all in spec seemed to be to make queuing the event optional which also covers the case of not running it in workers that get frozen immediately.
If you think we should spec it so that we say that we always queue the event but check and cancel if the page is active, that's fine. I don't think that's how we would implement it (since we know in advance whether we will cancel it and also, that cross-thread check ). The experience for devs is the same. |
I'm +1 on @fergald's pseudo-code in #7216 (comment) and this seems like something Firefox could/would implement. One necessary issue to address is how to deal with nested workers. In Firefox we're currently at a point where we may be refactoring aspects of how nested workers work, so we could potentially do something where the algorithm is already able to directly communicate to the nested workers, but currently I would expect that this algorithm would need to run in turn on the workers themselves to in turn message their owned workers. moot eventsI think it would be desirable for the event to be canceled if it becomes moot by the document coming out of bfcache. I do expect that we/Firefox would update an Atomics-style synchronized variable on the Worker binding on the main thread which the task on the worker to dispatch the event would check before actually dispatching the event. Which is mainly to say, I think we'd want to avoid having the task on the worker be able to directly have the ability to synchronously know if the document is in the bfcache directly; instead this is something its owning main-thread Worker binding would have a potentially-more-up-to-date understanding of this state than sequential task ordering allows, but the algorithm running on the worker would not be able to do anything on the main thread, just read this cached value. (And this could potentially allow for a case where an atomic is not used to convey the state, but instead conveyed via a high priority separate task queue.) I think this could also apply to |
Your pseudocode is really helpful in clarifying things, and it's good to hear that @asutherland likes it too. I wonder if it'd be worth including such pseudocode in the spec as a non-normative example, if nothing else, just because it clarifies the design goal a good deal... I agree with the general tension between allowing browsers the flexibility to do different things in these bfcache cases, versus wanting to spec something interoperable and which could be tested in a reasonable fashion. It's a hard balance. In particular it sounds like if the intent is to not fire the event for workers without blocking resources, but instead freeze them immediately with no event, then we can't really specify anything rigorous or testable. (Unless we specify "blocking resources", which is probably not worth doing?) I'm still trying to tease out whether we can have anything more rigorous/testable than just making the event optional. If we have to end up there, then that's OK. But it still seems like we could at least have some negative invariants expressed in the spec, such as:
Can you think of any other such invariants, either negative or positive? At this point I think we might be ready for spec text though, as it's getting pretty concrete and I think I understand the constraints. |
Maybe we could provide insight and make things testable by:
|
Chrome has finished rolling out Dedicated Worker support for BFCache where we just freeze the workers. What we see is that DW now blocks about .01% of eligible navigations. These could be fixed with the event above in this explainer, however given how small this is, our priority for speccing and implementing this is going to be quite low unless we find another strong reason to push it forward. |
Explainer:
beforePutToBFcache
andafterRestoreFromBFcache
events for DedicatedWorkerGlobalScopeAuthors
@hajimehoshi
Introduction
Today, browsers use an optimization feature when users navigate their browser’s history, called Back-Forward Cache (a.k.a BFCache). BFcache enables instant loading experience when users go back to a page they have recently visited.
Not every page can use this optimization. Different browsers have different heuristics that opt the pages out of BFCache when certain features are used by the web page. This feature detection also happens not only in documents but also in Web workers - so if a worker is using a feature that is not compatible with BFCache, the document might not be able to get BFCached.
In a document, web authors can listen for
pagehide
andpageshow
events. These arewindow
's events.pagehide
is fired when a navigation happens but before the decision is made whether the page is put in BFcache.pageshow
is fired when the page is restored from BFcache by a history navigation.pagehide
gives web authors the opportunity to handle features that can affect BFcache. For example, a page with an IndexedDB connection might not be eligible for BFcache in some browsers. In this case, by disconnecting the connection atpagehide
, the page can likely be put into BFcache in the browsers. They also can reconnect the connection atpageshow
.In a dedicated worker, there is no way to handle such lifecycle changes so far.
This means that it can be difficult to cache pages with a dedicated worker.
However, it is not feasible to add
pagehide
andpageshow
events to DedicatedWorkerGlobalScope.As a dedicated worker works in a different thread, the semantics of page lifecycle like
pagehide
andpageshow
doesn't match with dedicated workers.For example, if a dedicated worker's task lives very long, events like
pagehide
andpageshow
might be fired during the worker's task.In this case, it would be semantically incorrect if
pagehide
andpageshow
are fired after the long task.To improve this situation, this proposes to add
beforePutToBFcache
andafterRestoreFromBFcache
events to DedicatedWorkerGlobalScope.beforePutToBFcache
is fired when the browser makes a decision whether the page should be put into BFcache.afterRestoreFromBFcache
is fired after the browser restores the page from BFcache.Goals
This proposes to add new events
beforePutToBFcache
andafterRestoreFromBFcache
to DedicatedWorkerGlobalScope, which gives web authors chances to observe the timing when the associated document is being put into BFcache and react to it.These events are fired only when the dedicated worker doesn't have a shared worker or a service worker in its ancestor chain.
Non-goals
This doesn't propose to add the events to all the workers or the worklets other than a dedicated worker.
A dedicated worker, which doesn't have a shared worker or a service worker in its ancestor chain, belongs to one document so it is natural to handle a document lifecycle events in a dedicated worker. However,
API
The event
beforePutToBFcache
is dispatched when the page is navigated out and before unloading.This event is dispatched before the decision is made whether the page is put into BFcache.
This is similar to the
window
'spagehide
, but is different.When the browser gives up putting the page into BFcache, the event is not fired.
For example, if a dedicated worker's task takes very long, the browser might give up using BFcache.
The event
afterRestoreFromBFcache
is dispatched when the page is restored from BFcache by a history navigation.This is similar to
window
'safterRestoreFromBFcache
, but is different.The event type is
PageTransitionEvent
.The
persisted
read-only member is always true.Example
Discussion
Why not
postMessage
from a frame?Browser side can determine whether the page is cached or not after all the
beforePutToBFcache
andafterRestoreFromBFcache
events are handled.postMessage
just notifies the events to dedicated workers asynchronoucly, and browser cannot wait for theirpostMessage
handlings.It is impossible to do such determination with
postMessage
.Note that navigation itself happens immediately regardless of whether the page is cached or not.
References
/CC @domenic @nhiroki @rakina @fergald
The text was updated successfully, but these errors were encountered: