HTML

Living Standard — Last Updated 27 September 2023

    1. 7.4 Navigation and session history
      1. 7.4.1 Session history
        1. 7.4.1.1 Session history entries
        2. 7.4.1.2 Document state
        3. 7.4.1.3 Centralized modifications of session history
        4. 7.4.1.4 Low-level operations on session history
      2. 7.4.2 Navigation
        1. 7.4.2.1 Supporting concepts
        2. 7.4.2.2 Beginning navigation
        3. 7.4.2.3 Ending navigation
          1. 7.4.2.3.1 The usual cross-document navigation case
          2. 7.4.2.3.2 The javascript: URL special case
          3. 7.4.2.3.3 Fragment navigations
          4. 7.4.2.3.4 Non-fetch schemes and external software
        4. 7.4.2.4 Preventing navigation
        5. 7.4.2.5 Aborting navigation
      3. 7.4.3 Reloading and traversing
      4. 7.4.4 Non-fragment synchronous "navigations"
      5. 7.4.5 Populating a session history entry
      6. 7.4.6 Applying the history step
        1. 7.4.6.1 Updating the traversable
        2. 7.4.6.2 Updating the document
        3. 7.4.6.3 Scrolling to a fragment
        4. 7.4.6.4 Persisted history entry state

Welcome to the dragon's maw. Navigation, session history, and the traversal through that session history are some of the most complex parts of this standard.

The basic concept may not seem so difficult:

You can see some of the intertwined complexity peeking through here, in how traversal can cause a navigation (i.e., a network fetch to a stored URL), and how a navigation necessarily needs to interface with the session history list to ensure that when it finishes the user is looking at the right thing. But the real problems come in with the various edge cases and interacting web platform features:

In what follows, we have attempted to guide the reader through these complexities by appropriately cordoning them off into labeled sections and algorithms, and giving appropriate words of introduction where possible. Nevertheless, if you wish to truly understand navigation and session history, the usual advice will be invaluable.

7.4.1 Session history

7.4.1.1 Session history entries

A session history entry is a struct with the following items:

To get a session history entry's document, return its document state's document.


Serialized state is a serialization (via StructuredSerializeForStorage) of an object representing a user interface state. We sometimes informally refer to "state objects", which are the objects representing user interface state supplied by the author, or alternately the objects created by deserializing (via StructuredDeserialize) serialized state.

Pages can add serialized state to the session history. These are then deserialized and returned to the script when the user (or script) goes back in the history, thus enabling authors to use the "navigation" metaphor even in one-page applications.

Serialized state is intended to be used for two main purposes: first, storing a preparsed description of the state in the URL so that in the simple case an author doesn't have to do the parsing (though one would still need the parsing for handling URLs passed around by users, so it's only a minor optimization). Second, so that the author can store state that one wouldn't store in the URL because it only applies to the current Document instance and it would have to be reconstructed if a new Document were opened.

An example of the latter would be something like keeping track of the precise coordinate from which a popup div was made to animate, so that if the user goes back, it can be made to animate to the same location. Or alternatively, it could be used to keep a pointer into a cache of data that would be fetched from the server based on the information in the URL, so that when going back and forward, the information doesn't have to be fetched again.


A scroll restoration mode indicates whether the user agent should restore the persisted scroll position (if any) when traversing to an entry. A scroll restoration mode is one of the following:

"auto"
The user agent is responsible for restoring the scroll position upon navigation.
"manual"
The page is responsible for restoring the scroll position and the user agent does not attempt to do so automatically
7.4.1.2 Document state

Document state holds state inside a session history entry regarding how to present and, if necessary, recreate, a Document. It has:

User agents may destroy the documents of document states with non-null documents, as long as the Document is not fully active.

Apart from that restriction, this standard does not specify when user agents should destroy the document stored in a document state, versus keeping it cached.


A POST resource has:


A nested history has:

This will later contain ways to identify a child navigable across reloads.



Several contiguous entries in a session history can share the same document state. This can occur when the initial entry is reached via normal navigation, and the following entry is added via history.pushState(). Or it can occur via navigation to a fragment.

All entries that share the same document state (and that are therefore merely different states of one particular document) are contiguous by construction.


A Document has a latest entry, a session history entry or null.

This is the entry that was most recently represented by a given Document. A single Document can represent many session history entries over time, as many contiguous session history entries can share the same document state as explained above.

7.4.1.3 Centralized modifications of session history

To maintain a single source of truth, all modifications to a traversable navigable's session history entries need to be synchronized. This is especially important due to how session history is influenced by all of the descendant navigables, and thus by multiple event loops. To accomplish this, we use the session history traversal parallel queue structure.

A session history traversal parallel queue is very similar to a parallel queue. It has an algorithm set, an ordered set.

The items in a session history traversal parallel queue's algorithm set are either algorithm steps, or synchronous navigation steps, which are a particular brand of algorithm steps involving a target navigable (a navigable).

To append session history traversal steps to a traversable navigable traversable given algorithm steps steps, append steps to traversable's session history traversal queue's algorithm set.

To append session history synchronous navigation steps involving a navigable targetNavigable to a traversable navigable traversable given algorithm steps steps, append steps as synchronous navigation steps targeting target navigable targetNavigable to traversable's session history traversal queue's algorithm set.

To start a new session history traversal parallel queue:

  1. Let sessionHistoryTraversalQueue be a new session history traversal parallel queue.

  2. Run the following steps in parallel:

    1. While true:

      1. If sessionHistoryTraversalQueue's algorithm set is empty, then continue.

      2. Let steps be the result of dequeuing from sessionHistoryTraversalQueue's algorithm set.

      3. Run steps.

  3. Return sessionHistoryTraversalQueue.

Synchronous navigation steps are tagged in the algorithm set to allow them to conditionally "jump the queue". This is handled within apply the history step.

Imagine the joint session history depicted by this Jake diagram:

01
top/a/b

And the following code runs at the top level:

history.back();
location.href = '#foo';

The desired result is:

012
top/a/b/b#foo

This isn't straightforward, as the sync navigation wins the race in terms of being observable, whereas the traversal wins the race in terms of queuing steps on the session history traversal parallel queue. To achieve this result, the following happens:

  1. history.back() appends steps intended to traverse by a delta of −1.

  2. location.href = '#foo' synchronously changes the active session history entry entry to a newly-created one, with the URL /b#foo, and appends synchronous steps to notify the central source of truth about that new entry. Note that this does not yet update the current session history entry, current session history step, or the session history entries list; those updates cannot be done synchronously, and instead must be done as part of the queued steps.

  3. On the session history traversal parallel queue, the steps queued by history.back() run:

    1. The target history step is determined to be 0: the current session history step (i.e., 1) plus the intended delta of −1.

    2. We enter the main apply the history step algorithm.

      The entry at step 0, for the /a URL, has its document populated.

      Meanwhile, the queue is checked for synchronous navigation steps. The steps queued by the location.href setter now run, and block the traversal from performing effects beyond document population (such as, unloading documents and switching active history entries) until they are finished. Those steps cause the following to happen:

      1. The entry with URL /b#foo is added, with its step determined to be 2: the current session history step (i.e., 1) plus 1.

      2. We fully switch to that newly added entry, including a nested call to apply the history step. This ultimately results in updating the document by dispatching events like hashchange.

      Only once that is all complete, and the /a history entry has been fully populated with a document, do we move on with applying the history step given the target step of 0.

      At this point, the Document with URL /b#foo unloads, and we finish moving to our target history step 0, which makes the entry with URL /a become the active session history entry and 0 become the current session history step.

Here is another more complex example, involving races between populating two different iframes, and a synchronous navigation once one of those iframes loads. We start with this setup:

012
top/t
frames[0]/i-0-a/i-0-b
frames[1]/i-1-a/i-1-b

and then call history.go(-2). The following then occurs:

  1. history.go(-2) appends steps intended to traverse by a delta of −2. Once those steps run:

    1. The target step is determined to be 2 + (−2) = 0.

    2. In parallel, the fetches are made to populate the two iframes, fetching /i-0-a and /i-1-a respectively.

      Meanwhile, the queue is checked for synchronous navigation steps. There aren't any right now.

    3. In the fetch race, the fetch for /i-0-a wins. We proceed onward to finish all of apply the history step's work for how the traversal impacts the frames[0] navigable, including updating its active session history entry to the entry with URL /i-0-a.

    4. Before the fetch for /i-1-a finishes, we reach the point where scripts may run for the newly-created document in the frames[0] navigable's active document. Some such script does run:

      location.href = '#foo'

      This synchronously changes the frames[0] navigable's active session history entry entry to a newly-created one, with the URL /i-0-a#foo, and appends synchronous steps to notify the central source of truth about that new entry.

      Unlike in the previous example, these synchronous steps do not "jump the queue" and update the traversable before we finish the fetch for /i-1-a. This is because the navigable in question, frames[0], has already been altered as part of the traversal, so we know that with the current session history step being 2, adding the new entry as a step 3 doesn't make sense.

    5. Once the fetch for /i-1-a finally finishes, we proceed to finish updating the frames[1] navigable for the traversal, including updating its active session history entry to the entry with URL /i-1-a.

    6. Now that both navigables have finished processing the traversal, we update the current session history step to the target step of 0.

  2. Now we can process the steps that were queued for the synchronous navigation:

    1. The /i-0-a#foo entry is added, with its step determined to be 1: the current session history step (i.e., 0) plus 1. This also clears existing forward history.

    2. We fully switch to that newly added entry, including calling apply the history step. This ultimately results in updating the document by dispatching events like hashchange, as well as updating the current session history step to the target step of 1.

The end result is:

01
top/t
frames[0]/i-0-a/i-0-a#foo
frames[1]/i-1-a
7.4.1.4 Low-level operations on session history

This section contains a miscellaneous grab-bag of operations that we perform throughout the standard when manipulating session history. The best way to get a sense of what they do is to look at their call sites.

To get session history entries of a navigable navigable:

  1. Let traversable be navigable's traversable navigable.

  2. Assert: this is running within traversable's session history traversal queue.

  3. If navigable is traversable, return traversable's session history entries.

  4. Let docStates be an empty ordered set of document states.

  5. For each entry of traversable's session history entries, append entry's document state to docStates.

  6. For each docState of docStates:

    1. For each nestedHistory of docState's nested histories:

      1. If nestedHistory's id equals navigable's id, return nestedHistory's entries.

      2. For each entry of nestedHistory's entries, append entry's document state to docStates.

  7. Assert: this step is not reached.

To get session history entries for the navigation API of a navigable navigable given an integer targetStep:

  1. Let rawEntries be the result of getting session history entries for navigable.

  2. Let entriesForNavigationAPI be a new empty list.

  3. Let startingIndex be the index of the session history entry in rawEntries who has the greatest step less than or equal to targetStep.

    See this example to understand why it's the greatest step less than or equal to targetStep.

  4. Append rawEntries[startingIndex] to entriesForNavigationAPI.

  5. Let startingOrigin be rawEntries[startingIndex]'s document state's origin.

  6. Let i be startingIndex − 1.

  7. While i > 0:

    1. If rawEntries[i]'s document state's origin is not same origin with startingOrigin, then break.

    2. Prepend rawEntries[i] to entriesForNavigationAPI.

    3. Set i to i − 1.

  8. Set i to startingIndex + 1.

  9. While i < rawEntries's size:

    1. If rawEntries[i]'s document state's origin is not same origin with startingOrigin, then break.

    2. Append rawEntries[i] to entriesForNavigationAPI.

    3. Set i to i + 1.

  10. Return entriesForNavigationAPI.

To clear the forward session history of a traversable navigable navigable:

  1. Assert: this is running within navigable's session history traversal queue.

  2. Let step be the navigable's current session history step.

  3. Let entryLists be the ordered set « navigable's session history entries ».

  4. For each entryList of entryLists:

    1. Remove every session history entry from entryList that has a step greater than step.

    2. For each entry of entryList:

      1. For each nestedHistory of entry's document state's nested histories, append nestedHistory's entries list to entryLists.

To get all used history steps that are part of traversable navigable traversable:

  1. Assert: this is running within traversable's session history traversal queue.

  2. Let steps be an empty ordered set of non-negative integers.

  3. Let entryLists be the ordered set « traversable's session history entries ».

  4. For each entryList of entryLists:

    1. For each entry of entryList:

      1. Append entry's step to steps.

      2. For each nestedHistory of entry's document state's nested histories, append nestedHistory's entries list to entryLists.

  5. Return steps, sorted.

Certain actions cause a navigable to navigate to a new resource.

For example, following a hyperlink, form submission, and the window.open() and location.assign() methods can all cause navigation.

Although in this standard the word "navigation" refers specifically to the navigate algorithm, this doesn't always line up with web developer or user perceptions. For example:

Before we can jump into the navigation algorithm itself, we need to establish several important structures that it uses.

The source snapshot params struct is used to capture data from a Document initiating a navigation. It is snapshotted at the beginning of a navigation and used throughout the navigation's lifetime. It has the following items:

has transient activation
a boolean
sandboxing flags
a sandboxing flag set
allows downloading
a boolean
fetch client
an environment settings object, only to be used as a request client
source policy container
a policy container

To snapshot source snapshot params given a Document sourceDocument, return a new source snapshot params with

has transient activation
true if sourceDocument's relevant global object has transient activation; otherwise false
sandboxing flags
sourceDocument's active sandboxing flag set
allows downloading
false if sourceDocument's active sandboxing flag set has the sandboxed downloads browsing context flag set; otherwise true
fetch client
sourceDocument's relevant settings object
source policy container
sourceDocument's policy container

The target snapshot params struct is used to capture data from a navigable being navigated. Like source snapshot params, it is snapshotted at the beginning of a navigation and used throughout the navigation's lifetime. It has the following items:

sandboxing flags
a sandboxing flag set

To snapshot target snapshot params given a navigable targetNavigable, return a new target snapshot params with sandboxing flags set to the result of determining the creation sandboxing flags given targetNavigable's active browsing context and targetNavigable's container.


Much of the navigation process is concerned with determining how to create a new Document, which ultimately happens in the create and initialize a Document object algorithm. The parameters to that algorithm are tracked via a navigation params struct, which has the following items:

id
null or a navigation ID
navigable
the navigable to be navigated
request
null or a request that started the navigation
response
a response that ultimately was navigated to (potentially a network error)
fetch controller
null or a fetch controller
commit early hints
null or an algorithm accepting a Document, once it has been created
COOP enforcement result
a cross-origin opener policy enforcement result, used for reporting and potentially for causing a browsing context group switch
reserved environment
null or an environment reserved for the new Document
origin
an origin to use for the new Document
policy container
a policy container to use for the new Document
final sandboxing flag set
a sandboxing flag set to impose on the new Document
cross-origin opener policy
a cross-origin opener policy to use for the new Document
navigation timing type
a NavigationTimingType used for creating the navigation timing entry for the new Document
about base URL
a URL or null used to populate the new Document's about base URL

Once a navigation params struct is created, this standard does not mutate any of its items. They are only passed onward to other algorithms.


A navigation ID is a UUID string generated during navigation. It is used to interface with the WebDriver BiDi specification as well as to track the ongoing navigation. [WEBDRIVERBIDI]


After Document creation, the relevant traversable navigable's session history gets updated. The NavigationHistoryBehavior enumeration is used to indicate the desired type of session history update to the navigate algorithm. It is one of the following:

"push"
A regular navigation which adds a new session history entry, and will clear the forward session history.
"replace"
A navigation that will replace the active session history entry.
"auto"
The default value, which will be converted very early in the navigate algorithm into "push" or "replace". Usually it becomes "push", but under certain circumstances it becomes "replace" instead.

A history handling behavior is a NavigationHistoryBehavior that is either "push" or "replace", i.e., that has been resolved away from any initial "auto" value.

The navigation must be a replace, given a URL url and a Document document, if any of the following are true:

Other cases that often, but not always, force a "replace" navigation are:


Various parts of the platform track whether a user is involved in a navigation. A user navigation involvement 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.

For convenience at certain call sites, the user navigation involvement for an event is defined as follows:

  1. Assert: this algorithm is being called as part of an activation behavior definition.

  2. Assert: event's click".

  3. If event's activation".

  4. Return "none".

7.4.2.2 Beginning navigation

To navigate a navigable navigable to a URL url using a Document sourceDocument, with an optional POST resource, string, or null documentResource (default null), an optional response-or-null response (default null), an optional boolean exceptionsEnabled (default false), an optional NavigationHistoryBehavior historyHandling (default "auto"), an optional serialized state-or-null navigationAPIState (default null), an optional entry list or null formDataEntryList (default null), an optional referrer policy referrerPolicy (default the empty string), and an optional user navigation involvement userInvolvement (default "none"):

  1. Let cspNavigationType be "form-submission" if formDataEntryList is non-null; otherwise "other".

  2. Let sourceSnapshotParams be the result of snapshotting source snapshot params given sourceDocument.

  3. Let initiatorOriginSnapshot be sourceDocument's origin.

  4. Let initiatorBaseURLSnapshot be sourceDocument's document base URL.