shivanigithub | 2190650b | 2022-10-13 02:01:32 | [diff] [blame] | 1 | # History manipulation intervention in Chromium |
| 2 | |
| 3 | Reference: [PSA on blink-dev](https://groups.google.com/a/chromium.org/g/blink-dev/c/T8d4_BRb2xQ/m/WSdOiOFcBAAJ) |
| 4 | |
| 5 | ## Summary |
| 6 | Some pages make it difficult or impossible for the user to use the browser back |
| 7 | button to go back to the page they came from. Pages accomplish this using |
| 8 | redirects or by manipulating the browser history, resulting in an |
| 9 | abusive/annoying user experience. |
| 10 | |
| 11 | The history manipulation intervention mitigates such abuse by making the |
| 12 | browser’s back button skip over pages that added history entries or redirected |
| 13 | the user without ever getting a user activation. Note that the intervention only |
| 14 | impacts the browser back/forward buttons and not the `history.back()/forward()` |
| 15 | APIs. |
| 16 | |
| 17 | Here’s an example: |
| 18 | 1) User is on a.com and clicks to go to b.com |
| 19 | 2) b.com adds a history entry using `pushState` or navigates the user to another |
| 20 | page (c.com) without ever getting a user activation. |
| 21 | 3) If the user presses back, the browser will skip b.com and go back to a.com |
| 22 | instead. |
| 23 | |
| 24 | ## Spec |
| 25 | Because this only impacts browser UI, this is allowed by the spec, which only |
| 26 | governs the behavior of `history.back/forward`. |
| 27 | However, it might be good to spec this anyway, so that users get consistent |
| 28 | experiences in all browsers. That work is tracked at |
| 29 | https://github.com/whatwg/html/issues/7832 |
| 30 | |
| 31 | ## Invariants |
Shivani Sharma | eef521b | 2024-01-18 13:03:56 | [diff] [blame] | 32 | At a high level, the intervention ensures the back/forward buttons always |
| 33 | navigate to a page the user either navigated to or interacted with. It |
| 34 | guarantees the following invariants: |
shivanigithub | 2190650b | 2022-10-13 02:01:32 | [diff] [blame] | 35 | 1. Only back/forward navigations triggered by the back/forward buttons will ever |
| 36 | skip history entries. This ensures that the history API's behavior is |
| 37 | unaffected. |
| 38 | 2. The intervention marks a history entry as skippable if the document creates |
| 39 | another history entry without a user activation. |
| 40 | 3. If a document receives a user activation (before or after creating history |
| 41 | entries), its history entry is not skippable. With an activation, the |
| 42 | document can create many unskippable same-document history entries, until |
Shivani Sharma | eef521b | 2024-01-18 13:03:56 | [diff] [blame] | 43 | either a cross-document navigation or a back/forward occurs. Note that |
| 44 | same-document back/forwards do not normally reset any prior user activation, |
| 45 | but the intervention stops honoring such activations for creating new |
| 46 | entries until a new activation is received, per https://crbug.com/1248529. |
shivanigithub | 2190650b | 2022-10-13 02:01:32 | [diff] [blame] | 47 | 4. All same-document history entries will have the same skippable state. When |
| 48 | marking an entry unskippable after a user activation, this ensures that the |
| 49 | rest of the document's entries work as well. When marking an entry as |
| 50 | skippable, this ensures that all entries for the offending document will be |
| 51 | skipped. |
| 52 | 5. Revisiting a skippable history entry does not change its skippable status, |
| 53 | unless it receives a user activation. This ensures that history.back() will |
| 54 | not bypass the intervention, per https://crbug.com/1121293. |
| 55 | 6. The intervention applies to history entries created by subframes as well. A |
| 56 | user activation on any frame on the page is sufficient to make the entry |
| 57 | unskippable, per https://crbug.com/953056. |
| 58 | |
| 59 | ## Details |
| 60 | 1. The intervention works by setting the `should_skip_on_back_forward_ui_` |
| 61 | member for a `NavigationEntryImpl` object. The member is initially set to |
| 62 | false, and it is set to true if any document in the page adds a history entry |
| 63 | without having a user activation. |
| 64 | 2. `NavigationController::CanGoBack()` will return false if all entries are |
| 65 | marked to be skipped on back/forward UI. On desktop this leads to the back |
| 66 | button being disabled. On Android, pressing the back button will close the |
| 67 | current tab and a previous tab could be shown as it would normally happen on |
| 68 | Android when the back button is pressed from the first entry of a tab. |
| 69 | 3. The oldest `NavigationEntryImpl` that is marked as skippable is the one |
| 70 | that is pruned if max entry count is reached. |