This is my personal blog. The views expressed on these pages are mine alone and not those of my employer.

Monday, September 05, 2005

Really Simple History: Bookmarking and Browser History Support for AJAX Applications

I have posted a new specification for AJAX applications I call Really Simple History:

"This document is a strawman proposal for a very simple browser-supported API for AJAX applications to support bookmarking and the back and forward buttons. It is meant to be implemented by both browser manufacturers as well as developers creating emulation libraries to provide such support. It is in alpha draft form and is open to comments. It's operating principles are Keep It Simple Stupid (KISS) and to create APIs that work in the real world."

The spec is super simple, as its name implies, and is straightforward to read. Give it a gander and provide some feedback. I'm going to create an emulation shim for parts of the API soon.

Safari: No DHTML History Possible

I have bad news; it really is not possible to do either bookmarking or intercepting the back and forward buttons in the Safari web browser for AJAX applications.

But first, why would someone want to have bookmarking and back and forward support in AJAX/DHTML apps? First, users expect that web pages will behave a certain way, basicly that they can navigate through their actions using the back and forward controls. Second, bookmarking is important for users to be able to save their state in the middle of a web application, sharing it with friends through email and bookmarking it into their browser. AJAX applications must be able to support these functions to truly replace traditional non-AJAX web apps.

My main goal the last few weeks has been to create a simple API for AJAX applications, named DhtmlHistory, that supports these features. This API would make it possible to register an event handler to find out when the back and forward buttons have been pressed; the ability to register a new history event; and the ability for the framework to automatically update the browser's URL location bar when a new history event occurs, so that users can copy and paste the page's location, bookmark it, and send it around to friends to jump right into a specific state of an AJAX application.

I have the API working for Internet Explorer and FireFox (I need to do more QA work in those browsers though to make it rock solid), and spent much of last week trying to get this functionality going in Safari.

In Safari, this broke down into two segments: getting bookmarking working, and finding a reliable way to control the back and foward buttons and knowing when they are pressed.

On the bookmarking front, it is probably impossible due to two major problems in Safari. The only way to update a URL in an AJAX application is with an anchor, such as #foobar. This is because all other URL updates cause the page to completely reload due to browser security policies. In Safari, when you update the anchor, sometimes the page loading icon begins to spin continiously, never stopping; I tried many things to work around this behavior, but it did not work.

The other problem is that Safari is inconsistent in whether it puts URL changes due to anchors into the browser's history cache. It kinda does and kinda doesn't; if it just did one or the other things would be great. It turns out that if I change the page's location five times, using anchors, such as:

#helloworld1
#helloworld2
#helloworld3
#helloworld4

that the browser places none of the page change locations into the history except the last one; if I press the back button, I am immediately brought back to the initial page load; pressing the forward button takes me back to the #helloworld4.

After trying a million combinations on this one, trying every wierd combination I could think of, I've come to the conclusion that Safari can not support the basic techniques needed for AJAX bookmarking. Strike one Safari.

Okay, so bookmarking isn't possible for AJAX apps. How about back and forward support; that would be good enough? After again trying every obscure and strange hack I could think of, I almost gave up thinking I could somehow hook myself into Safari's history until I finally found a way. This technique ultimately doesn't work out because of a strange, killer bug in Safari, but first, the technique.

The technique essentially involves using an iframe, with a form hidden inside of it that submits its values using a GET request. Here is the pseudocode for this technique:

<iframe>
<form id="historyForm" method="GET">
<textarea id="currentLocation"
name="currentLocation">
</textarea>
</form>
</iframe>


When a developer wants to register a history event and new location, we simply grab the hidden iframe, set the 'currentLocation' field to the new location the developer wants, and then submit the hidden form. This causes the hidden iframe's location to change to match the results of the GET request, such as:

http://SomeLocation.com/historyIFrame.html?currentLocation=helloworld

Then, when the user jumps around with the back and forward buttons, the hidden iframe responds, jumping back and forth between different locations, with different results of the hidden form, such as:

http://SomeLocation.com/historyIFrame.html?currentLocation=helloworld
http://SomeLocation.com/historyIFrame.html?currentLocation=helloworld2
http://SomeLocation.com/historyIFrame.html?currentLocation=helloworld3
http://SomeLocation.com/historyIFrame.html?currentLocation=helloworld4

which we can intercept from within the iframe, extract the currentLocation value from the URL, and pass back up to the containing page to notify a DHTML history listener.

Excited, I fleshed out the entire thing, adding in code to handle the small details necessary to make this technique effective, such as differentiating programmatic changes to the iframe's location from ones that happen from the back and forward buttons.

Thinking I was the man, I uploaded my code to my web server and accessed the page through Safari, and ran into a killer Safari bug that completely puts the kibosh on this technique. It turns out that Safari stores history for forms when loaded from "file://" URLs, but not from "http://" ones. These must be completely different code paths in Safari, where one works correctly and the other does not.

You can see the full source code for this technique here; it will work if you save it to your local file system, but not over http. I tested it in Safari 1.3. I've also tried the remote version in Safari 2.0 and it still does not work over HTTP.

I've tried a variety of other techniques since this one, but all with diminishing returns and increasing complexity. The verdict: Safari really does not support what is necessary to intercept the back and forward buttons. Strike two Safari.

So here's the end result; Safari sucks as a platform for AJAX applications, which I will be writing a rant about soon. At this point I'm ending my explorations of trying to get advanced AJAX features working in Safari.

Safari is officially in the DHTML doghouse, joining our good buddy Internet Explorer. Unlike Internet Explorer, though, we don't have to bend to Safari's whims because it has close to nil market share; getting things to work in Safari is more about being a good Internet citizen versus a project necessity. Getting things working in Internet Explorer is the difference between success and failure.

To Safari I say: you owe me sixty bucks for the time I spent in Internet cafes trying to get this stuff to work, since I don't own a Mac. I will recommend to my clients that they not target Safari for advanced AJAX applications until Safari becomes a DHTML leader rather than a follower.

If Safari wants to get out of the doghouse, they must either completely emulate Firefox's behavior around hidden iframes and anchors, or they must support an API for DHTML history-like functions. I will be posting a Really Simple spec for DHTML history functions soon that it would be great for browser makers to support.

This page is powered by Blogger. Isn't yours?

Subscribe to Posts [Atom]