Closed Bug 679458 Opened 13 years ago Closed 9 years ago

scrollX/Y should not be restored before popstate event

Categories

(Core :: DOM: Navigation, defect)

x86
macOS
defect
Not set
normal

Tracking

()

RESOLVED DUPLICATE of bug 1186774

People

(Reporter: joe, Unassigned)

Details

User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.803.0 Safari/535.1

Steps to reproduce:

When the user goes back or forward, I access the window.scrollX and scrollY properties in my popstate handler so I can persist them.  I use history.pushState and recreate the entire page for each new URL, meaning that I need to also manage the history of scroll positions myself.


Actual results:

The browser restores scrollX and scrollY before firing the popstate event, meaning that I can't save the scroll position myself in my popstate listener. I have to workaround this by with a scroll event listener to save the scroll positions each time they change.


Expected results:

scrollX and scrollY should NOT be restored when going back/forward through entries created via history.pushState (this is how webkit behaves).  If you insist they must be restored, it should happen *after* the popstate event, which should have the ability to use preventDefault to prevent this.
Component: History: Global → Document Navigation
QA Contact: history.global → docshell
Isn't this bug 666792?

I kind of think the problem here is that there's no logical "I'm about to leave this state" event.  By the time we fire popstate, we've logically switched to the new state, so if we're going to scroll, we should have done it by then.

Not scrolling at all might be a good option.
I would vote for not scrolling at all. When somebody uses history.pushState, they are probably recreating the page themselves, and so it makes sense for them to also be responsible for scrolling.  As I mentioned earlier, WebKit works this way.
bz, sicking, smaug: Do you have an opinion on this?  I wish we could come up with something which doesn't require the average dev to worry about saving/restoring scroll position, but maybe it's too late for that.
Could we fire another event before the state change that the few devs who want to do manual position management can use to prevent position restoration (by calling preventDefault on it)?

Or is the expectation that most devs will want that?  Some hard data would be good.

Anecdotally, the uses of push/replaceState I have seen do NOT rebuild the page; they just make the URI look nicer (case in point, Bugzilla).
I do think we should aim for having pushState/popstate be designed for people rebuilding pages though. And not just hacks to clean up the urlbar. In fact, doesn't those hacks use replaceState?

I agree the scroll position is complicated though. I do sort of like the idea of restoring after and using preventDefault
Hmm.. Joe, if you want to continuously save and restore the scroll position, then why isn't the fact that gecko does this for you good? I.e. can't you fully rely on gecko taking care of the scroll position?
It's not good because Gecko restores the scroll position *before* it fires the popstate event.  I recreate the page in popstate, which means that regardless of whether Gecko restores the scroll position, I have to restore it again after I recreate the page, since replacing body.innerHTML resets scrollX/Y to zero.  I am sure there are lot of different ways to use the history API, but the most common one I've seen is doing what I'm doing - recreating all or most of the page in JavaScript, which means there is no benefit at all to having Gecko managing the scroll positions.
So if we restore the scroll position after firing popstate, wouldn't that mean *that* then mean that you don't have to worry about restoring it?

I'm trying to find a solution where the page doesn't have to worry about scroll position, and gecko instead deals with it.
In my case, I recreate the page asynchronously, so I don't see how the browser could know when it's appropriate to restore the scroll position.
I am also experiencing this bug and would expect the same result as described in the bug report.
Olli, is this bug something you'd care to take. I think popState is pretty unowned right now.
For normal back and forward pages, Chrome would first scroll to the previous position if possible, wait for it to load, and if the user hasn't scrolled, scroll to the previous position again.  For Firefox, if I remember correctly, it simply waits for the page to load and snaps to the position.

Coming to popstate, the behavior is completely overturned.  On receiving the popstate event, the page is loaded asynchronously.  Scrolling should follow after the asynchronous load, however the current event handling approaches mean that it is impossible to know when to accurately scroll.

Furthermore, the page length may change between states, thus, Firefox may attempt to scroll to a position that does not exist in the previous page, leaving the user stuck on the bottom of the previous page, instead of either his previous scroll position nor the intended scroll position.

Given all these borks, it leaves little workaround but for developers to take care of the scrolling themselves in the popState code.  If only the scroll position restoration was cancellable (event.preventDefault on popState), then developers could code for a seamless scroll experience.

--
To be honest, HTML5 history deviates from the normal flow that the browser has no way to know if the popstate call has succeeded, and updates the URL bar immediately; vs for normal flow, the URL bar does not update until the first packet is received, and pressing stop / ESC causes the previous page to blank out or current page to stop rendering.  There should be a way to notify the browser when exactly the URL bar should change to the new value and when the scroll position should change.  Such an issue should probably left to the spec?
It's been over 3 years is expected that this problem is solved. Where is it?
See bug 1155730 for a potential solution to this issue.
My testing indicates that the scroll position of frames and iframes is NOT automatically restored on popState (in the top frame). This is most probably an interesting but useless observation.
Bug 1155730 would be the perfect solution, but interestingly Blink has decided to implement restoring scroll position *after* firing popstate, as was proposed in bug 666792 but was shot down because it's against the spec. The Blink team decided to do it anyway until (their equivalent of) bug 1155730 is implemented, so I'm wondering if Firefox should do the same.

See https://code.google.com/p/chromium/issues/detail?id=474579 and http://src.chromium.org/viewvc/blink?view=revision&revision=194167
Just to clarify, Blink has not *recently* decided to implement firing restoring scroll position *after* firing popstate. That is a legacy behavior and has been present in Blink (and Webkit) for a long time. Our recent decision was that we should continue that behavior (i.e., continue not matching the spec) as it breaks legitimate workarounds. We can only change the order once a viable alternative (e.g. bug 1155730) becomes available for developers.

I have started a thread on WhatWG mailing list[1] to discuss how we can converge to a single behaviour across all vendors. There I have listed why I think the current spec'd order is preferable to alternative. Your feedback will definitely help inform that decision.

[1] https://lists.w3.org/Archives/Public/public-whatwg-archive/2015Aug/0010.html
Dupping forward to a bug with an actual patch and all.
Status: UNCONFIRMED → RESOLVED
Closed: 9 years ago
Resolution: --- → DUPLICATE
You need to log in before you can comment on or make changes to this bug.