39

I'm playing with window.onpopstate, and there is a thing that annoys me:

Browsers tend to handle the popstate event differently on page load. Chrome and Safari always emit a popstate event on page load, but Firefox doesn't.

source

I tested it, and yeah, in Chrome and Safari 5.1+ the popstate event is fired on page load, but not in Firefox or IE10.

The problem is, that I want to listen only to popstate events where user clicked the back or forward button (or the history was changed via javascript), but don't want to do anything on pageload.

In other words, I want to differentiate the popstate event from page load from the other popstate events.

This is what I tried so far (I'm using jQuery):

$(function() {
  console.log('document ready');
  
  setTimeout(function() {
    window.onpopstate = function(event) {
      // Do something here
    }, 10);
});

Basically I'm try to bind my listener function to popstate late enough to be not bound on page load, only later.

This seems to work; however, I don't like this solution. How can I be sure that the timeout chosen for setTimeout is big enough, but not too big (because I don't want it to wait too much).

I hope for a smarter solution!

MeSo2
  • 450
  • 1
  • 7
  • 18
Tamás Pap
  • 17,777
  • 15
  • 70
  • 102
  • 4
    This bug seems to have been fixed on Chrome already. – Pacerier Oct 11 '14 at 14:57
  • 1
    Please update your question, it happens only with safari as of today. Chrome seems to have fixed this. – Kush Jan 20 '15 at 08:11
  • This misfired `popstate` happens just after the `onload` processing which means 1ms is **always enough** to postpone the `popstate` registration (because JS has no multi-threading and process the postponed functions in the order they were registered). – Radek Pech Feb 14 '17 at 10:05

4 Answers4

44

Check for boolean truth of event.state in popstate event handler:

window.addEventListener('popstate', function(event) {
    if (event.state) {
        alert('!');
    }
}, false);

To ensure this will work, always specify a non-null state argument when calling history.pushState() or history.replaceState(). Also, consider using a wrapper library like History.js that provides consistent behavior across browsers.

Marat Tanalin
  • 13,927
  • 1
  • 36
  • 52
  • 2
    Yes, the event.state is empty on pageload, but can also be empty if pushState is called without state defined, like: `history.pushState(null, null, url)`; – Tamás Pap Apr 09 '13 at 09:33
  • 2
    So always define a state or write a wrapper class (or use an [existing one](https://github.com/browserstate/history.js)) that will make such things implicitly. Native implementations of HTML5 History API are inconsistent, so we are forced to have some tradeoff anyway. – Marat Tanalin Apr 09 '13 at 09:40
  • 1
    Yes, I will use a wrapper class, and never use `null` for state when calling `pushState` or `replaceState`. Please update your answer with your last comment, so I can accept it. Thanks for your help. – Tamás Pap Apr 09 '13 at 09:45
  • To clarify: make sure to call `history.pushState(state, null, url)`, do not use null on the first parameter. When Safari calls `popstate` on page load, the `event.state` will be nil. This is how to differentiate between initial page load vs explicit pushState call – tothemario Oct 03 '15 at 01:50
  • if i do `pushState` in **page2** then while fire `popstate` the `event.state` will be **null**. if i do `pushState` 2 times i have a valid `event.state` while comeback, but user should press back button 3 times to see **page1** –  Mar 03 '19 at 13:00
7

I had a similar problem and i had to validate to make sure if page was loaded completely.

I used something like this :

var page_loaded = false;    
window.onpopstate = function(event){
    if(!page_loaded){
       page_loaded = true;
       return false;
     }
    //Continue With Your Code
}
Rosmarine Popcorn
  • 10,761
  • 11
  • 59
  • 89
  • I was also thinking about a solution like this. The problem is, that window load happens before the first onpopstate, so `page_loaded` will be always true. (JSBin: http://jsbin.com/awidol/1/edit) – Tamás Pap Apr 09 '13 at 08:34
  • @TamasPap than a solution could be refusing to execute the code on first time, change page_load to true and than continue with the code. – Rosmarine Popcorn Apr 09 '13 at 08:37
  • Yeah, but then in Fireworks and IE10 the first onpopstate will not happen. – Tamás Pap Apr 09 '13 at 08:39
  • @TamasPap Than try to detect the browser and fill the if condition to get executed only on browsers which do that. – Rosmarine Popcorn Apr 09 '13 at 10:47
  • I ran into the same issue, this is what I've done, its not a clear fix, however it works 99% of the time: `var JUST_LOADED = true; setTimeout(function(){JUST_LOADED=false},399);` – Raj Nathani Mar 09 '14 at 05:59
5

To react on popstate event, you need to push some state onto the session history.

For example add this line to the document ready section:

history.pushState(null, null, window.location.pathname);

Not ideal, but it works in Chrome, Firefox and other browsers as well.

Then the event is fired correctly when user clicks on Back or Forward button, also when history.back(), history.forward(), history.go() methods are called manually. Each time when popstate has been invoked, you have to push another state again to make it working.

See also:

Community
  • 1
  • 1
kenorb
  • 155,785
  • 88
  • 678
  • 743
2

It seems none of the browsers are emitting the event on page load any more as of today:

Browsers used to handle the popstate event differently on page load, but now they behave the same. Firefox never emitted a popstate event on page load. Chrome did until version 34, while Safari did until version 10.0.

Source: https://developer.mozilla.org/en-US/docs/Web/API/PopStateEvent

Liang Zhou
  • 2,055
  • 19
  • 20