[whatwg] Onpopstate is Flawed

Henry Chan henry.fai.hang.chan at gmail.com
Tue Dec 14 23:23:44 PST 2010


As mentioned in:
https://bugzilla.mozilla.org/show_bug.cgi?id=618644
http://www.w3.org/Bugs/Public/show_bug.cgi?id=11468

I think the current behavior of onpopstate is very hard to understand. (And
I'm having trouble explaining it as well)

First off I'm going to use the term "media heavy site" as a synonym to
"pages with images on servers that take ages to respond" as it causes the
same effect but is much shorter.

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.

Hixie suggested me to use an extra variable to record the current page.
That removes the need of replaceState before onload but doesn't fix problem
2 and 3.

The solution I've come up is:
A1. onpopstate event should have an event state at the very start of the
navigation.  Remove the whole pending state object thing.  i.e. replaceState
before onload doesn't affect the event state at the inital onpopstate.
Fixes 1.
A2. fire inital onpopsate earlier, at domready, since most would attach ajax
on domready than onload.  Fixes 2 and 3 and removes the need for a pending
state object as onpopstate is now synchronous with user actions.
- or -
B1. onpopstate event should have an event state at the very start of the
navigation.  Remove the whole pending state object thing.  i.e. replaceState
before onload doesn't affect the event state at the inital onpopstate.
Fixes 1.  Allow the script to choose whether to let the inital onpopstate to
overwrite any user actions the user has done before it since initial is now
detectable.  Problem 3 is fixed but more code.
B2. just make onpopstate work before onload.  Fixes 2.

I'd prefer solution A.

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.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.whatwg.org/pipermail/whatwg-whatwg.org/attachments/20101215/9a9ffa21/attachment.htm>


More information about the whatwg mailing list