Charlie Reis | 358d23a | 2020-06-15 23:20:52 | [diff] [blame] | 1 | # Session History |
| 2 | |
| 3 | A browser's session history keeps track of the navigations in each tab, to |
| 4 | support back/forward navigations and session restore. This is in contrast to |
| 5 | "history" (e.g., `chrome://history`), which tracks the main frame URLs the user |
| 6 | has visited in any tab for the lifetime of a profile. |
| 7 | |
| 8 | Chromium tracks the session history of each tab in NavigationController, using a |
| 9 | list of NavigationEntry objects to represent the joint session history items. |
| 10 | Each frame creates _session history items_ as it navigates. A _joint session |
| 11 | history item_ contains the state of each frame of a page at a given point in |
| 12 | time, including things like URL, partially entered form data, scroll position, |
| 13 | etc. Each NavigationEntry uses a tree of FrameNavigationEntries to track this |
| 14 | state. |
| 15 | |
| 16 | [TOC] |
| 17 | |
| 18 | |
| 19 | ## Pruning Forward Navigations |
| 20 | |
| 21 | If the user goes back and then commits a new navigation, this essentially forks |
| 22 | the joint session history. However, joint session history is tracked as a list |
| 23 | and not as a tree, so the previous forward history is "pruned" and forgotten. |
| 24 | This pruning is performed for all new navigations, unless they commit with |
| 25 | replacement. |
| 26 | |
| 27 | |
| 28 | ## Subframe Navigations |
| 29 | |
| 30 | When the first commit occurs within a new subframe of a document, it becomes |
| 31 | part of the existing joint session history item (which we refer to as an "auto |
| 32 | subframe navigation"). The user can't go back to the state before the frame |
| 33 | committed. Any subsequent navigations in the subframe create new joint session |
| 34 | history items (which we refer to as "manual subframe navigations"), such that |
| 35 | clicking back goes back within the subframe. |
| 36 | |
| 37 | |
| 38 | ## Navigating with Replacement |
| 39 | |
| 40 | Some types of navigations can replace the previously committed joint session |
| 41 | history item for a frame, rather than creating a new item. These include: |
| 42 | |
| 43 | * `location.replace` (which is usually cross-document, unless it is a fragment |
| 44 | navigation) |
| 45 | * `history.replaceState` (which is always same-document) |
| 46 | * Client redirects |
| 47 | * The first non-blank URL after the initial empty document (unless the frame |
| 48 | was explicitly created with `about:blank` as the URL). |
| 49 | |
| 50 | |
| 51 | ## Identifying Same- and Cross-Document Navigations |
| 52 | |
| 53 | Each FrameNavigationEntry contains both an _item sequence number_ (ISN) and a |
| 54 | _document sequence number_ (DSN). Same-document navigations create a new session |
| 55 | history item without changing the document, and thus have a new ISN but the same |
| 56 | DSN. Cross-document navigations create a new ISN and DSN. NavigationController |
| 57 | uses these ISNs and DSNs when deciding which frames need to be navigated during |
| 58 | a session history navigation, using a recursive frame tree walk in |
| 59 | `FindFramesToNavigate`. |
| 60 | |
| 61 | |
| 62 | ## Classifying Navigations |
| 63 | |
| 64 | Much of the complexity in NavigationController comes from the bookkeeping needed |
| 65 | to track the various types of navigations as they commit (e.g., same-document vs |
| 66 | cross-document, main frame vs subframe, with or without replacement, etc). These |
| 67 | types may lead to different outcomes for whether a new NavigationEntry is |
| 68 | created, whether an existing one is updated vs replaced, and what events are |
| 69 | exposed to observers. This is handled by `ClassifyNavigation`, which determines |
| 70 | which `RendererDidNavigate` helper methods are used when a navigation commits. |
| 71 | |
| 72 | |
| 73 | ## Persistence |
| 74 | |
| 75 | The joint session history of a tab is persisted so that tabs can be restored |
| 76 | (e.g., between Chromium restarts, after closing a tab, or on another device). |
| 77 | This requires serializing the state in each NavigationEntry and its tree of |
| 78 | FrameNavigationEntries, using a PageState object and other metadata. |
W. James MacLean | 4aeb4b0 | 2023-03-08 20:48:57 | [diff] [blame] | 79 | See [Modifying Session History Serialization](modifying_session_history_serialization.md) |
| 80 | for how to safely add new values to be saved and restored. |
Charlie Reis | 358d23a | 2020-06-15 23:20:52 | [diff] [blame] | 81 | |
| 82 | Not everything in NavigationEntry is persisted. All data members of |
| 83 | NavigationEntryImpl and FrameNavigationEntry should be documented with whether |
| 84 | they are preserved after commit and whether they need to be persisted. |
| 85 | |
| 86 | Note that the session history of a tab can also be cloned when duplicating a |
| 87 | tab, or when doing a back/forward/reload navigation in a new tab (such as when |
| 88 | middle-clicking the back/forward/reload button). This involves direct clones of |
| 89 | NavigationEntries rather than persisting and restoring. |
| 90 | |
| 91 | ## Invariants |
| 92 | |
| 93 | * The `pending_entry_index_` is either -1 or an index into `entries_`. If |
| 94 | `pending_entry_` is defined and `pending_entry_index_` is -1, then it is a |
| 95 | new navigation. If `pending_entry_index_` is a valid index into `entries_`, |
| 96 | then `pending_entry_` must point to that entry and it is a session history |
| 97 | navigation. |
| 98 | * Newly created tabs have NavigationControllers with `is_initial_navigation_` |
| 99 | set to true. They can have `last_committed_entry_index_` defined before the |
| 100 | first commit, however, when session history is cloned from another tab. (In |
| 101 | this case, `pending_entry_index_` indicates which entry is going to be |
| 102 | restored during the initial navigation.) |
| 103 | * Every FrameNavigationEntry that has committed in the current session (as |
| 104 | opposed to those that have been restored) must have a SiteInstance. |
| 105 | * A renderer process can only update FrameNavigationEntries belonging to a |
| 106 | SiteInstance in that process. This especially includes attacker-controlled |
| 107 | data like PageState, which could be dangerous to load into a different |
| 108 | site's process. |
| 109 | * Any cross-SiteInstance navigation should result in a new NavigationEntry |
| 110 | with replacement, rather than updating an existing NavigationEntry. |
| 111 | |
| 112 | |
| 113 | ## Caveats |
| 114 | |
| 115 | * Not every NavigationRequest has a pending NavigationEntry. For example, |
| 116 | subframe navigations do not, and renderer-initiated main frame navigations |
| 117 | may clear an existing browser-initiated pending NavigationEntry (using |
| 118 | PendingEntryRef) without replacing it with a new one. |
Charlie Reis | 358d23a | 2020-06-15 23:20:52 | [diff] [blame] | 119 | * Some subframe documents may not have a corresponding FrameNavigationEntry |
| 120 | after commit (e.g., see [issue 608402](https://crbug.com/608402)). |