XmlHttpRequest subdomain update
Firefox 1.5 shipped yesterday, and it brings good news for my work on cross-subdomain XmlHttpRequest calls (I’m going to start using the abbreviation XHR for XmlHttpRequest now, to save on typing). In previous releases of Firefox (and in the other browsers I tested) setting document.domain in an iframe caused the iframe to lose the ability to make XHR calls. But for Firefox 1.5, the Mozilla developers decided this was a bug, and fixed it. So going forward you no longer need to resort to any ugly hacks to make cross-subdomain XHR calls in Firefox: load an iframe from the same server you’ll be making XHR calls to, set document domain in both the original window and iframe, and then proceed to make XHR calls in the iframe. It’s beautiful. Many thanks to Peter Van der Beken and Brendan Eich at Mozilla for taking care of this.
This means that the really hacky part of the cross-subdomain XHR technique I talked about the other day is now only needed for old versions of Mozilla-based browsers. This is great news because it addresses worries about the iframe-bridge hack going away in the future: it may very well not work in future Mozilla releases, but it doesn’t matter because we’ve got a much better solution.
I’ve posted some updated code that does the right thing in Firefox 1.5, while keeping the iframe bridge hack on older versions: test5.html. This works in Firefox 1.07, Firefox 1.5, IE6, Safari 1.3/2.0, and Konqueror 3.4.3. It still doesn’t work on Opera, which is looking like a lost cause unless someone can come up with an Opera-specific hack. Here’s the iframe code:
<html>
<head>
<script type="text/javascript" src="xmlhttp.js"></script>
<script type="text/javascript">
var AJAX_URL="http://www.fettig.net/playground/ajax-subdomain/ajaxdata.php";
function getTime(){
try {
getUrl(AJAX_URL, gotTime);
} catch(e) {
// getUrl failed, probably because we're running in an older
// Moz/Firefox/Gecko and we we've changed document.domain.
// switch to the bridge hack (if we haven't already)
if (window.bridgeGotTime) {
throw e;
} else {
document.location.replace("test5-bridge.html");
}
}
}
function gotTime(status, headers, result) {
var oldDomain = document.domain;
if (window.bridgeGotTime) {
window.bridgeGotTime(result);
} else {
document.domain = "fettig.net";
window.parent.gotTime(result);
}
// try to set document.domain. this is needed for IE/Konq/Safari
// it will fail in Gecko-based browsers, which is ok in ff > 1.5, but
// which forces the use of a bridge iframe hack in earlier Geckos.
try {
document.domain = oldDomain;
} catch (e) {}
setTimeout(getTime, 1000);
}
getTime();
</script>
</head>
</html>
Note that this doesn’t do any browser sniffing, it just tries different techniques until it finds one that works (or until they all fail).
I’ve also tweaked the bridge code a bit, to clean it up and to avoid the case where it only half works and you end up with a bunch of recursively loaded iframes. Here’s the updated code:
<html>
<head>
</head>
<body>
<script type="text/javascript">
function gotTime(result) {
window.parent.gotTime(result);
}
var subframe = document.createElement('iframe');
document.body.appendChild(subframe);
function setupBridge() {
subframe.contentWindow.bridgeGotTime = gotTime;
document.domain = "fettig.net";
}
subframe.src = "test4-iframe.html";
subframe.contentWindow.onload = setupBridge;
</script>
</body>
</html>