Is History API broken on iOS? (Location bar doesn't update on pushState)

Aral Balkan picture Aral Balkan · May 28, 2011 · Viewed 19.2k times · Source

Filing this under the either the I Can't Believe No One Noticed This Before or the I Must Be Missing Something categories:

It appears that if you do a simple window.history.pushState on iOS, the location bar doesn't update unless it is in response to a user gesture. The state itself does get pushed (as you can see by hitting the back button button).

Here's is the tiniest test-case I could come up with recreate the issue:

http://thelink.is/history-api-ios-bug

On a desktop browser that supports the History API, you should see the URL in the location bar change to /0, /1, etc., every second. On iOS – tested with iPhone (running iOS 4.3) and iPad (running iOS 4.3.3) – the location bar doesn't update but hitting the back button will take you the correct previous location (which will 404 on the test-case since there's no back-end logic to handle those URLs).

Thoughts? Workarounds? A shoulder to cry on and hugs?

UPDATE: this issue was fixed in iOS 5.

Answer

Remy Sharp picture Remy Sharp · May 29, 2011

So the bottom line is that iOS has added its own security around the history API, meaning that you can't use script to change the url. Only a user action can allow the history API to change the url - i.e. a click - as per Aral's example.

The workaround is to uses a hash (aka fragment identifier) on the url.

Instead of the history.pushState we'll just change the location:

var i = 0;
var locationUpdateInterval = setInterval(function(){
  window.location.hash = i;
  i++;
}, 1000);   

To capture the event either when something changes the that location in the iOS app or if they have permalink to a particular page/panel in your app:

// named function on purpose for later
function hashchange() {
  var pageId = location.hash.substr(1); // drop the # symbol
  // do something with pageId
}

window.onhashchange = hashchange;

// onload - if there's a hash on the url, try to do something with it
if (location.hash) hashchange();

It's pretty poor that we can't use the pushState/popState on iOS, but it's the same security as not being able to trigger fullscreen video unless the user initiates the action, which is the same as downloading video or audio content on iOS - you can't script it, the user must start it (somehow or another).

Just as a note about Android - the problems are pretty similar, so this (should) also work as a workaround for Android.

If you want desktop support, most browsers support onhashchange but, yep, you guessed, IE is lacking behind - so you can polyfill that bad boy in (though requires jQuery...): http://benalman.com/projects/jquery-hashchange-plugin/

Hope that helps.