Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Specify back button history-entry skipping? #7832

Open
domenic opened this issue Apr 18, 2022 · 5 comments
Open

Specify back button history-entry skipping? #7832

domenic opened this issue Apr 18, 2022 · 5 comments
Labels
interop Implementations are not interoperable with each other topic: history topic: user activation

Comments

@domenic
Copy link
Member

domenic commented Apr 18, 2022

Per WICG/interventions#21, some browsers have implemented a heuristic where pressing the back button skips certain entries in the joint session history. Generally, these are entries where there is no user interaction. The intent is to avoid "back trapping", e.g. if you arrive on a malicious site which does history.pushState() 10 times, this makes it hard to escape the site by pressing the back button.

Notably, this is different than ignoring the history.pushState() calls entirely (which is explicitly allowed). The joint session history entries still exist from the page's point of view: e.g., history.length still increases, and history.back() still goes back to them. But the user pressing the back button will do the equivalent of history.go(-11) or similar, instead of history.go(-1).


The current HTML Standard sort of allows this:

The History interface is not meant to place restrictions on how implementations represent the session history to the user.

For example, session history could be implemented in a tree-like manner, with each page having multiple "forward" pages. This specification doesn't define how the linear list of pages in the history object are derived from the actual session history as seen from the user's perspective.

... but sort of doesn't:

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 user and the browsing context being operated on.

So at a minimum we should make it clearer that mapping the user expression to the delta might involve such considerations.


But we should probably go further than that. We should probably give more detail on which entries, exactly, will be skipped. Otherwise there are problematic interop problems, where e.g. pressing back will skip a history entry in one browser, and sites depend on it, but in other browsers that entry will not be skipped, leading to an inadvertent bad user experience.

@johannhof has mentioned that reverse-engineering Chromium's logic was a pain for Firefox (and still not shipped). And @miketaylr points out that there are many cases of "back button works in Chrome but not Firefox" bugs; my suspicion is that many of these are due to this issue (although others may be due to other history interop problems).

So even though this part of the web platform, dealing with how you translate user gestures on browser UI into web page actions, is traditionally outside the bounds of specs, in this case I think working on a specification for interoperable behavior would be appreciated. (As a compromise, I think the end result would be mostly "should"s, not "must"s.)


The bad news is that the heuristics here are still evolving. E.g. Chromium has some known bugs in our back-trapping prevention, which @shivanigithub and @creis have been looking at. So it's not even clear whether any browser has an implementation stable enough to spec. Maybe we can try to spec what we have, with the understanding that it will probably continue evolving? I just want to make sure we don't freeze this mapping in stone too early, by specifying it.

As part of this work we may also want to try coming up with more interoperable heuristics for ignoring pushState()/replaceState() calls, since that has similar user-facing effects. As introduced in #999, the spec just says "Optionally, return" with no guidance.

@croxton
Copy link

croxton commented Oct 19, 2022

Hi Domenic, we've run into an issue with history.pushState() in Safari on iOS, when calls are made approx 500ms after user interaction. This breaks back-button behaviour in our apps when page requests take a while to complete, and leads to some confusing behaviour for users. You seem to have some knowledge of which browsers have implemented interventions to the history API - would you mind sharing some details about which browsers are affected and any details you feel able to of their respective interventions? It's very hard to find this kind of information anywhere and it would be super useful in finding a solution that works universally. Many thanks in advance!

@NetMage
Copy link

NetMage commented Nov 23, 2022

Not sure where to mention this, but I think it is also confusing that the context menu Back does not skip the skippable history entries the same way the toolbar back button does, which in my case causes my mouse button and backspace extension to behave differently as well - they apparently emulate the context menu back.

@domenic domenic mentioned this issue Mar 6, 2023
4 tasks
@whatwg whatwg deleted a comment from 21triple May 27, 2023
domenic added a commit that referenced this issue Jun 26, 2023
This imports much of the specification from https://github.com/WICG/navigation-api/blob/91c2e7f959418d71e26e5b9a6723fed2944001d9/spec.bs. It includes some fixes and renamings, as well as a lot of rearranging to flow better, but the feature set is the same.

In addition to the new "navigation API" section under "APIs related to navigation and session history", this makes the following changes to integrate the navigation API:

* Introduces the NavigationHistoryBehavior enum, as a superset of the "history handling" specification type. This includes an "auto" value to reflect that existing entry points will sometimes default to "replace" instead of "push". The navigation API allows overriding that auto behavior in some circumstances by explicitly supplying "push". (But in some cases, i.e., the initial about:blank or javascript: URLs, this is still not allowed.)

* Introduces the concept of "user navigation involvement". This will be helpful for solving issues such as w3c/webappsec-fetch-metadata#71.

* Introduces the concept of "history-action user activation", which is a separate type of user activation meant to be used specifically to prevent back button tracking. It has no associated timeout (unlike transient activation), but can be flipped to false (unlike sticky activation). It is used by the navigation API to prevent calling navigateEvent.preventDefault() on two traversals in a row without intervening user activation. It is likely also to be useful for issues such as #7832.

* The activation behavior for <a> and <area> elements is made more rigorous and consolidated into one place, so that we can hook into it appropriately to fire navigate events.

* Some surgery was needed on "apply the history step". The result is that it now has slightly more parameters, but also several wrapper algorithms which take care of setting up those parameters correctly for the cases of: navigable creation/destruction, push/replace, reload, and traverse. It also returns a value indicating if the application of the history step was canceled, and if so, how; this is used to give informative errors as return values of navigation.traverseTo().

* The "check if unloading is user-canceled" algorithm has become "check if unloading is canceled", as it now handles firing a possibly-cancelable navigate event at the top-level traversable.

Other changes scattered throughout are mostly integrating appropriate calls to the navigation API as necessary, especially to fire navigate events.
rubberyuzu pushed a commit to rubberyuzu/html that referenced this issue Jul 20, 2023
This imports much of the specification from https://github.com/WICG/navigation-api/blob/91c2e7f959418d71e26e5b9a6723fed2944001d9/spec.bs. It includes some fixes and renamings, as well as a lot of rearranging to flow better, but the feature set is the same.

In addition to the new "navigation API" section under "APIs related to navigation and session history", this makes the following changes to integrate the navigation API:

* Introduces the NavigationHistoryBehavior enum, as a superset of the "history handling" specification type. This includes an "auto" value to reflect that existing entry points will sometimes default to "replace" instead of "push". The navigation API allows overriding that auto behavior in some circumstances by explicitly supplying "push". (But in some cases, i.e., the initial about:blank or javascript: URLs, this is still not allowed.)

* Introduces the concept of "user navigation involvement". This will be helpful for solving issues such as w3c/webappsec-fetch-metadata#71.

* Introduces the concept of "history-action user activation", which is a separate type of user activation meant to be used specifically to prevent back button tracking. It has no associated timeout (unlike transient activation), but can be flipped to false (unlike sticky activation). It is used by the navigation API to prevent calling navigateEvent.preventDefault() on two traversals in a row without intervening user activation. It is likely also to be useful for issues such as whatwg#7832.

* The activation behavior for <a> and <area> elements is made more rigorous and consolidated into one place, so that we can hook into it appropriately to fire navigate events.

* Some surgery was needed on "apply the history step". The result is that it now has slightly more parameters, but also several wrapper algorithms which take care of setting up those parameters correctly for the cases of: navigable creation/destruction, push/replace, reload, and traverse. It also returns a value indicating if the application of the history step was canceled, and if so, how; this is used to give informative errors as return values of navigation.traverseTo().

* The "check if unloading is user-canceled" algorithm has become "check if unloading is canceled", as it now handles firing a possibly-cancelable navigate event at the top-level traversable.

Other changes scattered throughout are mostly integrating appropriate calls to the navigation API as necessary, especially to fire navigate events.
rubberyuzu pushed a commit to rubberyuzu/html that referenced this issue Jul 21, 2023
This imports much of the specification from https://github.com/WICG/navigation-api/blob/91c2e7f959418d71e26e5b9a6723fed2944001d9/spec.bs. It includes some fixes and renamings, as well as a lot of rearranging to flow better, but the feature set is the same.

In addition to the new "navigation API" section under "APIs related to navigation and session history", this makes the following changes to integrate the navigation API:

* Introduces the NavigationHistoryBehavior enum, as a superset of the "history handling" specification type. This includes an "auto" value to reflect that existing entry points will sometimes default to "replace" instead of "push". The navigation API allows overriding that auto behavior in some circumstances by explicitly supplying "push". (But in some cases, i.e., the initial about:blank or javascript: URLs, this is still not allowed.)

* Introduces the concept of "user navigation involvement". This will be helpful for solving issues such as w3c/webappsec-fetch-metadata#71.

* Introduces the concept of "history-action user activation", which is a separate type of user activation meant to be used specifically to prevent back button tracking. It has no associated timeout (unlike transient activation), but can be flipped to false (unlike sticky activation). It is used by the navigation API to prevent calling navigateEvent.preventDefault() on two traversals in a row without intervening user activation. It is likely also to be useful for issues such as whatwg#7832.

* The activation behavior for <a> and <area> elements is made more rigorous and consolidated into one place, so that we can hook into it appropriately to fire navigate events.

* Some surgery was needed on "apply the history step". The result is that it now has slightly more parameters, but also several wrapper algorithms which take care of setting up those parameters correctly for the cases of: navigable creation/destruction, push/replace, reload, and traverse. It also returns a value indicating if the application of the history step was canceled, and if so, how; this is used to give informative errors as return values of navigation.traverseTo().

* The "check if unloading is user-canceled" algorithm has become "check if unloading is canceled", as it now handles firing a possibly-cancelable navigate event at the top-level traversable.

Other changes scattered throughout are mostly integrating appropriate calls to the navigation API as necessary, especially to fire navigate events.
CarwynNelson added a commit to CarwynNelson/smallweb that referenced this issue Oct 28, 2023
See: whatwg/html#7832

Safari does history-entry skipping, which breaks back/forward buttons
even though it works just fine on Firefox.
@raulsebastianmihaila
Copy link

@domenic what do you think of a permissions API to allow an app to manipulate history entries in a 'non-skippable' way. The app would use the API and the user would be asked for permission right before the browser decides that some entry should be skipped and if the user provides the permission then the browser entries are not skipped anymore. I described other options as well here: https://issues.chromium.org/issues/330744614#comment20

@domenic
Copy link
Member Author

domenic commented May 10, 2024

I don't think this is something most browser UI teams would be interested in bothering the user about. The bar for a new permission is very high and needs to be something easily understood, like "microphone" or "camera" or "geolocation".

@davideperozzi
Copy link

davideperozzi commented Nov 8, 2024

@domenic I don't know if this is the right place to comment on this/ask about it. If not, feel free to remove my comment.

I came across this issue after wanting to trigger "pushState" on different sections on my website/app while the user is scrolling through (with the ability to switch back and forth via "popstate" of course). The App isn't using the default scrolling, but rather using the "wheel" event to add special behaviour. Is it planned to consider "scroll" or "wheel" events as user actions too?

I understand that "invoked in succession", these events could be used to push a lot of states to the history. Would something like a timeout or filter mechanism work here? So rules like: "The scroll event triggering the history push has to be one viewport apart from each other"? I read some comments in the google groups, but maybe I'm missing something that's fundamental here. It's working in Firefox for me right now, but I also don't know if they have a filter mechanism or special rules to prevent history spam.

Also just out of curiosity, would a click event be "tracked"? Lets say I click a button and on that click it triggers another button click programatically (with e.g. button.click()) and this (programatically called) click will push a state to the history. Is this considered a user action or would it be blocked?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
interop Implementations are not interoperable with each other topic: history topic: user activation
Development

No branches or pull requests

5 participants