This is an archived snapshot of W3C's public bugzilla bug tracker, decommissioned in April 2019. Please see the home page for more details.

Bug 11468 - spec logic: replaceState before onload should not affect onload object or popstate should not fire after onload.
Summary: spec logic: replaceState before onload should not affect onload object or pop...
Status: RESOLVED FIXED
Alias: None
Product: HTML WG
Classification: Unclassified
Component: LC1 HTML5 spec (show other bugs)
Version: unspecified
Hardware: Other other
: P3 normal
Target Milestone: ---
Assignee: Ian 'Hixie' Hickson
QA Contact: HTML WG Bugzilla archive list
URL: http://www.whatwg.org/specs/web-apps/...
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2010-12-04 06:15 UTC by contributor
Modified: 2011-08-05 14:21 UTC (History)
6 users (show)

See Also:


Attachments

Description contributor 2010-12-04 06:15:00 UTC
Specification: http://www.whatwg.org/specs/web-apps/current-work/multipage/history.html
Section: http://www.whatwg.org/specs/web-apps/current-work/#event-popstate

Comment:
spec logic: replaceState before onload should not affect onload object or
popstate should not fire after onload.

Posted from: 203.218.87.145
Comment 1 henry.fai.hang.chan 2010-12-04 14:50:08 UTC
A webapp alters all its links to use ajax to load the page.  To ease url sharing, it uses popstate.  To maintain backwards compatibility, the same pages are served by pasting in the popstate url.

Consider Example [1]: fetchAjax ignores NULL
1. A user goes to index.html
2. DOM ready fires and the links are assigned an onclick handler "loadLink", calls pushState with a state object of the new url, and calls "fetchAjax" to fetch the page using ajax.
3. "fetchAjax" is registered to onpopstate so that it will fetch the ajax page on a history traversal.
4. The onload event fires.
5. The onpopstate event fires.
6. "fetchAjax" does not fetch any page as the event state is NULL.
7. The user clicks on a link page1.html.
8. The "loadLink" function is called, triggering a pushState and then fetchAjax.
9. The user clicks on a link page2.html.
10.The "loadLink" function is called, triggering a pushState and then fetchAjax.
11.The user clicks back on his browser.
12.The opnpopstate even fires, with a event state of page1.html.
13.The fetchAjax fetches page1.html.
14.The user clicks back on his browser again.
PROBLEM:
15.The onpopstate even fires, with a event state of NULL.
16.index.html is not fetched, but the url is now 'index.html'
CAUSE: The handler cannot distinguish between first page load and back and forth page load as both have the event state of NULL.

Consider Example[2]:
1. A user goes to index.html
2. DOM ready fires and the links are assigned an onclick handler "loadLink", calls pushState with a state object of the new url, and calls "fetchAjax" to fetch the page using ajax.
3. "fetchAjax" is registered to onpopstate so that it will fetch the ajax page on a history traversal.
4. The user clicks a link to page1.html before onload is fired.
5. The onload event fires.
6. The onpopstate event fires.
PROBLEM:
7. index.html is refetched AND the URL stays on page1.html.
8. Going back refetches index.html.
9. Going forward refetches page1.html.

Workaround [1]: Use Example 1, but fetch location.href when event state is null.
PROBLEM: An ugly flash of content and reloading of images because onpopstate is called after onload.

Workaround [2]: Use Workaround[1], but add a variable on onload that causes the first onpopstate to return instead of fetching page.
PROBLEM: What if user aborts the page after dom ready fires but before onload?

Workaround [3]: Use Workaround[1], but add a variable on document parsing (wrong term?) that causes the first onpopstate to return instead of fetching page.
PROBLEM: Extra variables and overhead.

Workaround [3]: Fire replaceState on first onpopstate / onload.
PROBLEM[a]: If a user clicks a link page1.html, a pushState is fired.  When onpopstate / onload fires, the history becomes [index.html, index.html]. index.html is refetched. page1.html disappears totally from the history.
PROBLEM[b]: If a user navigates to page1.html then page2.html, then onpopstate/onload fires, the history becomes [index.html, page1.html, index.html].

Workaround [4]: Fire replaceState during document parsing. (wrong term?)
This works in Google Chrome.  window.onpopstate calls with a null event state.
Navigating to a new page and then clicking back will fire event with new event state.
PROBLEM: Starting with firefox 4b8pre (some build a few days ago), replaceState caused the window.onload event to fire with the replaced event state.  Causes same effect as Example 2.

Workaround [5]:
Remember the time for onpopstate and the time for onload.  If the difference is less than 200 milliseconds abort the onpopstate.
PROBLEM: unreliable.


Solution [1]:
  Don't fire onpopstate after onload.  Most simplest, but rejected in bug 10365.
Solution [2]:
  Queue the events up by the UA.
  PROBLEM: Back and forward doesn't work before onload fires, what if user aborts page load?
Solution [3]:
  Fire onpopstate after onload with a null event state, regardless of whatever replaceState.  And allow replaceState to call before document finishes loading.
Comment 2 Ian 'Hixie' Hickson 2010-12-13 23:17:24 UTC
Example[2] is fixed in the spec now; the popstate would include the pushState()d data.

Example[1] is working as intended. Just make the page load minimal, immediately replaceState() and then load the page from the popstate event. (You can include all the data in the initial connection so that the latency is no worse.) Alternatively, keep track of what the current state is and just don't do anything if reloading the current state (and use replaceState() when the page loads to track what the first state is).

EDITOR'S RESPONSE: This is an Editor's Response to your comment. If you are satisfied with this response, please change the state of this bug to CLOSED. If you have additional information and would like the editor to reconsider, please reopen this bug. If you would like to escalate the issue to the full HTML Working Group, please add the TrackerRequest keyword to this bug, and suggest title and text for the tracker issue; or you may create a tracker issue yourself, if you are able to do so. For more details, see this document:
   http://dev.w3.org/html5/decision-policy/decision-policy.html

Status: Partially Accepted
Change Description: no spec change
Rationale: See above.
Comment 3 henry.fai.hang.chan 2010-12-14 06:02:39 UTC
I don't understand how Example [2] is fixed.  It actually broke with the new spec as replaceState when called before onload changes onpopstate event state.
Which causes index.html to get loaded again.

> Example[1] is working as intended.
> Just make the page load minimal, immediately replaceState() and then load the 
> page from the popstate event. (You can include all the data in the initial 
> connection so that the latency is no worse.)

Load minimal? The most minimal version i made still takes 3 seconds to load on New Zealand as our server is in Hong Kong.  The user could still click within this period and onpopstate after onload would fire with the new event state if i used replaceState before onload.

> Alternatively, keep track of what the current state is and just don't do
> anything if reloading the current state (and use replaceState() when the page
> loads to track what the first state is).

Isn't that more code?

The actual solution I am proposing is, onpopstate should refer to the event state at the BEGINNING of the navigation, i.e. after onload always fires with event state null no matter if replaceState or pushState is called before onload.

This works within Google Chrome (so-called "stable") already.

And reduces code. Of course an extra variable isn't much in terms of memory in this modern world. But why make an extra variable when it could be altogether avoided?

If i need to implement this extra variable, I might just go with onhashchange altogether.  I don't see any advantage other than prettier urls.
Comment 4 henry.fai.hang.chan 2010-12-14 06:16:35 UTC
> The pending state object is used to keep track of what state object to use in 
> the inital popstate event fired by the parser once it stops parsing. The pending > state object must be initially null.

Why do we need a pending state object?

It's useless.  If the document updates itself according to event state, then it shouldn't need this extra onpopstate after onload because nothing needs to be changed.

Ok, if I don't want it I can discard the first onpopstate.

What if onpopstate after document parsing doesn't fire because the page was aborted during load?

The links still work because DOMready was called.

Of course I can use a variable to track current event state.
But that's unnecessary.  And what if i wanted to user to still see a 'page refresh' if he clicked on index.html twice, and clicked back?

So how can I distinguish the initial onpopstate and other onpopstate?

Do you see where the flaw is?
Comment 5 henry.fai.hang.chan 2010-12-14 06:32:50 UTC
> Example[2] is fixed in the spec now; the popstate would include the
> pushState()d data.

That makes it harder to detect the inital onpopstate doesn't it?

also see https://bugzilla.mozilla.org/show_bug.cgi?id=618644
Comment 6 Ian 'Hixie' Hickson 2011-01-11 19:05:05 UTC
If we fired the initial popstate onload, then the page would essentially "regress" at load if the user caused there to be pushstates before the load, unless we kept track of all the states and popped through the in sequence, but that would likely conflict with whatever code was calling pushState() in the first place. If we just didn't delay the popstate event, then going back in history from another page to a page that already had state data would cause the state data to be lost as the popstate would fire before the scripts to handle it.

> How minimal?

Minimal enough that there's no UI to interact with before onload fires.

Your problems all seem to be predicated on the idea that the page will appear usable (but not be) for a long time before the load event fires. You don't want to do that. If you need to wait for the 'load' event (which you do if you're using pushState/popstate) then construct your page so that only critical resources load before the load event, then have the load event set up the page, and then kick off non-critical resource loads.


EDITOR'S RESPONSE: This is an Editor's Response to your comment. If you are satisfied with this response, please change the state of this bug to CLOSED. If you have additional information and would like the editor to reconsider, please reopen this bug. If you would like to escalate the issue to the full HTML Working Group, please add the TrackerRequest keyword to this bug, and suggest title and text for the tracker issue; or you may create a tracker issue yourself, if you are able to do so. For more details, see this document:
   http://dev.w3.org/html5/decision-policy/decision-policy.html

Status: Rejected
Change Description: no spec change
Rationale: this is working as intended.
Comment 7 Ian 'Hixie' Hickson 2011-03-23 20:01:25 UTC
I am considering applying the changes listen in http://hacks.mozilla.org/2011/03/history-api-changes-in-firefox-4/ to the spec, which I believe would change this from "WONTFIX" to "FIXED".
Comment 8 Ian 'Hixie' Hickson 2011-03-23 22:01:27 UTC
I'll track that as bug 12277. Please add any relevant comments there.
Comment 9 Michael[tm] Smith 2011-08-04 05:11:47 UTC
mass-move component to LC1