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>
XmlHttpRequest subdomain update
This Abe Fettig’s Weblog � XmlHttpRequest subdomain update is an update to the article I linked to in 42: How to make XmlHttpRequest calls to another server in your domain. Good news is that Firefox 1.5 is better than Firefox
Trackback by 42 — November 30, 2005 @ 6:17 pm
I’m hot on your heels… I found that the single iframe version works in older firefox versions when the web server uses port 80. Firefox Ajax calls don’t ignore the domain value but seem to assume a port value of 80. When the document.domain value fits the actual domain and the server uses port 80, ajax calls and the cross iframe scripting work. This is not enough for me because I am not root at my workplace and am not allowed to start webservers on port 80. So thank you for the info about firefox 1.5, that makes my life a lot easier.
Comment by Martin Scheffler — December 1, 2005 @ 6:41 am
Thanks for this new release. I’m into using SOAP web services directly with XmlHttpRequest so cross-(sub)domain access is a major interest for me. Maybe there is a solution for opera, too. Apparently, Opera implements so-called “Cross-document messaging”. have a look here:
http://virtuelvis.com/archives/2005/12/cross-document-messaging
Maybe this can be incorporated into your solution.
Comment by Thomas Rudin — December 2, 2005 @ 7:22 am
Thanks, Thomas. That’s the first I’ve seen of cross-domain messaging. It definitely looks like a good solution to this kind of problem, as long as you’re willing to accept the limitation of only being able to pass an event name and a string (which is really not bad considering that the string could contain xml or javascript).
Comment by Abe — December 2, 2005 @ 12:59 pm
very cool! nice work!
but how come there’s still a link to “test4-iframe” in here? does that mean there are:
- the main page
- the test5-iframe.html
- the test5-bridge.html
- AND test4-iframe.html??
Comment by schav — December 12, 2005 @ 5:31 pm
Good catch, schav. That’s a typo I didn’t catch - it should be test5-iframe.html. You don’t need the any of the test4 files.
Comment by Abe — December 12, 2005 @ 11:56 pm
i’ve been testing out the code, and i can get it to work with my setup successfully. great job, abe!
one thing though, was wondering if you took this model and have been using it. in your example you return the responseText of the XML…this works fine and dandy, but i tried returning the responseXML.documentElement to the parent page function (e.g., gotTime()) and run into permission issues when i try to call any methods, such as “GetElementsByTagName()” on it, in Firefox. Was wondering if you are seeing the same thing. This works fine in IE though.
Comment by schav — December 14, 2005 @ 8:53 am
nevermind…i see your earlier post about limitations on text strings…
thanks!
Comment by schav — December 14, 2005 @ 9:08 am
schav - I assume you’re using Firefox 1.0, with the bridge hack? I’d guess that Firefox puts responseXML.documentElement in the same domain as the child frame, thus imposing the same restrictions on access as two regular HTML-page documents in different frames. If possible, I’d try processing the document in the child frame to extract what you need from it, and then passing only the extracted string values up to the parent page, or pass the XML document as text and load it into a node in the parent page for DOM access.
Comment by Abe — December 14, 2005 @ 10:42 am
[…] ject
used in Ajax applications. Web geeks, please read on.
Updates to this post
See this post for an updated version of this techinique that works (hack-free) i […]
Pingback by Abe Fettig’s Weblog » How to make XmlHttpRequest calls to another server in your domain — January 4, 2006 @ 8:15 am
Abe, your technique is elegant and functional - kudos.
However, I’m wondering if anyone has found a way to implement the same functionality for non-port 80 web servers - I’m currently trying to integrate a web-based IM client (which is served from http://chat.foo.com:5280/) into a frames-based website (which is http://www.foo.com/) for a client, but trying to set document.domain back to the original value in my bridge frame is throwing an “illegal value” exception.
Any tips/ideas (other than moving the chat server to port 80)?
Comment by David Quick — January 5, 2006 @ 12:31 pm
David Quick: why not use a firewall redirect to map port 5280 to port 80 on the chat server?
Comment by Chris Snell — February 6, 2006 @ 2:05 pm
David:
I’ll reply by email too since this is a month afeter your comment, but I haven’t had any problems using this on ports other than 80. The only place it won’t work is when one server is using HTTPS and the other isn’t.
Comment by Abe — February 6, 2006 @ 9:56 pm
I have the same problem in a similar situation which I solved a bit differently.
My webapps run in Apache & JBoss2 behind the DMZ. The webapps are served by a portal that runs in JBoss1 in the DMZ. My Ajax code is in my webapps & needs to access stuff on Apache or JBoss2.
The browser thinks that the webapps are served by JBoss1 & won’t let my ajax code talk to Apache or JBoss2. My solution was to create an application agnostic proxyServlet in JBoss1. This servlet is about 50 lines of code - it takes a url (to Apache or JBoss2) sent to it by the ajax code, runs it & returns the String result back to the ajax code - tested & works in FF1.5 & IE6. I can practically access any server outside of the ajax code’s originating server this way.
Comment by Amit P. — February 21, 2006 @ 8:02 pm
Amit: The setup you describe makes perfect sense. Unfortunately in my case I was specifically looking for a way to expose two different web servers to the client, with no proxy in front of them. But at other times I’ve done something similar, with Twisted or Apache acting as the reverse proxy.
Comment by Abe — February 21, 2006 @ 11:04 pm
Wondering if the interaction between the parent and the child can still occur if the src attribute of the iframe is first changed. What I am looking to do and am having problems with, is to load a page with an iframe where the iframe src = “#”, upon clicking a link the src is changed, the html from the that src url is loaded and then that html is read, stripped of useless stuff I dont want and then reloaded back into the same iframe. With the code you have above does it account for IE6 not allowing the frame contents to be changed once the src attribute is changed?
Comment by scottr — February 22, 2006 @ 5:19 pm
Hi, Abe, thanks for great article.
2 fixes needed..
First, getUrl function should add something random to request to prevent IE from caching XMLHttp response.
Simple test did not work for me out-of-the box, so I fixed it like:
Line 20:
function getUrl(url, cb) {
var xmlhttp = getXmlHttp();
xmlhttp.open(”GET”, url+’?r=’+Math.random());
Second, last Opera build seems to have the same fix as Firefox 1.5
This fix makes your non-bridged example test3 work in IE, Opera 9, FF 1.5
line 10:
try { document.domain = oldDomain; } catch(e) {}
Comment by Ilia Kantor — March 21, 2006 @ 5:34 am
Setting different ports doesn’t appear to work in pre-1.5 firefox. Works fine in 1.5 though.
Comment by Mike — March 30, 2006 @ 5:55 am
I really want to get this example working for my situation. I have access to two hosted servers, hosted by different companies and totally different domains.
I originally tried doing what was posted above on my own but ran into trouble because of the domain and security violations as you did.
I tried adapting your model to mine and found it to not work. I get an error when I try to alter the document.domain. I get an illegal document.domain value.
Your example works on your servers but your example copied on my server with changes to the requested document (for obvious reasons) and the domain value from “fettig.net” to “myserver.com” also throw the illegal domain name error in Firefox 1.5.0.2 and in illegal argument exception in IE6.
Does anybody have any ideas? Sources are at:
http://www.nyteshade.com/abetest.html
http://www.sassyvixens.net/abeiframe.html
Comment by Gabriel — April 21, 2006 @ 3:58 pm
I’m new to this whole cross-domain frame mess. Can anyone explain to me why, from a security point of view, its feasible to say: if i control my domain, i also control my subdomains (which is why firefox allowed the setting of document.domain, i assumed) but its not secure to read data from a different PORT from the same DOMAIN? Am I missing something? or is it a technical issue of which I’m not aware? This is so annoying. Costs us hours and hours of time, and all firefox dedicates to it is one lousy page. IE allows a call to a different port…(i know…IE is inherently insecure…but this, i like!)
Help? Can anyone explain this to me?
Comment by Lars Schultz — April 24, 2006 @ 9:20 am
Unless someone else prove’s otherwise my testing shows that this fix from Abe works for cross domain XHRs as long as the second domain is a sub-domain of the first or vice versa.
Comment by Gabriel — April 24, 2006 @ 11:11 am
i dont know if this is correct or not…but i was able to make cross domain calls without much hassle….but as long as it works i am good and it works for all browsers
i retrived the response as string and then converted it to xml to read it…
heres the code…this function will be called by putting this onclick=”makeRequest(’http://rss.cnn.com/rss/cnn_topstories.rss’)”
and pl. put these in ur script tags
var http_request = false;
var xmldoc;
function makeRequest(url) {
if (window.XMLHttpRequest)
{ // for mozilla and firefox
try
{ netscape.security.PrivilegeManager.enablePrivilege(”UniversalBrowserRead”);
} catch (e)
{ alert(”Permission UniversalBrowserRead denied.”);
}
http_request = false;
http_request = new XMLHttpRequest();
if (http_request.overrideMimeType)
{ http_request.overrideMimeType(’text/xml’);
}
}else if (window.ActiveXObject)
{ //for ie
try
{ http_request = new ActiveXObject(”Msxml2.XMLHTTP”);
}catch (e)
{ try
{ http_request = new ActiveXObject(”Microsoft.XMLHTTP”);
} catch (e)
{ }
}
}
if (!http_request)
{
alert(’Giving up :( Cannot create an XMLHTTP instance’);
return false;
}
http_request.onreadystatechange = alertContents;
http_request.open(’GET’, url, true);
http_request.send(null);
}
function alertContents()
{ if (http_request.readyState == 4)
{ if (http_request.status == 200)
{ var string = http_request.responseText;
var xmldoc;
if (window.XMLHttpRequest)
{ var parser = new DOMParser();
var doc = parser.parseFromString(string, “text/xml”);
xmldoc = doc.documentElement;
}else if (window.ActiveXObject)
{ xmldoc = http_request.responseXML;
}
var title = xmldoc.getElementsByTagName(”title”)[0].firstChild.nodeValue;
var desc = xmldoc.getElementsByTagName(”description”)[0].firstChild.nodeValue;
alert(’title ‘+title);alert(’description ‘+desc);
} else
{ alert(’There was a problem with the request.’);
}
}
}
Comment by anks — June 14, 2006 @ 7:25 pm
anks -
I believe you can only enable additional privileges that way if you’re accessing a local file. Did you try putting your test file on a web server and seeing if it still worked?
Comment by Abe — June 14, 2006 @ 10:21 pm
Hi to all.
22- Anks!
this is a good code but unfortunately - it doesn’t work with Opera =(.
Opera don’t understand this command:
“netscape.security.PrivilegeManager.enablePrivilege(’UniversalBrowserRead’);”
So it fires “Security violation error” after
“http_request.send(null);”
Comment by nice_day — July 15, 2006 @ 7:03 pm
to Thomas Rudin:
Thanks for URL - I GOOGLED a little and found some adittional information about cross-Document messading!
Look..
http://whatwg.org/specs/web-apps/current-work/#crossDocumentMessages
Here’s located Web Applications 1.0 STANDART which can be helpful for cross-Document messading in browsers.
Comment by nice_day — July 15, 2006 @ 8:08 pm
I’m having an issue with Netscape 8.1.2. The bridge loads, which in turn loads the child iframe but this statement does not seem to be getting executed:
subframe.contentWindow.onload = setupBridge;
I put an alert statement inside the setupBridge function which should pop up when the child iframe is finished loading but that’s not happening.
Any ideas what could be wrong?
Comment by Andres — December 1, 2006 @ 11:38 am
Following Gabriel’s comment on cross domain AJAX being only possible within subdomains, does the comment still stand true?
I have an online application on a server(let’s say @ www.foo.com) which needs to poll AJAX-queries to a localhost server. Can I still use the above method?
I’m trying your methods, but it seems like I can only change document.domain to a subdomain value, else an illegal value error will be thrown.
It’s quite vexing that I can only workaround this using object signing, because object signing only allows me to serve up static pages, whereas I have dynamic content..
Comment by Frank — January 4, 2007 @ 10:41 pm
[…] Abe Fettig’s Weblog » XmlHttpRequest subdomain update Ruddy AJAX not supporting subdomain-ness. Well and truely rubs. (tags: ajax xmlhttprequest javascript) […]
Pingback by John Martin Blog » Blog Archive » links for 2007-01-05 — January 5, 2007 @ 1:24 pm
I’m so dumb. I think all these subdomain issues need not be addressed if you simply use the JSON framework. Google for “JSON” and “cross-domain” together.
Comment by Frank — January 8, 2007 @ 1:13 am
This no longer works in Firefox 2.0
Comment by Victor — January 17, 2007 @ 1:43 pm
Victor, I just went to test5.html with FF2 on a Mac and it is working like a charm.
Comment by Brandy — May 24, 2007 @ 10:01 am
[…] to a browser on domain E. However, it is possible for domains to communicate with sub-domains using the document.domain technique in Firefox, IE, Safari, and […]
Pingback by Alex Pooley’s Blog » Blog Archive » How To Cross Domain Javascript — August 7, 2007 @ 7:27 am
[…] Pooley has written up his thoughts on cross domain JavaScript via DNS. Alex builds on the document.domain fun: The Problem From a naive perspective, it is not possible for a web page from domain D, to […]
Pingback by Ajaxian » Cross domain JavaScript via DNS — August 7, 2007 @ 9:51 am
[…] I noticed two interesting articles. The first was the specifics of writing these queries (located here). Then, the next gave a breakdown of how this might be useful in a mash-up collaborative sense […]
Pingback by Cross Domain AJAX - A quick anatomy of a mashup | 102 Degrees — September 19, 2007 @ 3:40 pm