Disabling automatic scroll restoration on navigation


#1

Note: This has already been proposed on whatwg mailing list. The consensus so far has been that it is a important problem to fix and I have incorporated several round of feedback on the API design. I am hoping to cast a wider net here and get additional feedback (and hopefully buy in) from other vendors and interested parties.

TL;DR

Add a new boolean attribute to history in order to allow web developer to opt out of automatic scroll restoration that occurs during navigation. This allows developers to get rid of their brittle hacks around it. We are very interested to solve this issue in blink and I have a working patch behind a flag.

Problem Summary

History [spec][current-spec] does not specify the scroll position persistence and restoration and leaves it up to user agents to implement. All major browsers provide scroll restoration functionality for both traditional document navigations and entries create via History API (i.e., pushState, and replaceState). Despite its prevalence there is currently no reliable and cross- browser solution for developers to opt out of this behaviour or control any of its aspects.

The default scroll restoring behaviour works well for document style web sites but it is often not appropriate for single-page web applications. Precise controlling of the scroll restoration is particularly important for single-page applications due to the following reasons:

  1. They may be re-constructing the page content onpopstate every time and often asynchronously. The user agent does not know when page is fully constructed so it may attempt to restore scroll position on a partially constructed page which has an incorrect size causing the scroll to land on an unintended incorrect position due to clamping.

  2. They may want to provide consistent scroll restoration regardless of how the page is entered e.g., via history navigation or vanilla links clicks.

  3. They may want to control the details of visual transition between UI states for example using a specific scrolling animation to transition instead of an instant jump, or even restoring scroll position of inner scrollers.

Proposed Solution

We propose to modify History API and make it possible for web authors to explicitly disable the default scroll restoration behavior. This is achieved by adding a new attribute, scrollRestoration, to window.history. This new attribute can take two values: ‘auto’, ‘manual’ with ‘auto’ being the default.

Setting scrollRestoration to a new value updates the current entry in history session. Also any future entry that is added to history session for the same document will use this new scrollRestoration value. However, changing this value does not impact any previous entries in sessions history including those that belong to the same document. Furthermore, any navigation to a new page with a different document resets the scrollRestoration to its default value.

Note that this ensures that changes to scrollRestoration only affect entries that will be created for the same document (e.g., entries created using pushState, or due to fragment navigation).

enum ScrollRestoration {
    "auto",
    "manual"
};

partial interface History {
     attribute ScrollRestoration scrollRestoration;
};

scrollRestoration: Indicates whether the user agent should restore scroll position when traversing to this entry. Anytime this attribute is modified the current entry in joint session history should be updated to reflect the change.


Allow disabling of automatic scroll restoration on navigation
#2

Beyond just scroll position, I’d like there to be mechanisms for controlling other UA behaviors around history. I started writing some thoughts on those, but I think, since they’re separate (albeit related) concerns, they should be proposed in their own thread: see Cached Content Updating and State Saving.

Declarability review

Also, with any new proposal, I think it’s important that, rather than ask “why use a declarative mechanism for this?”, we should instead ask “why not?”, lest the platform devolve into a mess of flavor-of-the-month client-side framework-ism even worse than the way web development already is.

That being said - even though this Scroll Restoration property could be expressed with something like a <meta> tag - in this case, I agree that this should only be exposed to scripting, since its functionality (disabling plain-content treatment of scroll position) only makes sense for dynamic pages - exposing it declaratively would exclusively serve to make it easier for content to misguidedly disable a useful behavior for unfounded reasons (akin to the way some pages, usually written around 2005, use <body oncontextmenu="alert('Right click disabled')"> because they think it “protects” them).


Cached Content Updating and State Saving
#3

Agreed. I am going to comment under that topic itself but just wanted to note that we think scale restoration control can be exposed with a similar API, and perhaps in future we can expose the actual recorded scroll position (and scale value).


#4

For some more context, here is a list of alternative APIs that have been considered before reaching consensus on the current design:

  1. Use events to allow detection of various state restorations and their prevention. This requires the state restoration to be done after DOMReady which leads to unacceptable UX.
  2. Use a fourth optional argument on pushState, replaceState to declare restoration preference. This was deemed too complex for simple cases and also unnecessarily linked scrolled restoration with state creation.
  3. Same as #2 but with two new methods (i.e., push, replace) that take a dictionary as argument. This advantages of a cleaner API didn’t out-weigh backward compatibility concerns.

#5

Note that this API is now shipping in Chrome 46, and we’ve got a lot of interest from some major web properties so it’s soon going to start getting harder to change the pieces that have shipped already. Feedback welcome!