1. The AppHistory class
partial interface Window {readonly attribute AppHistory appHistory ; };
Each Window object has an associated app history, which is a new AppHistory instance created alongside the Window.
The appHistory getter steps are to return this's app history.
[Exposed =Window ]interface :AppHistory EventTarget {sequence <AppHistoryEntry >entries ();readonly attribute AppHistoryEntry ?current ;undefined updateCurrent (AppHistoryUpdateCurrentOptions );options readonly attribute AppHistoryTransition ?transition ;readonly attribute boolean canGoBack ;readonly attribute boolean canGoForward ;AppHistoryResult navigate (USVString ,url optional AppHistoryNavigateOptions = {});options AppHistoryResult reload (optional AppHistoryReloadOptions = {});options AppHistoryResult goTo (DOMString ,key optional AppHistoryNavigationOptions = {});options AppHistoryResult back (optional AppHistoryNavigationOptions = {});options AppHistoryResult forward (optional AppHistoryNavigationOptions = {});options attribute EventHandler onnavigate ;attribute EventHandler onnavigatesuccess ;attribute EventHandler onnavigateerror ;attribute EventHandler oncurrentchange ; };dictionary {AppHistoryUpdateCurrentOptions required any ; };state dictionary {AppHistoryNavigationOptions any ; };info dictionary :AppHistoryNavigateOptions AppHistoryNavigationOptions {any ;state boolean =replace false ; };dictionary :AppHistoryReloadOptions AppHistoryNavigationOptions {any ; };state dictionary {AppHistoryResult Promise <AppHistoryEntry >;committed Promise <AppHistoryEntry >; };finished
Each AppHistory object has an associated entry list, a list of AppHistoryEntry objects, initially empty.
Each AppHistory object has an associated current index, an integer, initially −1.
AppHistory appHistory has entries and events disabled if the following steps return true:
-
Let browsingContext be appHistory’s relevant global object's browsing context.
-
If browsingContext is null, then return true.
-
If browsingContext is still on its initial
about:blankDocument, then return true. -
If appHistory’s relevant global object's associated Document's origin is opaque, then return true.
-
Return false.
AppHistory instance appHistory:
-
If appHistory has entries and events disabled, then:
-
Assert: appHistory’s entry list is empty.
-
Return.
-
-
Let sessionHistory be appHistory’s relevant global object's browsing context's session history.
-
Let appHistorySHEs be a new empty list.
-
Let oldCurrentAHE be the current entry of appHistory.
-
Let currentSHE be sessionHistory’s current entry.
-
Let backwardIndex be the index of currentSHE within sessionHistory, minus 1.
-
While backwardIndex > 0:
-
Let she be sessionHistory[backwardIndex].
-
If she’s origin is same origin with currentSHE’s origin, then prepend she to appHistorySHEs.
-
Otherwise, break.
-
Set backwardIndex to backwardIndex − 1.
-
-
Append currentSHE to appHistorySHEs.
-
Let forwardIndex be the index of currentSHE within sessionHistory, plus 1.
-
While forwardIndex < sessionHistory’s size:
-
Let she be sessionHistory[forwardIndex].
-
If she’s origin is same origin with currentSHE’s origin, then append she to appHistorySHEs.
-
Otherwise, break.
-
Set forwardIndex to forwardIndex + 1.
-
-
Let newCurrentIndex be the index of currentSHE within appHistorySHEs.
-
Let newEntryList be an empty list.
-
For each oldAHE of appHistory’s entry list:
-
Set oldAHE’s index to −1.
-
-
Let index be 0.
-
Let disposedAHEs be a clone of appHistory’s entry list.
-
For each she of appHistorySHEs:
-
If appHistory’s entry list contains an
AppHistoryEntryexistingAHE whose session history entry is she, then: -
Otherwise:
-
Let newAHE be a new
AppHistoryEntrycreated in the relevant realm of appHistory. -
Set newAHE’s session history entry to she.
-
Append newAHE to newEntryList.
-
-
Set newEntryList[index]'s index to index.
-
Set index to index + 1.
-
-
Set appHistory’s entry list to newEntryList.
-
Set appHistory’s current index to newCurrentIndex.
-
If oldCurrentAHE is not null, then fire an event named
currentchangeat appHistory usingAppHistoryCurrentChangeEvent, with itsnavigationTypeattribute initialized to TODO and itsfrominitialized to oldCurrentAHE. -
If appHistory’s ongoing navigation is non-null, then notify about the committed-to entry given appHistory’s ongoing navigation and the current entry of appHistory.
It is important to do this before firing the
disposeevents, since event handlers fordisposecould start another navigation, or otherwise change the value of appHistory’s ongoing navigation. -
For each disposedAHE of disposedAHEs:
-
Fire an event named
disposeat disposedAHE.
-
AppHistory appHistory:
-
Let index be 0.
-
For each ahe of appHistory’s entry list:
-
If ahe’s session history entry is equal to she, then return index.
-
Increment index by 1.
-
-
Assert: this step is never reached.
1.1. Introspecting the app history entry list
entries =appHistory.entries()-
Returns an array of
AppHistoryEntryinstances representing the current app history list, i.e. all session history entries for thisWindowthat are same origin and contiguous to the current session history entry. appHistory.canGoBack-
Returns true if the current
AppHistoryEntryis not the first one in the app history entries list. appHistory.canGoForward-
Returns true if the current
AppHistoryEntryis not the last one in the app history entries list.
entries() method steps are:
-
If this has entries and events disabled, then return the empty list.
-
Return this's entries list.
canGoBack getter steps are:
-
If this has entries and events disabled, then return false.
-
Assert: this's current index is not −1.
-
If this's current index is 0, then return false.
-
Return true.
canGoForward getter steps are:
-
If this has entries and events disabled, then return false.
-
Assert: this's current index is not −1.
-
If this's current index is equal to this's entry list's size − 1, then return false.
-
Return true.
1.2. The current entry
[Exposed =Window ]interface :AppHistoryCurrentChangeEvent Event {(constructor DOMString ,type AppHistoryCurrentChangeEventInit );eventInit readonly attribute AppHistoryNavigationType ?;navigationType readonly attribute AppHistoryEntry ; };from dictionary :AppHistoryCurrentChangeEventInit EventInit {AppHistoryNavigationType ?=navigationType null ;required AppHistoryEntry ; };destination
appHistory.current-
The current
AppHistoryEntry. appHistory.updateCurrent({state})-
Update the app history state of the current
AppHistoryEntry, without performing a navigation likeappHistory.reload()would do.This method is best used to capture updates to the page that have already happened, and need to be reflected into the app history state. For cases where the state update is meant to drive a page update, instead use
appHistory.navigate()orappHistory.reload().
AppHistory appHistory is the result running of the following algorithm:
-
If this has entries and events disabled, then return null.
-
Assert: this's current index is not −1.
-
Return appHistory’s entry list[appHistory’s current index].
The current getter steps are to return the current entry for this.
updateCurrent(options) method steps are:
-
Let current be the current entry for this.
-
If current is null, then throw an "
InvalidStateError"DOMException. -
Let serializedState be StructuredSerializeForStorage(options["
state"]), rethrowing any exceptions. -
Set current’s session history entry's app history state to serializedState.
-
Fire an event named
currentchangeat this usingAppHistoryCurrentChangeEvent, with itsnavigationTypeattribute initialized to null and itsfrominitialized to current.
1.3. Ongoing navigation tracking
[Exposed =Window ]interface {AppHistoryTransition readonly attribute AppHistoryNavigationType navigationType ;readonly attribute AppHistoryEntry from ;readonly attribute Promise <undefined >finished ;AppHistoryResult rollback (optional AppHistoryNavigationOptions = {}); };options
appHistory.transition-
An
AppHistoryTransitionobject representing any ongoing navigation that hasn’t yet reached thenavigatesuccessornavigateerrorstage, if one exists, or null if there is no such transition ongoing.Since
appHistory.current(and other properties likelocation.href) are updated immediately upon navigation, thisappHistory.transitionproperty is useful for determining when such navigations are not yet fully settled, according to any promises passed toevent.transitionWhile(). appHistory.transition.navigationType-
One of "
reload", "push", "replace", or "traverse", indicating what type of navigation this transition is for. appHistory.transition.from-
The
AppHistoryEntryfrom which the transition is coming. This can be useful to compare againstappHistory.current. appHistory.transition.finished-
A promise which fulfills at the same time the
navigatesuccessevent fires, or rejects at the same time thenavigateerrorfires. {committed,finished} =appHistory.transition.rollback(){committed,finished} =appHistory.transition.rollback({info})-
Aborts the ongoing navigation, and immediately performs another navigation which is the logical opposite of the one represented by this transition:
-
If
navigationTypeis "reload", it will perform a replace navigation that resets the app history state to that found in theAppHistoryEntrystored infrom. -
If
navigationTypeis "push", it will traverse to theAppHistoryEntrystored infrom, and then delete the previously-currentAppHistoryEntryfrom the app history list, so that it cannot be reached withappHistory.forward()or the forward button. -
If
navigationTypeis "replace", it will perform another replace navigation that resets the URL and app history state to those found in theAppHistoryEntrystored infrom. -
If
navigationTypeis "traverse", it will traverse to theAppHistoryEntrystored infrom. (This could involve going either forward or backward in the app history list.)
Aborting the ongoing navigation will cause
navigateerrorto fire, anynavigateEvent.signalinstances to fireabort, and any relevant promises to reject. This includesappHistory.transition.finished.Then, the rollback navigation described above starts. This will fire a
navigateevent, and resetappHistory.transitionto a newAppHistoryTransitioninstance. Theinfooption, if provided, will populate theinfoproperty of the fired event.This method can only be called while the transition is still ongoing, i.e. while
appHistory.transitionequals thisAppHistoryTransitionobject. Calling it afterward will cause both returned promises rejected with an "InvalidStateError"DOMException. -
An AppHistory has a transition, which is an AppHistoryTransition or null.
The transition getter steps are to return this's transition.
An AppHistoryTransition has an associated navigation type, which is an AppHistoryNavigationType.
An AppHistoryTransition has an associated from entry, which is an AppHistoryEntry.
An AppHistoryTransition has an associated finished promise, which is an Promise.
The navigationType getter steps are to return this's navigation type.
The from getter steps are to return this's from entry.
The finished getter steps are to return this's finished promise.
rollback(options) method steps are:
-
TODO use options.
During any given navigation, the AppHistory object needs to keep track of the following:
| State | Duration | Explanation |
|---|---|---|
The AppHistoryNavigateEvent
| For the duration of event firing | So that if the navigation is canceled while the event is firing, we can cancel the event. |
The event’s signal
| Until all promises passed to transitionWhile() have settled
| So that if the navigation is canceled, we can signal abort. |
The AppHistoryEntry being navigated to
| From when it is determined, until all promises passed to transitionWhile() have settled
| So that we know what to resolve any committed and finished promises with.
|
Any finished Promise that was returned
| Until all promises passed to transitionWhile() have settled
| So that we can resolve or reject it appropriately. |
| State | Duration | Explanation |
|---|---|---|
Any state
| For the duration of event firing | So that we can update the current entry’s state after the event successfully finishes firing without being canceled. |
| State | Duration | Explanation |
|---|---|---|
Any info
| Until the task is queued to fire the navigate event
| So that we can use it to fire the navigate event after the the trip through the session history traversal queue.
|
Any committed Promise that was returned
| Until the session history is updated (inside that same task) | So that we can resolve or reject it appropriately. |
Furthermore, we need to account for the fact that there might be multiple traversals queued up, e.g. via
const key1= appHistory. entries()[ appHistory. current. index- 1 ]. key; const key2= appHistory. entries()[ appHistory. current. index+ 1 ]. key; appHistory. goTo( key1); // intentionally no await appHistory. goTo( key2);
And, while non-traversal navigations cannot be queued in the same way since a new non-traversal navigation cancels an old one, we need to keep some state around so that we can properly cancel the old one. That is, given
const p1= appHistory. navigate( url1). finished; const p2= appHistory. navigate( url2). finished;
we need to ensure that when navigating to url2, we still have the Promise p1 around so that we can reject it. We can’t just get rid of any ongoing navigation promises the moment the second call to navigate() happens.
We also need to ensure that, if we start a new navigation, navigations which have gotten as far as firing navigate events, but not yet as far as firing navigatesuccess or navigateerror, get finalized with an aborted navigation error.
We end up accomplishing all this using the following setup:
Each AppHistory object has an associated ongoing navigate event, an AppHistoryNavigateEvent or null, initially null.
Each AppHistory object has an associated ongoing navigation signal, which is an AbortSignal or null, initially null.
Each AppHistory object has an associated ongoing navigation, which is an app history API navigation or null, initially null.
Each AppHistory object has an associated upcoming non-traverse navigation, which is an app history API navigation or null, initially null.
Each AppHistory object has an associated upcoming traverse navigations, which is a map from strings to app history API navigations, initially empty.
An app history API navigation is a struct with the following items:
-
An app history, an
AppHistory -
A key, a string or null
-
An info, a JavaScript value
-
An serialized state, a serialized state or null
-
A committed-to entry, an
AppHistoryEntryor null -
A committed promise, a
Promise -
A finished promise, a
Promise -
A did finish before commit, a boolean
We need to store the ongoing navigation signal separately from the app history API navigation struct, since it needs to be tracked even for navigations that are not via the app history APIs.
AppHistory appHistory, a JavaScript value info, and a serialized state-or-null serializedState:
-
Let committedPromise and finishedPromise be new promises created in appHistory’s relevant Realm.
-
Let ongoingNavigation be an app history API navigation whose app history is appHistory, key is null, info is info, serialized state is serializedState, committed-to entry is null, committed promise is committedPromise, finished promise is finishedPromise, and did finish before commit is false.
-
Assert: appHistory’s upcoming non-traverse navigation is null.
-
Set appHistory’s upcoming non-traverse navigation to ongoingNavigation.
-
Return ongoingNavigation.
AppHistory appHistory, a string key, and a JavaScript value info:
-
Let committedPromise and finishedPromise be new promises created in appHistory’s relevant Realm.
-
Let traversal be an app history API navigation whose whose app history is appHistory, key is key, info is info, serialized state is null, committed-to entry is null, committed promise is committedPromise, finished promise is finishedPromise, and did finish before commit is false.
-
Set appHistory’s upcoming traverse navigations[key] to traversal.
-
Return traversal.
AppHistory appHistory and a string-or-null destinationKey:
-
Assert: appHistory’s ongoing navigation is null.
-
If destinationKey is not null, then:
-
Assert: appHistory’s upcoming non-traverse navigation is null.
-
If appHistory’s upcoming traverse navigations[destinationKey] exists, then:
-
Set appHistory’s ongoing navigation to appHistory’s upcoming traverse navigations[destinationKey].
-
Remove appHistory’s upcoming traverse navigations[destinationKey].
-
-
-
Otherwise,
-
Set appHistory’s ongoing navigation to appHistory’s upcoming non-traverse navigation.
-
Set appHistory’s upcoming non-traverse navigation to null.
-
-
Let appHistory be navigation’s app history.
-
If appHistory’s ongoing navigation is navigation, then set appHistory’s ongoing navigation to null.
-
Otherwise,
-
Assert: navigation’s key is not null.
-
Assert: appHistory’s upcoming traverse navigations[navigation’s key] exists.
-
Remove appHistory’s upcoming traverse navigations[navigation’s key].
-
AppHistoryEntry entry:
-
Set navigation’s committed-to entry to entry.
-
Resolve navigation’s committed promise with entry.
After this point, navigation’s committed promise is only needed in cases where it has not yet been returned to author code. Implementations might want to clear it out to avoid keeping it alive for the lifetime of the app history API navigation.
-
If navigation’s did finish before commit is true, then resolve the finished promise for navigation.
-
If navigation’s finished promise is null, then return.
-
If navigation’s committed-to entry entry is null, then:
-
Set navigation’s did finish before commit to true.
-
Return.
In same-document traversal cases, resolve the finished promise can be called before notify about the committed-to entry, since the latter requires a roundtrip through the relevant session history traversal queue and the former just depends on the settlement of promises passed to
transitionWhile(). -
-
Resolve navigation’s finished promise with its committed-to entry.
-
Clean up navigation.
-
If navigation’s finished promise is null, then return.
-
Reject navigation’s finished promise with exception.
-
If navigation’s committed promise is not null, then reject navigation’s committed promise with exception.
-
Clean up navigation.
1.4. Navigating
{committed,finished} =appHistory.navigate(url){committed,finished} =appHistory.navigate(url, options)-
Navigates the current page to the given url. options can contain the following values:
-
replacecan be set to true to replace the current session history entry, instead of pushing a new one. -
infocan be set to any value; it will populate theinfoproperty of the correspondingnavigateevent. -
statecan be set to any serializable value; it will populate the state retrieved byappHistory.current.getState()once the navigation completes, for same-document navigations. (It will be ignored for navigations that end up cross-document.)
By default this will perform a full navigation (i.e., a cross-document navigation, unless the given URL differs only in a fragment from the current one). The
navigateevent’stransitionWhile()method can be used to convert it into a same-document navigation.The returned promises will behave as follows:
-
For navigations that get aborted, both promises will reject with an "
AbortError"DOMException. -
For same-document navigations created by using the
navigateevent’stransitionWhile()method,committedwill fulfill immediately, andfinishedwill fulfill or reject according to the promises passed totransitionWhile(). -
For other same-document navigations (e.g., non-intercepted fragment navigations), both promises will fulfill immediately.
-
For cross-document navigations, both promises will never settle.
In all cases, when the returned promises fulfill, it will be with the
AppHistoryEntrythat was navigated to. -
{committed,finished} =appHistory.reload(options)-
Reloads the current page. The
infoandstateoptions behave as described above.The default behavior of performing a from-network-or-cache reload of the current page can be overriden by using the
navigateevent’stransitionWhile()method. Doing so will mean this call only updates state or passes along the appropriateinfo, plus performing whatever actions thenavigateevent handler sees fit to carry out.The returned promises will behave as follows:
-
If the reload is aborted, both promises will reject with an "
AbortError"DOMException. -
If the reload is intercepted by using the
navigateevent’stransitionWhile()method,committedwill fulfill immediately, andfinishedwill fulfill or reject according to the promises passed totransitionWhile(). -
Otherwise, both promises will never settle.
-
navigate(url, options) method steps are:
-
Parse url relative to this's relevant settings object. If that returns failure, then return an early error result for a "
SyntaxError"DOMException. Otherwise, let urlRecord be the resulting URL record. -
If this's relevant global object's associated Document is not fully active, then return an early error result for an "
InvalidStateError"DOMException. -
If this's relevant global object's associated Document's unload counter is greater than 0, then return an early error result for an "
InvalidStateError"DOMException. -
Let serializedState be null.
-
If options["
state"] exists, then set serializedState to StructuredSerializeForStorage(options["state"]). If this throws an exception, then return an early error result for that exception. -
Let info be options["
info"] if it exists; otherwise, undefined. -
Let historyHandling be "
replace" if options["replace"] is true; otherwise, "default". -
Return the result of performing a non-traverse app history navigation given this, urlRecord, serializedState, info, and historyHandling.
reload(options) method steps are:
-
If this's relevant global object's associated Document is not fully active, then return an early error result for an "
InvalidStateError"DOMException. -
If this's relevant global object's associated Document's unload counter is greater than 0, then return an early error result for an "
InvalidStateError"DOMException. -
Let urlRecord be this's relevant global object's active document's URL.
-
Let serializedState be null.
-
If options["
state"] exists, then set serializedState to StructuredSerializeForStorage(options["state"]). If this throws an exception, then return an early error result for that exception. -
Otherwise,
-
Let current be the current entry of this.
-
If current is not null, then set serializedState to current’s app history state.
-
-
Let info be options["
info"] if it exists; otherwise, undefined. -
Return the result of performing a non-traverse app history navigation given this, urlRecord, serializedState, info, and "
reload".
AppHistory object appHistory, a URL url, a serialized state-or-null serializedState, a JavaScript value info, and a history handling behavior historyHandling:
-
Let browsingContext be appHistory’s relevant global object's browsing context.
-
Assert: browsingContext is not null.
-
Assert: historyHandling is either "
replace", "reload", or "default". -
Let ongoingNavigation be the result of setting the upcoming non-traverse navigation for appHistory given info and serializedState.
-
Navigate browsingContext to url with historyHandling set to historyHandling, appHistoryState set to serializedState, and the source browsing context set to browsingContext.
-
If appHistory’s upcoming non-traverse navigation is ongoingNavigation, then:
This means the navigate algorithm bailed out before ever getting to the inner navigate event firing algorithm which would promote the upcoming navigation to ongoing.
-
Set appHistory’s upcoming non-traverse navigation to null.
-
Return an early error result for an "
AbortError"DOMException.
-
-
If ongoingNavigation’s serialized state is non-null, then set browsingContext’s session history's current entry's app history state to ongoingNavigation’s serialized state.
At this point ongoingNavigation’s serialized state is no longer needed and can be nulled out instead of keeping it alive for the lifetime of the app history API navigation.
-
Return «[ "
committed" → ongoingNavigation’s committed promise, "finished" → ongoingNavigation’s finished promise ]».
Unlike location.assign() and friends, which are exposed across origin-domain boundaries, appHistory.navigate() and appHistory.reload() can only be accessed by code with direct synchronous access to the appHistory property. Thus, we avoid the complications around tracking source browsing contexts, and we don’t need to deal with the allowed to navigate check and its accompanying exceptionsEnabled flag. We just treat all navigations as being initiated by the AppHistory object itself.
An an early error result for an exception e is a dictionary instance given by «[ "committed" → a promise rejected with e, "finished" → a promise rejected with e ]».
1.5. Traversing
{committed,finished} =appHistory.goTo(key){committed,finished} =appHistory.goTo(key, {info})-
Traverses the joint session history to the closest joint session history entry that matches the
AppHistoryEntrywith the given key.infocan be set to any value; it will populate theinfoproperty of the correspondingnavigateevent.If a traversal to that joint session history is already in progress, then this will return the promises for that original traversal, and
infowill be ignored.The returned promises will behave as follows:
-
If there is no
AppHistoryEntryinappHistory.entrieswith the given key, both will reject with an "InvalidStateError"DOMException. -
For same-document traversals intercepted by the
navigateevent’stransitionWhile()method,committedwill fulfill as soon as the traversal is processed andappHistory.currentis updated, andfinishedwill fulfill or reject according to the promises passed totransitionWhile(). -
For non-intercepted same-document traversals, both promises will fulfill as soon as the traversal is processed and
appHistory.currentis updated -
For cross-document traversals, both promises will never settle.
-
{committed,finished} =appHistory.back(){committed,finished} =appHistory.back({info})-
Traverse the joint session history to the closest previous joint session history entry which results in this frame navigating, i.e. results in
appHistory.currentupdating.infocan be set to any value; it will populate theinfoproperty of the correspondingnavigateevent.If a traversal to that joint session history is already in progress, then this will return the promises for that original traversal, and
infowill be ignored.The returned promises behave equivalently to those returned by
goTo(). {committed,finished} =appHistory.forward(){committed,finished} =appHistory.forward({info})-
Traverse the joint session history to the closest forward joint session history entry which results in this frame navigating, i.e. results in
appHistory.currentupdating.infocan be set to any value; it will populate theinfoproperty of the correspondingnavigateevent.If a traversal to that joint session history is already in progress, then this will return the promises for that original traversal, and
infowill be ignored.The returned promises behave equivalently to those returned by
goTo().
goTo(key, options) method steps are:
-
If this's current index is −1, then return an early error result for an "
InvalidStateError"DOMException. -
If this's entry list does not contain any
AppHistoryEntrywhose session history entry's app history key equals key, then return an early error result for an "InvalidStateError"DOMException. -
Return the result of performing an app history traversal given this, key, and options.
back(options) method steps are:
-
If this's current index is −1 or 0, then return an early error result for an "
InvalidStateError"DOMException. -
Let key be this's entry list[this's current index − 1]'s session history entry's app history key.
-
Return the result of performing an app history traversal given this, key, and options.
forward(options) method steps are:
-
If this's current index is −1 or is equal to this's entry list's size − 1, then return an early error result for an "
InvalidStateError"DOMException. -
Let key be this's entry list[this's current index + 1]'s session history entry's app history key.
-
Return the result of performing an app history traversal given this, key, and options.
The following algorithm is specified in terms of the session history rewrite pull request against the HTML Standard, because the existing session history traversal infrastructure is broken enough that it’s hard to build on. It is expected to track that work as it continues.
To perform an app history traversal given an AppHistory object appHistory, a string key, and an AppHistoryNavigationOptions options:
-
If appHistory’s relevant global object's associated Document is not fully active, then return an early error result for an "
InvalidStateError"DOMException. -
If appHistory’s relevant global object's associated Document's unload counter is greater than 0, then return an early error result for an "
InvalidStateError"DOMException. -
If appHistory’s current entry's session history entry's app history key equals key, then return «[ "
committed" → a promise resolved with appHistory’s current entry, "finished" → a promise resolved with appHistory’s current entry ]» -
If appHistory’s upcoming traverse navigations[key] exists, then:
-
Let navigation be appHistory’s upcoming traverse navigations[key].
-
Return «[ "
committed" → navigation’s committed promise, "finished" → navigation’s finished promise ]».
-
-
Let navigable be appHistory’s relevant global object's browsing context's containing navigable.
-
Let traversable be navigable’s traversable navigable.
-
Let initiatorBC be appHistory’s relevant global object's browsing context.
-
Let info be options["
info"] if it exists, or undefined otherwise. -
Let ongoingNavigation be the result of setting an upcoming traverse navigation for appHistory given key and info.
-
Enqueue the following steps on traversable’s session history traversal queue:
-
Let navigableEntries be the result of getting the session history entries given navigable.
-
Let targetEntry be the session history entry in navigableEntries whose app history key equals key. If no such entry exists, then:
-
Reject the finished promise for ongoingNavigation with an "
InvalidStateError"DOMException. -
Abort these steps.
This can occur if the appHistory object’s view of session history is outdated, which can happen for brief periods while all the relevant threads and processes are being synchronized in reaction to a history change (such as the user clearing their history).
-
-
If targetEntry is navigable’s active session history entry, then abort these steps.
This can occur if a previously-queued-up traversal already took us to this session history entry. In that case that previous traversal will have dealt with ongoingNavigation already.
-
Let targetStep be null.
-
If targetEntry’s step is greater than traversable’s current session history step, then set targetStep to targetEntry’s step.
-
Otherwise:
-
Let afterTarget be the session history entry after targetEntry in navigableEntries.
-
Let allSteps be the result of getting all history steps that are part of the target session TODO.
-
Set targetStep to the greatest number in allSteps that is less than afterTarget’s step.
-
-
Apply the history step targetStep to traversable, with true, initiatorBC, and "
none".-
If this aborts due to user-canceled unloading or due to the
navigateevent being canceled, then finalize with an aborted navigation error given appHistory and ongoingNavigation. -
If this aborts due to the initiator allowed-to-navigate check, then finalize with an aborted navigation error given appHistory, ongoingNavigation, and a new "
SecurityError"DOMExceptioncreated in appHistory’s relevant Realm.
Eventually apply the history step will have well-specified hooks for communicating these conditions back to its caller.
-
-
-
Return «[ "
committed" → ongoingNavigation’s committed promise, "finished" → ongoingNavigation’s finished promise ]».
1.6. Event handlers
The following are the event handlers (and their corresponding event handler event types) that must be supported, as event handler IDL attributes, by objects implementing the AppHistory interface:
| Event handler | Event handler event type |
|---|---|
onnavigate
| navigate
|
onnavigatesuccess
| navigatesuccess
|
onnavigateerror
| navigateerror
|
oncurrentchange
| currentchange
|
2. The navigate event
2.1. The AppHistoryNavigateEvent class
[Exposed =Window ]interface :AppHistoryNavigateEvent Event {(constructor DOMString ,type AppHistoryNavigateEventInit );eventInit readonly attribute AppHistoryNavigationType navigationType ;readonly attribute AppHistoryDestination destination ;readonly attribute boolean canTransition ;readonly attribute boolean userInitiated ;readonly attribute boolean hashChange ;readonly attribute AbortSignal signal ;readonly attribute FormData ?formData ;readonly attribute any info ;undefined transitionWhile (Promise <undefined >); };newNavigationAction dictionary :AppHistoryNavigateEventInit EventInit {AppHistoryNavigationType = "push";navigationType required AppHistoryDestination ;destination boolean =canTransition false ;boolean =userInitiated false ;boolean =hashChange false ;required AbortSignal ;signal FormData ?=formData null ;any ; };info enum {AppHistoryNavigationType ,"reload" ,"push" ,"replace" };"traverse"
event.navigationType-
One of "
reload", "push", "replace", or "traverse", indicating what type of navigation this is. event.destination-
An
AppHistoryDestinationrepresenting the destination of the navigation. event.canTransition-
True if
transitionWhile()can be called to convert this navigation into a single-page navigation; false otherwise.Generally speaking, this will be true whenever the current
Documentcan have its URL rewritten to the destination URL, except for cross-document back/forward navigations, where it will always be false. event.userInitiated-
True if this navigation was due to a user clicking on an
aelement, submitting aformelement, or using the browser UI to navigate; false otherwise. event.hashChange-
True if this navigation is a fragment navigation; false otherwise.
event.signal-
An
AbortSignalwhich will become aborted if the navigation gets canceled, e.g. by the user pressing their browser’s "Stop" button, or another higher-priority navigation interrupting this one.The expected pattern is for developers to pass this along to any async operations, such as
fetch(), which they perform as part of handling this navigation. event.formData-
The
FormDatarepresenting the submitted form entries for this navigation, if this navigation is a "push" or "replace" navigation representing a POST form submission; null otherwise.(Notably, this will be null even for "
reload" and "traverse" navigations that are revisiting a session history entry that was originally created from a form submission.) event.info-
An arbitrary JavaScript value passed via other app history APIs that initiated this navigation, or null if the navigation was initiated by the user or via a non-app history API.
event.transitionWhile(newNavigationAction)-
Synchronously converts this navigation into a same-document navigation to the destination URL.
The given newNavigationAction promise is used to signal the duration, and success or failure, of the navigation. After it settles, the browser signals to the user (e.g. via a loading spinner UI, or assistive technology) that the navigation is finished. Additionally, it fires
navigatesuccessornavigateerrorevents as appropriate, which other parts of the web application can respond to.This method will throw a "
SecurityError"DOMExceptionifcanTransitionis false, or ifisTrustedis false. It will throw an "InvalidStateError"DOMExceptionif not called synchronously, during event dispatch.
The navigationType, destination, canTransition, userInitiated, hashChange, signal, formData, and info getter steps are to return the value that the corresponding attribute was initialized to.
An AppHistoryNavigateEvent has the following associated values which are only conditionally used:
-
classic history API serialized data, a serialized state-or-null, used when its
navigationTypeis "reload", "push" or "replace"
This is set appropriately when the event is fired.
An AppHistoryNavigateEvent also has an associated navigation action promises list, which is a list of Promise objects, initially empty.
transitionWhile(newNavigationAction) method steps are:
-
If this's relevant global object's active Document is not fully active, then throw an "
InvalidStateError"DOMException. -
If this's
isTrustedattribute was initialized to false, then throw a "SecurityError"DOMException. -
If this's
canTransitionattribute was initialized to false, then throw a "SecurityError"DOMException. -
If this's dispatch flag is unset, then throw an "
InvalidStateError"DOMException. -
If this's canceled flag is set, then throw an "
InvalidStateError"DOMException. -
Append newNavigationAction to this's navigation action promises list.
2.2. The AppHistoryDestination class
[Exposed =Window ]interface {AppHistoryDestination readonly attribute USVString url ;readonly attribute DOMString ?key ;readonly attribute DOMString ?id ;readonly attribute long long index ;readonly attribute boolean sameDocument ;any getState (); };
event.destination.url-
The URL being navigated to.
event.destination.key-
The value of the
keyproperty of the destinationAppHistoryEntry, if this is a "traverse" navigation, or null otherwise. event.destination.id-
The value of the
idproperty of the destinationAppHistoryEntry, if this is a "traverse" navigation, or null otherwise. event.destination.index-
The value of the
indexproperty of the destinationAppHistoryEntry, if this is a "traverse" navigation, or −1 otherwise. event.destination.sameDocument-
Indicates whether or not this navigation is to the same
Documentas the currentdocumentvalue, or not. This will be true, for example, in cases of fragment navigations orhistory.pushState()navigations.Note that this property indicates the original nature of the navigation. If a cross-document navigation is converted into a same-document navigation using
event.transitionWhile(), that will not change the value of this property. state = event.destination.getState()-
For "
traverse" navigations, returns the deserialization of the state stored in the destination session history entry.For "
push" and "replace" navigations, returns the deserialization of the state passed toappHistory.navigate(), if the navigation was initiated in that way, or undefined if it wasn’t.For "
reload" navigations, returns the deserialization of the state passed toappHistory.reload(), if the reload was initiated in that way, or undefined if it wasn’t.
An AppHistoryDestination has an associated URL, which is a URL.
An AppHistoryDestination has an associated key, which is a string-or-null.
An AppHistoryDestination has an associated id, which is a string-or-null.
An AppHistoryDestination has an associated index, which is an integer.
An AppHistoryDestination has an associated state, which is a serialized state-or-null.
An AppHistoryDestination has an associated is same document, which is a boolean.
The url getter steps are to return this's URL, serialized.
The key getter steps are to return this's key.
The id getter steps are to return this's id.
The index getter steps are to return this's index.
The sameDocument getter steps are to return this's is same document.
getState() method steps are:
-
Return StructuredDeserialize(this's state).
2.3. Firing the event
navigate event at an AppHistory appHistory given a session history entry destinationEntry, and an optional user navigation involvement userInvolvement (default "none"):
-
Let event be the result of creating an event given
AppHistoryNavigateEvent, in appHistory’s relevant Realm. -
Let destination be a new
AppHistoryDestinationcreated in appHistory’s relevant Realm. -
If destinationEntry’s origin is same origin with appHistory’s relevant settings object's origin, then:
-
Set destination’s key to destinationEntry’s app history key.
-
Set destination’s id to destinationEntry’s app history id.
-
Set destination’s index to the result of getting the app history index of destinationEntry within appHistory.
-
Set destination’s state to destinationEntry’s app history state.
-
-
Otherwise,
-
Set destination’s is same document to true if destinationEntry’s document is equal to appHistory’s relevant global object's associated Document; otherwise false.
-
Let result be the result of performing the inner navigate event firing algorithm given appHistory, "
traverse", event, destination, userInvolvement, and null. -
Assert: result is true (traversals are never cancelable).
navigate event at an AppHistory appHistory given an AppHistoryNavigationType navigationType, a URL destinationURL, a boolean isSameDocument, an optional user navigation involvement userInvolvement (default "none"), an optional serialized state-or-null state (default null), an optional list of FormData entries or null formDataEntryList (default null), and an optional serialized state-or-null classicHistoryAPISerializedData (default null):
-
Let event be the result of creating an event given
AppHistoryNavigateEvent, in appHistory’s relevant Realm. -
Set event’s classic history API serialized data to classicHistoryAPISerializedData.
-
Let destination be a new
AppHistoryDestinationcreated in appHistory’s relevant Realm. -
Set destination’s URL to destinationURL.
-
Set destination’s key to null.
-
Set destination’s id to null.
-
Set destination’s index to −1.
-
Set destination’s state to state.
-
Set destination’s is same document to isSameDocument.
-
Return the result of performing the inner navigate event firing algorithm given appHistory, navigationType, event, destination, userInvolvement, and formDataEntryList.
navigate event firing algorithm is the following steps, given an AppHistory appHistory, an AppHistoryNavigationType navigationType, an AppHistoryNavigateEvent event, an AppHistoryDestination destination, a user navigation involvement userInvolvement, and a list of FormData entries or null formDataEntryList:
-
Promote the upcoming navigation to ongoing given appHistory and destination’s key.
-
Let ongoingNavigation be appHistory’s ongoing navigation.
-
If appHistory has entries and events disabled, then:
-
If ongoingNavigation is not null, then clean up ongoingNavigation.
In this case the committed promise and finished promise will never fulfill, since we never create
AppHistoryEntrys for the initialabout:blankDocumentso we have nothing to resolve them with. -
Return true.
-
-
Let document be appHistory’s relevant global object's associated document.
-
If document can have its URL rewritten to destination’s URL, and either destination’s is same document is true or navigationType is not "
traverse", then initialize event’scanTransitionto true. Otherwise, initialize it to false. -
If navigationType is not "
traverse", then initialize event’scancelableto true. Otherwise, initialize it to false. -
Initialize event’s
navigationTypeto navigationType. -
Initialize event’s
destinationto destination. -
If ongoingNavigation is not null, then initialize event’s
infoto ongoingNavigation’s info. Otherwise, initialize it to undefined.At this point ongoingNavigation’s info is no longer needed and can be nulled out instead of keeping it alive for the lifetime of the app history API navigation.
-
Initialize event’s
signalto a newAbortSignalcreated in appHistory’s relevant Realm. -
Let currentURL be document’s URL.
-
If all of the following are true:
-
destination’s is same document is true;
-
destination’s URL equals currentURL with exclude fragments set to true; and
-
destination’s URL's fragment is not identical to currentURL’s fragment
then initialize event’s
hashChangeto true. Otherwise, initialize it to false. -
-
If userInvolvement is not "
none", then initialize event’suserInitiatedto true. Otherwise, initialize it to false. -
If formDataEntryList is not null, then initialize event’s
formDatato a newFormDatacreated in appHistory’s relevant Realm, associated to formDataEntryList. Otherwise, initialize it to null. -
Assert: appHistory’s ongoing navigate event is null.
-
Set appHistory’s ongoing navigate event to event.
-
Assert: appHistory’s ongoing navigation signal is null.
-
Set appHistory’s ongoing navigation signal to event’s
signal. -
Let dispatchResult be the result of dispatching event at appHistory.
-
Set appHistory’s ongoing navigate event to null.
-
If dispatchResult is false:
-
If navigationType is not "
traverse" and event’ssignal's aborted flag is unset, then finalize with an aborted navigation error given appHistory and ongoingNavigation.If navigationType is "
traverse", then we will finalize with an aborted navigation error in perform an app history traversal. -
Return false.
-
-
Let hadTransitionWhile be true if event’s navigation action promises list is not empty; otherwise false.
-
Let endResultIsSameDocument be true if hadTransitionWhile is true or destination’s is same document is true.
-
If hadTransitionWhile is true:
-
Let fromEntry be the current entry for appHistory.
-
Assert: fromEntry is not null.
-
Set appHistory’s transition to a new
AppHistoryTransitioncreated in appHistory’s relevant Realm, whose navigation type is navigationType, from entry is fromEntry, and whose finished promise is a new promise created in appHistory’s relevant Realm.
-
-
If endResultIsSameDocument is true:
-
Let transition be appHistory’s transition.
-
Assert: transition is not null.
-
Wait for all of event’s navigation action promises list, with the following success steps:
-
If event’s
signal's aborted flag is set, then abort these steps. -
Fire an event named
navigatesuccessat appHistory. -
Resolve transition’s finished promise with undefined.
-
If appHistory’s transition is transition, then set appHistory’s transition to null.
-
If ongoingNavigation is non-null, then resolve the finished promise for ongoingNavigation.
-
If event’s
signal's aborted flag is set, then abort these steps. -
Fire an event named
navigateerrorat appHistory usingErrorEvent, witherrorinitialized to rejectionReason, andmessage,filename,lineno, andcolnoinitialized to appropriate values that can be extracted from rejectionReason in the same underspecified way the user agent typically does for the report an exception algorithm. -
Reject transition’s finished promise with rejectionReason.
-
If appHistory’s transition is transition, then set appHistory’s transition to null.
-
If ongoingNavigation is non-null, then reject the finished promise for ongoingNavigation with rejectionReason.
-
-
-
Otherwise, if ongoingNavigation is non-null, then:
-
Set ongoingNavigation’s serialized state to null.
This ensures that any call to
appHistory.navigate()which triggered this algorithm does not overwrite the app history state of the current entry for cross-document navigations. -
Clean up ongoingNavigation.
-
-
If hadTransitionWhile is true and navigationType is not "
traverse":-
Let isPush be true if navigationType is "
push"; otherwise, false. -
Run the URL and history update steps given document and event’s
destination's URL, with serializedData set to event’s classic history API serialized data and isPush set to isPush. -
Return false.
-
-
Return true.
AppHistory appHistory, an app history API navigation or null ongoingNavigation, and an optional DOMException error:
-
If appHistory’s ongoing navigate event is non-null, then:
-
Set appHistory’s ongoing navigate event's canceled flag to true.
-
Set appHistory’s ongoing navigate event to null.
-
-
If appHistory’s ongoing navigation signal is non-null, then:
-
Signal abort on appHistory’s ongoing navigation signal.
-
Set appHistory’s ongoing navigation signal to null.
-
-
If error was not given, then set error to a new "
AbortError"DOMException, created in appHistory’s relevant Realm. -
Fire an event named
navigateerrorat appHistory usingErrorEvent, witherrorinitialized to error, andmessage,filename,lineno, andcolnoinitialized to appropriate values that can be extracted from error and the current JavaScript stack in the same underspecified way the user agent typically does for the report an exception algorithm.Thus, for example, if this algorithm is reached because of a call to
window.stop(), these properties would probably end up initialized based on the line of script that calledwindow.stop(). But if it’s because the user clicked the stop button, these properties would probably end up with default values like the empty string or 0. -
If ongoingNavigation is non-null, then:
-
Set ongoingNavigation’s serialized state to null.
This ensures that any call to
appHistory.navigate()which triggered this algorithm does not overwrite the app history state of the current entry for aborted navigations. -
Reject the finished promise for ongoingNavigation with error.
-
-
If appHistory’s transition is not null, then:
-
Reject appHistory’s transition's finished promise with error.
-
Set appHistory’s transition to null.
-
-
Let appHistory be bc’s active window's app history.
-
If appHistory’s ongoing navigation signal is null, then return.
-
Finalize with an aborted navigation error given appHistory and appHistory’s ongoing navigation.
-
Let appHistory be bc’s active window's app history.
-
Let traversals be a clone of appHistory’s upcoming traverse navigations.
-
For each traversal of traversals: finalize with an aborted navigation error given appHistory and traversal.
3. App history entries
[Exposed =Window ]interface :AppHistoryEntry EventTarget {readonly attribute USVString url ;readonly attribute DOMString key ;readonly attribute DOMString id ;readonly attribute long long index ;readonly attribute boolean sameDocument ;any getState ();attribute EventHandler onnavigateto ;attribute EventHandler onnavigatefrom ;attribute EventHandler onfinish ;attribute EventHandler ondispose ; };
entry.url-
The URL of this app history entry.
entry.key-
A user agent-generated random UUID string representing this app history entry’s place in the app history list. This value will be reused by other
AppHistoryEntryinstances that replace this one due to replace-style navigations. This value will survive session restores.This is useful for navigating back to this location in the app history entry list, using
appHistory.goTo(key). entry.id-
A user agent-generated random UUID string representing this specific app history entry. This value will not be reused by other
AppHistoryEntryinstances. This value will survive session restores.This is useful for associating data with this app history entry using other storage APIs.
entry.index-
The index of this app history entry within
appHistory.entries(), or −1 if the entry is not in the app history list. entry.sameDocument-
Indicates whether or not this app history entry is for the same
Documentas the currentdocumentvalue, or not. This will be true, for example, when the entry represents a fragment navigation or single-page app navigations. state = entry.getState()-
Returns the deserialization of the state stored in this entry, which was added to the entry using
appHistory.navigate(). This state survives session restores.Note that in general, unless the state value is a primitive,
entry.getState() !== entry.getState(), since a fresh copy is returned each time.This state is unrelated to the classic history API’s
history.state.
Each AppHistoryEntry has an associated session history entry, which is a session history entry.
Each AppHistoryEntry has an associated index, which is an integer.
key getter steps are:
-
If this's relevant global object's associated Document is not fully active, then return the empty string.
-
Return this's session history entry's app history key.
id getter steps are:
-
If this's relevant global object's associated Document is not fully active, then return the empty string.
-
Return this's session history entry's app history id.
url getter steps are:
-
If this's relevant global object's associated Document is not fully active, then return the empty string.
-
Return this's session history entry's URL, serialized.
index getter steps are:
-
If this's relevant global object's associated Document is not fully active, then return −1.
sameDocument getter steps are:
-
If this's relevant global object's associated Document is not fully active, then return false.
-
Return true if this's session history entry's document equals this's relevant global object's associated Document, and false otherwise.
getState() method steps are:
-
If this's relevant global object's associated Document is not fully active, then return undefined.
-
If this's session history entry's app history state is null, then return undefined.
-
Return StructuredDeserialize(this's session history entry's app history state).
Unlike history.state, this will deserialize upon each access.
This can in theory throw an exception, if attempting to deserialize a large ArrayBuffer when not enough memory is available.
The following are the event handlers (and their corresponding event handler event types) that must be supported, as event handler IDL attributes, by objects implementing the AppHistoryEntry interface:
| Event handler | Event handler event type |
|---|---|
onnavigateto
| navigateto
|
onnavigatefrom
| navigatefrom
|
onfinish
| finish
|
ondispose
| dispose
|
TODO: actually fire finish, navigateto, and navigatefrom.
4. Patches to fire the navigate event
The following section details monkeypatches to [HTML] that cause the navigate event to be fired appropriately, and for canceling the event to cancel the navigation. The first few sections detail slight tweaks to existing algorithms to pass through useful information into the navigation and history traversal algorithms. Then, § 4.3 Navigation algorithm updates contains the actual firing of the event.
4.1. Form submission patches
To properly thread the form entry list from its creation through to AppHistoryNavigateEvent's formData property, we need the following modifications:
-
Let navigationType be "
form-submission" if entryList is non-null; otherwise, "other".
-
Navigate target browsing context to destination, with historyHandling set to historyHandling
and navigationType set to "entryList set to entryList .form-submission"
4.2. Browser UI/user-initiated patches
To more rigorously specify when a navigation is initiated from browser UI or by the user interacting with a, area, and form elements, both for the purposes of the AppHistoryNavigateEvent's userInitiated property and for prohibiting interception of certain types of browser-UI-initiated navigations, we need the following modifications:
Introduce (right before the definition of the navigate algorithm) the concept of a user navigation involvement, which is one of the following:
- "
browser UI" -
The navigation was initiated by the user via browser UI mechanisms
- "
activation" -
The navigation was initiated by the user via the activation behavior of an element
- "
none" -
The navigation was not initiated by the user
Define the user navigation involvement for an Event event as "activation" if event’s isTrusted attribute is initialized to true, and "none" otherwise.
Modify the navigate algorithm to take an optional named argument userInvolvement (default "none"). Then, update the paragraph talking about browser-UI initiated navigation as follows:
A user agent may provide various ways for the user to explicitly cause a browsing context to navigate, in addition to those defined in this specification.
Such cases must set the userInvolvement argument to "browser UI".
This infrastructure partially solves whatwg/html#5381, and it’d be ideal to update the `Sec-Fetch-Site` spec at the same time.
Modify the navigate to a fragment algorithm to take a new userInvolvement argument. Then, update the call to it from navigate to set userInvolvement to this userInvolvement value.
Modify the traverse the history by a delta argument to take an optional named argument userInvolvement (default "none"). Then, update the paragraph talking about user-initiated navigation as follows:
When the user navigates through a browsing context, e.g. using a browser’s back and forward buttons, the user agent must traverse the history by a delta with a delta equivalent to the action specified by the userand, the browsing context being operated on , and userInvolvement set to "browser UI" .
Modify the follow the hyperlink algorithm to take a new userInvolvement argument. Then, update the call to it from navigate to set userInvolvement to this userInvolvement value.
area elements by introducing the event argument and replacing the follow the hyperlink step with the following:
-
Otherwise, follow the hyperlink created by element with the user navigation involvement for event.
a elements by replacing its follow the hyperlink step with the following:
-
Otherwise, follow the hyperlink created by element with the user navigation involvement for event.
Expand the section on "Providing users with a means to follow hyperlinks created using the link element" by adding the following sentence:
Such invocations of follow the hyperlink algorithm must set the userInvolvement argument to "browser UI".
Modify the plan to navigate algorithm to take a userInvolvement argument. Then, update the call to it from navigate to set userInvolvement to this userInvolvement value.
Modify the submit algorithm to take an optional userInvolvement argument (default "none"). Have the submit algorithm pass along its value to all invocations of plan to navigate.
Modify the definition of the activation behavior for input elements to take an event argument. Then, pass along this argument to the invocation of the input activation behavior.
Modify the Submit Button state’s input activation behavior by having it take an event argument and pass along the user navigation involvement for event as the final argument when it calls submit.
Modify the Image Button state’s input activation behavior by having it take an event argument and pass along the user navigation involvement for event as the final argument when it calls submit.
Modify the button element’s activation behavior by having it take an event argument and, in the Submit Button case, to pass along the user navigation involvement for event as the final argument when it calls submit.
Modify the no-submit button case for implicit form submission to pass along "activation" as the final argument when it calls submit.
The case of implicit submission when a submit button is present is automatically taken care of because it fires a (trusted) click event at the submit button.
4.3. Navigation algorithm updates
With the above infrastructure in place, we can actually fire and handle the navigate event in the following locations:
-
Let appHistory be history’s relevant global object's app history.
-
Let navigationType be "
push" if isPush is true, and "replace" otherwise. -
Let continue be the result of firing a push or replace navigate event at appHistory with navigationType set to navigationType, isSameDocument set to true, destinationURL set to newURL, and classicHistoryAPISerializedData set to serializedData.
-
If continue is false, return.
-
Let appHistory be the current entry's document’s relevant global object's app history.
-
Let navigationType be the result of converting a history handling behavior to a navigation type given historyHandling.
-
Let continue be the result of firing a push or replace navigate event at appHistory given with navigationType set to navigationType, isSameDocument set to true, userInvolvement set to userInvolvement, and destinationURL set to url.
-
If continue is false, return.
4.4. History traversal updates
-
Let traversable be source browsing context’s containing navigable's traversable navigable.
-
Let initiatorOrigin be source browsing context’s active document's origin.
-
Enqueue the following steps to traversable’s session history traversal queue:
-
Let allSteps be the result of getting all history steps for traversable.
-
Let currentStepIndex be the index of the current session history step within allSteps.
-
Let targetStepIndex be currentStepIndex plus delta.
-
If allSteps[targetStepIndex] does not exist, then return.
-
Apply the history step allSteps[targetStepIndex] to traversable with true, step, initiatorOrigin, and userInvolvement.
-
-
Fire a traversal navigate event at previousDocument’s relevant global object's app history with destinationEntry set to targetEntry and userInvolvement set to userInvolvement.
5. Patches to session history
This section details monkeypatches to [HTML] to track appropriate data for associating an AppHistory with a session history entry.
5.1. New session history entry items
Each session history entry gains the following new items:
-
origin, an origin
-
app history key, a string, initially set to the result of generating a random UUID
-
app history id, a string, initially set to the result of generating a random UUID
-
app history state, which is serialized state or null, initially null
5.2. Carrying over the app history key
replace" case by adding the following step after the construction of newEntry:
-
If newEntry’s origin is the same as sessionHistory’s current entry's origin, then set newEntry’s app history key to sessionHistory’s current entry's app history key.
5.3. Carrying over the app history state
5.4. Tracking the origin member
Update the update the session history with the new page algorithm’s "replace" and "default" cases to set newEntry’s origin to newDocument’s origin as part of its creation.
Update the navigate to a fragment algorithm to set the new session history entry's origin to the current entry's document's origin.
Update the URL and history update steps algorithm to set the new session history entry's origin to document’s origin.
Potentially update the traverse the history algorithm to consult the new origin field, instead of checking the document's origin, since the document can disappear?? Needs further investigation.
5.5. Updating the AppHistory object
-
Update the entries of newDocument’s relevant global object's app history.
-
Update the entries of document’s relevant global object's app history.
We do not update the entries when initially creating a new browsing context, as we intentionally don’t want to include the initial about:blank Document in any app history entry list.
6. Other patches
6.1. Canceling navigation and traversals
The existing HTML specification discusses canceling a navigation and traverals in a few places. However, the process is not very well-defined, and per whatwg/html#6927, is not very interoperable. We plan to make it more rigorous, after the session history rewrite lands.
Specifically, the spec uses a few phrases:
-
"Cancel any existing but not-yet-mature attempts to navigate a browsing context", in the navigate algorithm and the traverse the history by a delta algorithm. This cancels any ongoing navigations, including history traversal navigations which have made their way back into the main event loop to perform an "
entry update" navigation. -
"Cancel that navigation", in the stop document loading algorithm. This is likely supposed to work the same as the above?
-
"Remove any tasks queued by the history traversal task source that are associated with any Document objects in the top-level browsing context’s document family." This cancels queued-up traversals that have not yet made their way back to the main event loop. This is currently called from any same-document navigations, i.e. the URL and history update steps and navigating to a fragment, as well as as part of traverse the history for cross-document traversals.
whatwg/html#6927 reveals that implementations don’t really follow this breakdown. In particular, modulo one case in Firefox, traversals are only canceled as part of discarding a browsing context.
That leaves us with two main operations: canceling not-yet-mature navigations, and dealing with browsing context discarding.
App history introduces a new complication here, which is that a navigation might have matured but still be "ongoing", in the sense of § 1.3 Ongoing navigation tracking. That is, consider a case such as:
appHistory. addEventListener( "navigate" , e=> { e. transitionWhile( new Promise( r=> setTimeout( r, 1 _000))); e. signal. addEventListener( "abort" , () => { ... }); }); const p= appHistory. navigate( "#1" ); setTimeout(() => window. stop(), 500 );
Without the navigate event handler, this kind of synchronous fragment navigation would be straightforward: it matures synchronously, and the stop() call does nothing. But because we have used the navigate handler to indicate that the navigation is still ongoing, we want the stop() call to finalize that navigation with an aborted navigation error, in particular causing p to reject and the abort event to fire on e.signal.
The integration is then as follows:
-
Wherever the spec ends up canceling not-yet-mature navigations for a browsing context bc, we also inform app history about canceling navigation in bc. (Regardless of whether or not there are any not-yet-mature navigations still in flight.)
-
When the spec discards a browsing context bc, we also inform app history about browsing context discarding given bc. (Regardless of whether or not there are any not-yet-mature navigations still in flight, or any traversals queued up.)