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

Should navigating to the current URL preserve history.state? #6213

Open
domenic opened this issue Dec 10, 2020 · 7 comments
Open

Should navigating to the current URL preserve history.state? #6213

domenic opened this issue Dec 10, 2020 · 7 comments
Labels
interop Implementations are not interoperable with each other topic: history

Comments

@domenic
Copy link
Member

domenic commented Dec 10, 2020

Spinning off from #6197 (comment).

Test page: https://boom-bath.glitch.me/session-history-2.html

  1. Press "Set history.state to 'foo'"
  2. Then press any of the other three four buttons/links.

Results:

  • Firefox: location.reload() retains history.state. All other mechanisms clear it.
  • Chrome: history.state is always retained.
  • Safari: ???

Also of note: both browsers agree that overall document scroll position is retained on location.reload() and cleared otherwise. Chrome, however, clears the textbox scroll position and its value (if updated) in all cases, including location.reload(); Firefox retains those pieces of data in location.reload().

At a spec level, discussed a bit in #6197 (comment), we have several distinguishable actions:

  • (X) Navigate normally to the same URL as the current URL
  • (Y) Navigate with replacement (location.replace()) to the same URL as the current URL
  • (Z) Navigate with reloading (location.reload()) to the same URL as the current URL

and we have the following potential behaviors:

  • (A) Keep the current session history entry. Just replace its document.
  • (A') Keep the current session history entry. Replace its document and maybe reset a few other things.
  • (B) Throw away the current session history entry. Create a new one with a new document, the same URL, and defaults for everything else.
  • (B') Remove the current session history entry. Create a new one with a new document, the same URL, and potentially copy over some of the other stuff from the old one.

So from I can see:

  • Firefox performs (A) or maybe (A') for (Z), but performs (B) for (X) and (Y).
  • Chrome performs (B') for (X), (Y), and (Z), copying over at least history.state, but not copying over most scroll positions. It does copy over the main document scroll position for case (Z).

I'm not sure what the best behavior is here. At a spec level, the simplest thing would be (B) for all cases (X), (Y), and (Z), but that matches no browsers. This is all complicated by the fact that the other pieces of a session history entry are harder to test and in some cases not totally specified.

/cc @natechapin @smaug----

@domenic domenic added topic: history interop Implementations are not interoperable with each other labels Dec 10, 2020
domenic pushed a commit that referenced this issue Dec 11, 2020
This fixes several related issues surrounding the "update the session
history with the new page" and "traverse the history" algorithms.

* A mistaken attempted refactoring in #6039 got the wrong Document for
  the newDocument variable in "traverse the history". To fix this, we
  explicitly pass in the new document to the algorithm from all its call
  sites. (Discussed in #6197.)

* As discussed more extensively in #6197, the same-URL and entry
  update/reload conditions in "update the session history with the new
  page" were not correctly triggering the new-document clause in
  "traverse the history", despite replacing the document. This was
  partially due to #6039, although the phrasing before #6039 was
  extremely ambiguous, so #6039 was mostly a transition from "unclear"
  to "clearly wrong".

* This fixes the "easy bug" discussed in #6202, where the same-URL case
  was using the wrong URL to determine sameness. That bug was also
  introduced in #6039. The harder bug, as well as the
  action-at-a-distance nature of the same-URL check, remain tracked in
  #6202.

* For years, the spec has required deleting the future session history
  entries after performing a navigation with history handling of
  "replace", e.g. via location.replace(). This is not correct; a
  "replace" navigation does not delete future session history entries.
  Instead it just replaces the current entry, leaving future entries
  alone.

* The latter point makes the handling of same-URL navigations almost
  identical to "replace" navigations (including non-same-URL "replace"
  navigations). This change makes this clear, by using the same text for
  both, but adding a pointer to #6213 which highlights the fact that
  some browsers treat them slightly differently (and some treat them the
  same).

* Finally, this adds or modifies a few assertions to check that we're in
  the "default" history handling behavior, so it's clearer to the reader
  what the non-exceptional path through the spec is.

Closes #6197.
@domenic
Copy link
Member Author

domenic commented Mar 10, 2021

In another venue @rakina notes that Chrome's comparison is between does not compare the URL that started the navigation with the browsing context's active document's URL, but instead compares the final URL of the navigation to the browsing context's active document's URL. Also, Chrome ignores fragments.

Fortunately, after #6476, the red XXX box talking about sometimes copying over the state also compares the final URL (i.e., the URL of newDocument) to the browsing context's active document's URL (i.e., the URL of sessionHistory's current entry that is about to be replaced). So the XXX box is getting closer to at least one browser, which is nice.

@rakina
Copy link
Member

rakina commented Mar 23, 2021

I tested this out with a modified version of Domenic's test above. I checked history.state, history.scrollRestoration, and the scroll offset of the viewport/tab and textarea

No fragment (sameurl.html -> sameurl.html)

Same-URL nav type Creates new document Keeps history.state, history.scrollRestoration Keeps viewport scroll offset Keeps textarea scroll offset
location.reload() Chrome, Firefox, Safari Chrome, Firefox, Safari Chrome, Firefox, Safari Firefox
location.href = location.href Chrome, Firefox, Safari Chrome, Safari
location.replace(location.href) Chrome, Firefox, Safari Chrome, Safari
URL bar enter Chrome, Firefox, Safari Chrome, Safari Chrome
Browser UI reload Chrome, Firefox, Safari Chrome, Firefox, Safari Chrome, Firefox, Safari Firefox

With fragment (sameurl.html#bottom -> sameurl.html#bottom)

Same-URL nav type Creates new document Keeps history.state & history.scrollRestoration Keeps viewport scroll offset Scrolls to fragment Keeps textarea scroll offset
location.reload() Chrome, Firefox, Safari Chrome, Firefox, Safari Chrome, Firefox, Safari Firefox
location.href = location.href Chrome, Safari Chrome, Firefox, Safari
location.replace(location.href) Chrome, Safari Chrome, Firefox, Safari
URL bar enter Chrome, Safari Chrome, Safari Chrome Firefox, Safari
Browser UI reload Chrome, Firefox, Safari Chrome, Firefox, Safari Chrome, Firefox, Safari Firefox

Reloads

  • Chrome, Firefox, Safari restores history.state,history.scrollRestoration, viewport offset on reload.
  • Only Firefox restores the textarea offset on reload.
  • Chrome treats URL bar navigation to the same URL as reloads (but that's out of scope of spec).

Same-URL navigations

  • Script-initiated same-URL navigations will create a same-document navigation if the URL has fragment, and load a new document otherwise.
  • Chrome & Safari keeps history.state & history.scrollRestoration on all same-URL navigation.

General

  • Chrome, Firefox, Safari replaces the previous entry on reloads and same-URL navigations (even for the same-document case).
  • Not sure how to check the other session history attributes. I guess browsing context name can be checked via WPT, but that seems to have its own set of problems.

I think since Chrome & Safari behaves the same way, maybe it makes sense to copy history.state & history.scrollRestoration on same-URL navigations. Or maybe it's weird to do that but not restore the scroll offsets too?

@domenic
Copy link
Member Author

domenic commented Mar 24, 2021

Thanks for continuing the investigation Rakina!

Not sure how to check the other session history attributes. I guess browsing context name can be checked via WPT, but that seems to have its own set of problems.

I think we should spend a small amount of time on this. If writing tests is easy, and it aligns well with other pieces of state (e.g. is always copied over in the same way as history.state), then let's try to make sure it's specced and tested well. If it's too hard to write tests for, or gives weird results due to how it interacts with other parts of the system, then we can set it aside.

I think since Chrome & Safari behaves the same way, maybe it makes sense to copy history.state & history.scrollRestoration on same-URL navigations.

Agreed.

Or maybe it's weird to do that but not restore the scroll offsets too?

Could we restore the scroll offsets too? Everyone seems to restore the viewport scroll offset according to your table above. And we can say that the lack of consistency for textarea scroll offset restoration is down to the "may" clause in https://html.spec.whatwg.org/multipage/browsing-the-web.html#restore-persisted-state .


Note that once we get this more solid, especially if we copy over most (all?) of the fields, the next natural question will be whether we should maintain the distinction between the "entry update"/"reload" case and the "replace" case in https://html.spec.whatwg.org/#update-the-session-history-with-the-new-page . Maybe we can merge them. But figuring out the full consequences of that will be tricky, so my instinct is to wait and tackle it after we improve the spec for the "replace" case, like we're doing here.

@domenic
Copy link
Member Author

domenic commented Apr 14, 2021

@rakina, do you think you'd be able to help with web platform tests for this? I'd love to get this spec issue resolved.

@jakearchibald
Copy link
Contributor

Here's another fun one:

  1. Navigate to /a.
  2. pushState({}, '', '').
  3. Browser UI reload, but server redirects to /b.

Chrome, Safari, Firefox: history.state is null.

  1. Navigate to /a.
  2. pushState({}, '', '').
  3. Browser UI reload, but server redirects to /a.

Firefox: history.state is null.
Chrome, Safari: history.state is retained.

  1. Navigate to /a#foo.
  2. pushState({}, '', '').
  3. Browser UI reload, but server redirects to /a (the hash is still retained in the browser).

Firefox, Safari: history.state is null.
Chrome: history.state is retained.

The correlates with the findings in #6680, including "I have no idea what Safari is doing".

@samthor
Copy link

samthor commented Jul 20, 2021

Chiming in with another oddity with Firefox around events. (I'm not sure this needs a whole new bug although happy to file one.)

Demo here, for the "With fragment" case above (re: @rakina's demo). When a fragment is loaded, the two internal navigations (location.href = location.href & location.replace(location.href))—plus user-driven self navigation via clicking a link—have different outcomes:

  • Chrome/Safari: emits "popstate" event (and does not change history.state), even though the overall history isn't changing whatsoever (no length changes, new no entries).

  • Firefox: resets history.state for the current history entry to null without any announcement.

@smaug----
Copy link

I do see popstate in Firefox when clicking a link.

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
5 participants