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.
Here's what I found:
When the pushed location contains a hash symbol, the adressbar will be updated. So this will work:
But the window.location object will not be updated so you need to save the pushed url into a variable and use that instead of window.location if you need the pushed location.
Tested on Safari for Android.
I found a hack that kinda works. It turns out that if you change the hash right after history.pushState the location bar gets updated. Like:
changes the location bar to http://example.com/a/new/url#new. Which introduces another problem because the hash becomes it's own history entry. So you'll need to listen to onHashChange anyway.
It's a bit complicated, but there are some people who really, really hate hashbang urls and are very vocal about it. So it's worth it.
Works fine for me when using: https://github.com/browserstate/history.js - this also fixes many other cross browser bugs with the HTML5 History API.
As of v1.7, here are the bugs it solves:
(Update: Just saw that Remy answered too – read his in-depth answer, above, instead.)
Remy provided a workaround for this issue on Twitter.
Basically, if you change the location.hash, the address in the location bar updates. However, this does create a separate entry in the history (which doesn't work for what I'm trying to achieve). The workaround I'm implementing is to use hash-bang URLs for iOS and regular ones for other platforms until the iOS bug is fixed. This is definitely not ideal and I hope that Mobile Safari on iOS will start behaving like Chrome, Firefox and Safari on desktop.
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: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:
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.