-
Notifications
You must be signed in to change notification settings - Fork 28
Description
We've started implementing #32 in Chromium. (See especially #32 (comment) for the latest status.)
However, we've run into a small snag, which is that allowing such interception can interfere with performance. To explain this we need a bit of background about how traversal works today in terms of inter-process communication.
Today, with no beforeunload handlers:
- Something (either browser or render process) initiates a traversal
- We can directly start fetching the document to be traversed to
Today, with beforeunload handlers:
- Something (either browser or render process) initiates a traversal
- We have to ask the renderer process to run its
beforeunloadhandlers. This might stop the traversal. - Only after the beforeunload handlers have finished, can we start fetching the document to be traversed to. (We cannot do this in parallel with running the
beforeunloadhandlers, because of side-effecting GETs such as/logoutlinks. If the user says "actually, I don't want to leave this site", it's bad if we've already triggered the server-side processing for/logout.)
You can see how the beforeunload path is slower than the no-beforeunload path, as when the traversal is browser-initiated (e.g. the back button), it has to do some extra round-tripping across processes.
If we allow navigate to cancel traversals, then we start making any page which has navigate handlers behave much more like the beforeunload path. Every browser-initiated traversal requires an extra process round trip to the renderer process, to run the navigate handler JavaScript and see if it called navigateEvent.preventDefault(). This is unfortunate. Note that you pay this cost whenever you have any navigate handler, even if you don't intend to use it for traversal cancelation. Even if you are just using it for analytics, or just using it for SPA routing, your cross-document traversals get slower.
How can we fix this? Here are some ideas I've had so far:
Require an opt-in
Something like
navigation.setTraversalNavigateEventMode("cancelable");By default, traversal navigate events would not be cancelable. If you opt in by using this mode, your traversals get a bit slower, but you get the extra power to cancel them.
This would send the browser process a message asking it to flip into cancelable-traversal mode, where it always consults the renderer process.
This call might need to be async itself. Or maybe there's some clever queuing strategy where we can let it be sync and ensure any messages of this sort are processed before messages to the browser process requesting a traversal.
Only allow canceling same-document traversals
The original concern here applies to cross-document traversals, where there's potentially an expensive step of hitting the network, which we want to start as soon as possible.
If we restricted cancelable traversal navigate events to only be for same-document traversals, then this problem might disappear.
I'm not 100% sure on this, because slowing down those traversals might also be a bad user experience. But it wouldn't directly impact metrics like LCP, which are relevant to cross-document traversals...
One nice part of this approach is that it gets rid of any overlap between navigate and beforeunload. If you want to prevent cross-document traversals, you need beforeunload. If you want to prevent same-document traversals, you need navigate. This was the original ask in #32, so it seems we'd still be meeting web developer needs. Allowing custom cancelation experiences for same-origin cross-document traversals was a nice bonus, but maybe it isn't actually necessary.
In one sense, this is a nice conservative extension: it solves the minimal use case for now, and leaves us room for future expansion to allow canceling cross-document (maybe even one day cross-origin??) traversals. If we did so, it would probably via an opt-in like the above.
In another sense, this is locking in a potential slowdown for all same-document traversals, which might be bad. So we'd need to be sure we're OK with that before proceeding.