Popstate on page's load in Chrome
I am using History API for my web app and have one issue.
I do Ajax calls to update some results on the page and use history.pushState()
in order to update the browser's location bar without page reload. Then, of course, I use window.popstate
in order to restore previous state when back-button is clicked.
The problem is well-known — Chrome and Firefox treat that popstate
event differently. While Firefox doesn't fire it up on the first load, Chrome does. I would like to have Firefox-style and not fire the event up on load since it just updates the results with exactly the same ones on load. Is there a workaround except using History.js? The reason I don't feel like using it is — it needs way too many JS libraries by itself and, since I need it to be implemented in a CMS with already too much JS, I would like to minimize JS I am putting in it.
So, would like to know whether there is a way to make Chrome not fire up popstate
on load or, maybe, somebody tried to use History.js as all libraries mashed up together into one file.
Solution 1:
In Google Chrome in version 19 the solution from @spliter stopped working. As @johnnymire pointed out, history.state in Chrome 19 exists, but it's null.
My workaround is to add window.history.state !== null into checking if state exists in window.history:
var popped = ('state' in window.history && window.history.state !== null), initialURL = location.href;
I tested it in all major browsers and in Chrome versions 19 and 18. It looks like it works.
Solution 2:
In case you do not want to take special measures for each handler you add to onpopstate, my solution might be interesting for you. A big plus of this solution is also that onpopstate events can be handled before the page loading has been finished.
Just run this code once before you add any onpopstate handlers and everything should work as expected (aka like in Mozilla ^^).
(function() {
// There's nothing to do for older browsers ;)
if (!window.addEventListener)
return;
var blockPopstateEvent = document.readyState!="complete";
window.addEventListener("load", function() {
// The timeout ensures that popstate-events will be unblocked right
// after the load event occured, but not in the same event-loop cycle.
setTimeout(function(){ blockPopstateEvent = false; }, 0);
}, false);
window.addEventListener("popstate", function(evt) {
if (blockPopstateEvent && document.readyState=="complete") {
evt.preventDefault();
evt.stopImmediatePropagation();
}
}, false);
})();
How it works:
Chrome, Safari and probably other webkit browsers fire the onpopstate event when the document has been loaded. This is not intended, so we block popstate events until the the first event loop cicle after document has been loaded. This is done by the preventDefault and stopImmediatePropagation calls (unlike stopPropagation stopImmediatePropagation stops all event handler calls instantly).
However, since the document's readyState is already on "complete" when Chrome fires onpopstate erroneously, we allow opopstate events, which have been fired before document loading has been finished to allow onpopstate calls before the document has been loaded.
Update 2014-04-23: Fixed a bug where popstate events have been blocked if the script is executed after the page has been loaded.
Solution 3:
Using setTimeout only isn't a correct solution because you have no idea how long it will take for the content to be loaded so it's possible the popstate event is emitted after the timeout.
Here is my solution: https://gist.github.com/3551566
/*
* Necessary hack because WebKit fires a popstate event on document load
* https://code.google.com/p/chromium/issues/detail?id=63040
* https://bugs.webkit.org/process_bug.cgi
*/
window.addEventListener('load', function() {
setTimeout(function() {
window.addEventListener('popstate', function() {
...
});
}, 0);
});
Solution 4:
The solution has been found in jquery.pjax.js lines 195-225:
// Used to detect initial (useless) popstate.
// If history.state exists, assume browser isn't going to fire initial popstate.
var popped = ('state' in window.history), initialURL = location.href
// popstate handler takes care of the back and forward buttons
//
// You probably shouldn't use pjax on pages with other pushState
// stuff yet.
$(window).bind('popstate', function(event){
// Ignore inital popstate that some browsers fire on page load
var initialPop = !popped && location.href == initialURL
popped = true
if ( initialPop ) return
var state = event.state
if ( state && state.pjax ) {
var container = state.pjax
if ( $(container+'').length )
$.pjax({
url: state.url || location.href,
fragment: state.fragment,
container: container,
push: false,
timeout: state.timeout
})
else
window.location = location.href
}
})