W3C home > Mailing lists > Public > whatwg@whatwg.org > March 2011

[whatwg] Onpopstate is Flawed

From: Ian Hickson <ian@hixie.ch>
Date: Fri, 25 Mar 2011 00:55:02 +0000 (UTC)
Message-ID: <Pine.LNX.4.64.1103231845410.19153@ps20323.dreamhostps.com>
On Wed, 15 Dec 2010, Henry Chan wrote:
>
> https://bugzilla.mozilla.org/show_bug.cgi?id=618644
> http://www.w3.org/Bugs/Public/show_bug.cgi?id=11468
> 
> Consider a new web application that loads pages.  All links are 
> transformed into ajax and storing the history data with pushState if the 
> useragent supports them.  The inital page load serves the full page so 
> that useragents that don't support pushState will get the old page 
> navigation.
> 
> The user clicks on page1.html.  Then he clicks on page2.html.  For every 
> clicking, we pushState an object into the history containing the new 
> url. The user clicks back.  A popstate event with state {url: 
> 'page1.html'} is fired.  The ajax application loads page1.html.  The 
> user clicks back again. A popstate event with state [null] is fired.  
> The ajax application doesn't do anything.  The user refreshes the page 
> and thinks "what the heck?"
> 
> So I change the application a bit.  When the event state is null, I 
> repull the original page.  What happens is, the application fetches the 
> page again after onload because onpopstate is called after onload.  
> This makes the application refetch the same page after onload.  If the 
> site is media heavy then that eats both bandwidth and makes the user 
> wait.  (I originally proposed to remove the onpopstate after onload but 
> realized that for some applications, the event state might not be 
> reflected in the url, and when reloading the page, the page would need 
> the event state to load the correct page.)
> 
> I need to distinguish the inital popstate and other popstates.  
> Assuming that popstate always fired after onload, I just had a variable 
> that removed the first popstate.  Turns out if the user presses the stop 
> button before onload fires, onpopstate doesn't fire.
> 
> So I fire a history.replaceState({url:'index.html'}) on domready of 
> index.html.  The first onpopstate fires with event state [null].  
> Refreshing the page or navigating forward then backward gives a event 
> state of {url: 'index.html'}. Yay!
> 
> Until recently the spec changed.  And Firefox 4 got updated to match the 
> spec.  The inital popstate now fires with the 'pending state object'.  
> Now I can't distinguish the inital popstate anymore.
> 
> And the problem just doesn't stop there.
> 
> Suppose this: the user clicks on page1.html.  When the initial 
> onpopstate fires, there is an event state of {url:'index.html'}.  
> Index.html is refetched.  The user is probably very bemused.  And the 
> location bar claims to be still on page1.html (Fx implementation bug?)
> 
> Another problem, clicking back and forward before page loads doesn't 
> trigger onpopstate.  Which means on a media heavy site, when the user 
> clicks on page1.html then page2.html and clicks back, he won't get 
> page1.html.  He'd be stuck on page2.html (as per spec) while the url in 
> the location bar is page1.html.  (Fx Implementation bug?).  If the user 
> has clicked back and forward before the page load, the initial 
> onpopstate after onload doesn't even fire. (Fx implementation bug?)
> 
> So to round up, the problems I have are
> 1. unable to distinguish inital onpopstate
> 2. onpopstate doesn't work till onload
> 3. the inital onpopstate will overwrite any user actions the user has done
> before it.
>
> [...]
> 
> Check out my testcase at www.wyavtv.org/test-popstate.html.
> Try clicking on the links before onload.
> Reload the test and try clicking on the links and navigating back and
> forward before onload.
> Reload the test and try just waiting for it to load.
> 
> The test page is initially called test-popstate.html.  Onload it now changes
> to index.html.  I made it this way so that we can tell if the useragent
> loads the same "identical" page twice, i.e. if replaceState affects the
> inital onpopstate.  Currently chrome doesn't have a pending state object and
> it stays on test-popstate.html, but chrome has problem 2 and 3 as well.
> 
> Index.html, page1.html and page2.html and page3.html are non-existent
> pages.  Please press the reload test button in my testcase to reload the
> page, don't press F5; it won't load, and you'll poison my server logs.

On Mon, 31 Jan 2011, Jonas Sicking wrote:
> 
> Would the following behavior solve your issue:
> 
> If pushState or replaceState is called before the initial popstate, 
> simply don't fire the after-onload-popstate.
> 
> If the back button is pressed (or history.back() is called) after a 
> pushState/replaceState, but before onload, fire a popstate for the newly 
> transitioned to state. Still leave the after-onload-popstate canceled.
> 
> I.e. if the webpage calls pushState or replaceState before onload fires, 
> then it is deemed that the page has transitioned to the new state and no 
> after-onload-popstate is needed.
> 
> This behavior makes the most sense to me and allows the page to start 
> handling state transitions before the page finishes loading.

On Wed, 2 Feb 2011, Justin Lebar wrote:
>
> I'm a bit uncomfortable with this behavior, since it seems that having 
> replaceState cancel the initial popstate is at least somewhat 
> surprising.
> 
> How is this better than never firing an initial popstate?

On Wed, 2 Feb 2011, Jonas Sicking wrote:
> 
> My thinking was that if someone calls replaceState, then probably that 
> means that they're currently changing the page to represent that new 
> state. If they do that then I don't see that they initial popstate would 
> help them in any way?
> 
> Yet another solution would be to always expose the current state through 
> a member on the window or the document. Then popstate would represent 
> any transition in the current state and wouldn't be needed for the 
> initial page load.
> 
> So during loading, any script that wants to know what the initial (or 
> current) state is does not need to wait for the first popstate, but can 
> simply grab the state and go.
> 
> The main problem I can think of with this design, apart from it being a 
> bigger change from what we've got, is what happens if someone modifies 
> the current-state member on the window/document. While we can make the 
> member read-only, that doesn't help if the state is a deep object 
> hierarchy. In IndexedDB we decided to not attempt to solve the problem 
> and instead rely on authors not to trigger the footgun.

(We could also just return a clone each time, or make it a function that 
returns a clone.)

On Wed, 2 Feb 2011, Justin Lebar wrote:
> 
> Don't fire an initial popstate, because this causes stale popstates
> when pushState is called before the popstate.
> 
> Expose the state object to the DOM so pages can find out what the
> initial state is when they load.  (The initial state might not be null
> if we're restoring after a crash, or if we're going back in history
> after we unloaded the document.)
>
> Otherwise, fire popstate like normal, once for each navigation.
> (With the caveat that you never want to fire a stale popstate -- that
> is, if any navigations or push/replaceStates have occurred since you
> queued the task to fire the popstate, don't fire it.)
> 
> I think we need the caveat in step 3 because firing popstate isn't
> synchronous (step 11 at [1]).

In addition to the thread and links above, I was also directed to this 
blog post discussing how the discussion above resulted in changes in FF4:

   http://hacks.mozilla.org/2011/03/history-api-changes-in-firefox-4/

After confirming with WebKit and Opera developers that the changes seem 
reasonable, I've implemented that in the spec. As far as I can tell, these 
changes make this API significantly better overall, and address the bulk 
of the comments raised above.

There does remain one problem with this API, which is that when you 
navigate to a fragment identifier, history.state becomes null. I'm not 
sure how to fix that.


On Sun, 19 Dec 2010, Henry Chan wrote:
>
> *bump*
> 
> This is really serious, it blocks a very good use of the API, and it's 
> unreliable to use it if back and forward is clicekd before onload.

Bumping has no effect, sorry (because I don't see the bump any more than 
the initial e-mail!). I get to all the feedback eventually. If there's 
something that affects an imminent implementation, please let me know and 
I'll prioritise it. (Though right now, the list of things that affect 
imminent implementations is itself quite long for once.)

-- 
Ian Hickson               U+1047E                )\._.,--....,'``.    fL
http://ln.hixie.ch/       U+263A                /,   _.. \   _\  ;`._ ,.
Things that are impossible just take longer.   `._.-(,_..'--(,_..'`-.;.'
Received on Thursday, 24 March 2011 17:55:02 UTC

This archive was generated by hypermail 2.4.0 : Wednesday, 22 January 2020 16:59:31 UTC