Bookmark with del.icio.us submit HTML5%20History%20is%20the%20Future digg.com reddit

HTML5 History is the Future

interface History {
  ...
  void pushState(in any data, in DOMString title, in optional DOMString url);
  void replaceState(in any data, in DOMString title, in optional DOMString url);
};
interface HTMLBodyElement : HTMLElement {
  ...
  attribute Function onpopstate;
};

The new interface provides push/pop semantics that, until now, we've emulated using JavaScript libraries like Really Simple History.

Web Apps and History

Web applications need to interact with the browser history to support bookmarkable and "back-buttonable" states. This is a pretty common feature of Ajaxy applications. For example, visit http://www.viewru.com/#Bonobo and browse or search for your favorite musician. The application uses an Ajaxy, single-window-context model to keep the music playing while you browse, but supports bookmarks and the back-button because users expect these features to work in their browser.

Prior to the HTML5 History additions, a solution to this problem involved these two parts:

// Store a representation of the state in the location hash
function setState(state) {
  window.location.hash = state;
}
// Detect a change to location.hash
var lastState;
window.setTimeout(function() {
  var state = window.location.hash;
  if (lastState != state) {
    lastState = state;
    handleState(state);
  }
}, 100);

The solution is less than ideal for several reasons:

Sluggishness
  • Executing a timeout function every 100ms won't make your app any faster.
  • 100ms delay in responding to back button actions.

Browser compatibility
  • The solution above doesn't work, for example, in IE 6-7, which won't add a history event on a hash change. Really Simple History gets around this by adding an iframe and issuing a call to your server for every state change. Webkit browsers present different challenges.

Not a stack
  • Suppose your application transitions from A->B->C. Can it transition directly back to A?

The HTML5 version looks like this:

// Push the state onto the history stack
function setState(state) {
  window.history.pushState(state, '');
}
// Respond to a popstate event.
window.onpopstate = function(e) {
  handleState(e.state);
};

Here, state may be a string representation of the current application state, but it can also be an object. Note that it will be deep-copied on each pushState() operation.

Storing application state in the hash is a hack

You may point out that the new hashchange event in HTML5 addresses concerns about sluggishness, but why use location.hash at all? Storing application state in the location hash presents some real problems and limitations. Here's a short list:

  • Content isn't crawlable: Most crawlers do not execute JavaScript or traverse dynamic fragment ids. If you want users to find your site and content via Search Engines, this just won't do.
  • Audible "click" in IE: For some reason, IE makes a sound every time you update the location. Imagine if your application updated the location on every mousemove event!
  • HTTP-Referer is inaccurate or missing data: The fragment ID isn't exposed in the referrer.
  • Handling bookmarks requires extra round trip:
    • Fetch myApp.html
    • Parse myApp.html#myState (server can't see myState)
    • Fetch myState
  • Legacy (and non-JS) browsers effectively have different URI schemes and can't share bookmarks and links.

HTML5 History and URLs

Perhaps the most interesting feature of the new pushState() and replaceState() interfaces is the optional url argument. For the first time, web developers are able to change the effective URL to represent a change in application state without navigating away from a window context. It's very powerful. The location bar, bookmark service, and HTTP-Referer all reflect the new URL. This feature addresses many of the issues I mentioned above:

  • Content is crawlable: A developer may express all crawlable states of their application as valid URLs.
  • HTTP-Referer is meaningful: The referrer can reflect the latest state of the application and show or hide any information to/from 3rd parties, at the developer's discretion.
  • Bookmarks can be handled in a single fetch: Since application state can be expressed in a URL, it's possible to service a request for the application and state in one request/response.
  • Can use a single URI scheme to cover new and old browsers: Since information is encoded in URLs instead of the location hash, modern and legacy users can share bookmarks and links.
  • Audible "click" in IE? Okay, I don't know about this one, but it makes sense that programmatic operations on window.history would not have an audible side effect, right?

Availability and further reading

You can take these new features for a spin in Firefox nightlies. For more info, see:

Comments

Great overview, Kyle. Thanks.

Completely tangential, however... For the past 6 years or so, I've always used Bonobo is my default artist search in a music service. Happy to see you do, too. :)

It's a dirty hack, that I decided to ditch from a project and instead uses legacy methods and the HTML5 method of pushState.

It's worth mentioning though that you don't always have to poll a hash change. Many modern browsers support the window.onhashchange event. Even IE8 supports onhashchange, and who's going to pretend that's a modern browser? :P

Post a Comment

(optional)
(optional)