Description
When working on tests for #137, we noticed something about the Chromium implementation, and I believe about the current spec, which is a bit strange. Which is that, for traversals, we fire navigate
events only after getting a response back from the server, i.e. just around unload
event time.
This is in contrast to non-traverse navigations, where we fire navigate
immediately and synchronously.
Recall that non-traverse navigations and traversals are fundamentally different because traversals can affect many different frames. In particular, one frame can cause multiple other frames (including cross-origin/cross-process ones) to traverse. So traversals are in a crucial sense always async: they go from the initiating frame, up to the main browser process, which potentially does a bunch of network fetches, and only then to they descend into the traversed frames, firing unload
and replacing the documents with the new ones. Whereas non-traverse navigations just go to the single destination frame, and navigate it.
In addition to the consistency question between non-traverse and traverse navigations, this is related to #32 and #178. In particular, if we eventually want to make some traversals cancelable, it might make more sense for the event to fire earlier rather than later?
Keeping that desired end state in mind, we basically have three options here:
-
Stick with the status quo for now, of
navigate
firing late for traversals. In the future, when we want to make it cancelable, move it to fire early, but still async.- Implementation-wise, early-but-still-async would require traversals to start in the source frame, go up to the browser process, which then fans out to all potentially-traversed frames to fire
navigate
events, waits to see if anyone canceled them, then proceeds with the network stuff, before eventually fanning back out to the traversed frames with the results. This is annoying to implement but not impossible, and seems like whatbeforeunload
might be doing already. (Although nobody really thinksbeforeunload
is a great precedent to build on...) - This eventual change would improve consistency somewhat in that
navigate
would always fire "soon" for both traverse and non-traverse navigations. Even though the former would be async and the latter would be sync, at least neither would be network-dependent, whereas right now traversalnavigate
timing is network-dependent. - This eventual change would have a slight compat risk, but it's very hard to depend on the timing here unless you are doing strange things that correlate the server being hit with your client-side code.
- Implementation-wise, early-but-still-async would require traversals to start in the source frame, go up to the browser process, which then fans out to all potentially-traversed frames to fire
-
Stick with
navigate
firing late for traversals forever, including in the future when we make traversals cancelable.- This is easy to implement for now, as browsers can continue to use their current code paths for firing
unload
in order to firenavigate
. - The main downside is lack of consistency between traversals and non-traverse navigations: not only do they differ in sync vs. async, the traversal
navigate
timing is also network-dependent.
- This is easy to implement for now, as browsers can continue to use their current code paths for firing
-
Try to hack
navigate
to fire sync for some traversals.- In particular, I think the best we can do is fire it sync for frames that are same-origin-domain to the frame which called
history.back()
ornavigation.traverseTo()
. - This would require some extra implementation work to sync enough data to all frames about whether a given delta would traverse them (and thus they should synchronously fire
navigate
), or not. - For the browser UI back button, sync vs. async isn't meaningful, but it would require similar work to (1) to fire
navigate
before hitting the server when using the browser back button. So, the browser back button would not be included in this option; only (1) would remove the server-dependent component from that case. - This would give some degree of consistency between navigations and non-traverse navigations, at least for developer-initiated traversals? But maybe that consistency is a false, confusing consistency, because developers really should be thinking of traversal as async, even though we can come up with special hacks for same-origin-domain frames.
- In particular, I think the best we can do is fire it sync for frames that are same-origin-domain to the frame which called
I think the main near-term question is whether (3) is valuable.
/cc @annevk @smaug---- @japhet as I'm adding this to the "Might block v1" milestone, to decide on whether (3) is worthwhile.