Closed domenic closed 2 years ago
I know I caused a lot of trouble by whinging about this late in the process, but I'm really grateful that this change happened. I'm giving a talk on this later, and this API makes it so much easier to explain (especially as I'm talking about page transitions in the same talk).
Fixes #230. Mini-explainer:
The
"navigate"
event provided by the navigation API has a method on itsNavigateEvent
instance,event.transitionWhile(promise, options)
. This method would intercept the navigation, convert it into a same-document navigation, and then use the provided promise to signal to the browser and to other parts of the web application that we were in a transitional phase while the new navigation finishes up.However, the signature of
transitionWhile(promise)
worked poorly in practice. The most important reason is the common coding pattern of:i.e., the common pattern of generating a promise via an immediately-invoked async function. The problem with this pattern is that it's equivalent to
This means some portion of your "navigation handling code" would run, before the
navigate
event had finished firing and the URL/entry had been updated.We know of at least two bad examples of this:
When you synchronously modify the DOM. This throws off scroll restoration logic, as discussed in #230 (with detail at https://github.com/WICG/navigation-api/issues/230#issuecomment-1137891972). Because the sync code is modifying the DOM of the starting entry, not the destination entry.
When you are trying to create a redirect. See https://github.com/WICG/navigation-api/issues/124#issuecomment-869804711. If you can synchronously determine you want to redirect, you need to do one behavior (canceling the current navigation, then starting a new one), whereas if you need to use async logic, then you need to do another behavior (replacing the current page). We proposed creating a first-class
navigation.transition.redirect()
helper to abstract this logic, but it's not a great sign that we'd need to create such a helper in the first place.Our solution is to replace
transitionWhile(promise, options)
withintercept({ handler, ...options })
. Essentially, we are replacing the idea of you passing a promise---which people would often generate with an async function---with the idea of you passing an async function directly. Then, the user agent can be sure to schedule the entirety of the async function after thenavigate
event has finished firing, and the URL/entry updated. This avoids the above problems.This particular API shape has other minor benefits:
Code such as
hid some fairly-important information (the
focusReset: "manual"
) down at the bottom. With the new structure, we can instead writeIt is common in applications that are just starting to use the navigation API, and are not yet ready to centralize their routing logic into the
"navigate"
event handler, to simply want to intercept the navigation and convert it into a navigation-API-handled one, but leave the rest of their application as the one that reacts to this navigation. Previously this would requirewhich felt like a hack. ("I want to transition, while an already-fulfilled promise is outstanding"!?) Now it is simply
which communicates the intent better.
The immediately-invoked async function pattern was ugly. So ugly we were contemplating JavaScript language and Web IDL-level changes to get rid of it. See #40 and https://github.com/whatwg/webidl/issues/1020. These changes are now not necessary, at least for our API.
This change slightly reduces (but does not fully remove) the confusion between the navigation API's notion of a navigation transition, and the in-progress shared element transitions spec's idea of a navigation transition. See https://github.com/WICG/shared-element-transitions/issues/143. The reason it does not eliminate it is that
navigation.transition
still exists. (We may settle this by renamingnavigation.transition
; see https://github.com/WICG/navigation-api/issues/230#issuecomment-1145418960.)Preview | Diff