Iframes, onload, and document.domain
In this new Web 2.0, mashup world that the Internet has become, a lot of focus has been placed on the use of iframes for embedding third-party content onto a page. Iframes provide a level of security since JavaScript access it limited by domain name, so an iframe containing content from another site cannot access JavaScript on the containing page. This cross-domain restriction goes both ways as the containing page also has no programmatic access to the iframe. In all ways, the containing page and the iframed page are cut off from communication (which has led to the cross-document messaging API in HTML5). The missing piece of intrigue in most discussions surrounding iframes is JavaScript object ownership.
Iframes and ownership
The iframe element itself, <iframe>, is owned by the containing page, and so you may work on it as an element (getting/setting attributes, manipulating its style, moving it around in the DOM, etc.). The window object representing the iframe content is the property of the page that was loaded into the iframe. In order for the containing page to access the iframe’s window object in any meaningful way, the domain of the containing page and the iframe page need to be the same (details).
When the domains match, the containing page can access the window object for the iframe. The iframe element object has a property called contentDocument that contains the iframe’s document object, so you can use the parentWindow property to retrieve the window object. This is the standard way to retrieve the iframe’s window object and is supported by most browsers. Internet Explorer prior to version 8 didn’t support this property and so you had to use the proprietary contentWindow property. Example:
function getIframeWindow(iframeElement){
return iframeElement.contentWindow || iframeElement.contentDocument.parentWindow;
}
Additionally, the containing page’s window object can be retrieved from the iframe using the window.parent property. The iframe page can also retrieve a reference to the iframe element in which it resides by using the window.frameElement property. This crosses the ownership boundary since the iframe is owned by the containing page but is directly accessible off the iframe’s window object.
Using the iframe element’s onload
Trying to determine when an iframe is loaded is an interesting task due the ownership issues surrounding iframes. Browsers that aren’t Internet Explorer do something very useful: they expose a load event for the iframe element so that it’s possible for you to be aware when an iframe has loaded, regardless of the content. Since the iframe element is owned by the containing page, you never need to worry about cross-domain restrictions. An iframe loading local content can be monitored just as well as an iframe loading foreign content (experiment). Example code:
var iframe = document.createElement("iframe");
iframe.src = "simpleinner.htm";
iframe.onload = function(){
alert("Iframe is now loaded.");
};
document.body.appendChild(iframe);
This works in all browsers except Internet Explorer (even version 8!). I had hoped that perhaps using the Pretty disappointing.attachEvent() method would work, but alas, Internet Explorer just doesn’t support the load event on an iframe element.
Using the iframe window’s onload
It seemed that Internet Explorer was going to foil my day…again. Then, I remembered that I’m not worried about foreign content in an iframe. In my specific case, I was dealing with content from the same domain. Since the cross-domain restriction didn’t apply, I could access the iframe’s window object directly and assign an onload event handler. Example:
var iframe = document.createElement("iframe"),
iframeWindow;
iframe.src = "simpleinner.htm";
document.body.appendChild(iframe);
iframeWindow = iframe.contentWindow || iframe.contentDocument.parentWindow;
iframeWindow.onload = function(){
alert("Local iframe is now loaded.");
};
The interesting part of this approach is that you have to assign the event handler after the iframe element has been added to the page. Prior to that, the iframe’s window object doesn’t exist and so you can’t assign the event handler. This approach works in Internet Explorer and Firefox for same-domain pages only. Other browsers haven’t yet created the window object and so throw an error (experiment).
Enter document.domain
I had resigned myself to using one method of detecting an iframe loading for Internet Explorer and another for every other browser, so I continued on my task. Next, I had to set document.domain on the containing page because I had a couple of different subdomains from which I needed to load iframes. When using different subdomains, setting document.domain to the root of the hostname allows iframes to communicate with their parent and each other. For example, if I had to load an iframe page from www2.nczonline.net, that is technically considered a different domain and would not be allowed. However, if I set document.domain to “nczonline.net” in both the containing page and the iframe page, the two are allowed to communicate. A single line of code, ideally placed at the top of the page, is all it takes:
document.domain = "nczonline.net";
This equalizes the domain difference and allows everything to work as if both pages were from the same domain. Or so I thought.
The problem with this approach is that prior to the iframe being loaded, it’s still considered to be owned by the domain as specific in its src attribute. A relative path automatically prepends the domain on which the containing page was loaded from (www.nczonline.net) versus the one assigned to document.domain. That means a comparison of wnczonline.net to www.nczonline.net fails the same-domain check and causes a JavaScript error when you try to access the iframe’s window object (experiment). The iframe page won’t have its associated domain changed until it’s loaded and the JavaScript command to change the domain has been executed. Once the iframe page has been loaded, however, everything works fine. But how do you know once the iframe page has been loaded?
Reversing the process
Having still not come across a cross-browser solution to determining when an iframe has loaded, I decided to reverse my thinking. Instead of the containing page asking when the iframe is loaded, what if the iframe told the containing page that it was loaded? If the iframe page listened for its own load event and then told the containing page when that occurred, that should solve the problem. I wanted this to be as simple as assigning an event handler, so I came up with the following idea: I’d assign a method onto the iframe element. Then, the iframe page will call that method when it has loaded. The method has to be assigned to the element rather than the iframe’s window object because the latter doesn’t exist in all browsers at an early enough moment in time. The result looked like this:
var iframe = document.createElement("iframe");
iframe.src = "simpleinner.htm";
iframe._myMethod = function(){
alert("Local iframe is now loaded.");
};
document.body.appendChild(iframe);
This code assigned a method called _myMethod() onto the iframe element. The page being loaded in the iframe then adds this code:
window.onload = function(){
window.frameElement._myMethod();
}
Since this code is executed after the assignment to document.domain, there are no security restrictions to worry about. This works great for any resources that share the same root hostname (experiment). It works across all browsers, which is exactly what I was looking for, but the problem of detecting when a foreign resource was loaded in an iframe was still annoying me.
Using the iframe’s onreadystatechange
I decided to look into Internet Explorer’s iframe implementation a little bit more. It was clear that assigning something to the onload property didn’t produce the desired effect, but I figured there must be something else similar. I tried to attach the event handler using attachEvent(), which also didn’t work. Okay, clearly there was no support for the load event on the iframe. What about something else?
That’s when I recalled IE’s bizarre readystatechange event that it has on documents. This is, of course, completely different than the readystatechange event fired on XMLHttpRequest objects. I wondered if the iframe element might support this event as well, and as it turns out, it does. The iframe element supports the readyState property, which is changed to “interactive” and then “complete” when the contents of the iframe have been loaded. And because this is on the iframe element and not on the iframe window object, there is no concern about cross-domain restrictions (experiment). The final code I ended up with is along these lines:
var iframe = document.createElement("iframe");
iframe.src = "simpleinner.htm";
if (navigator.userAgent.indexOf("MSIE") > -1 && !window.opera){
iframe.onreadystatechange = function(){
if (iframe.readyState == "complete"){
alert("Local iframe is now loaded.");
}
};
} else {
iframe.onload = function(){
alert("Local iframe is now loaded.");
};
}
document.body.appendChild(iframe);
The check to determine if the browser is IE or not is a bit messy. I would have preferred to check for the existence of iframe.readyState, however, this throws an error when you try to access the property prior to adding the iframe into the document. I considered using the existence of document.readyState to determine whether to use readystatechange, however, most other browsers now support this property, so that’s not a good enough determinant. With YUI, I’d just use Y.UA.ie to determine this (you can use whichever method suits you best).
IE’s hidden onload support
Shortly after posting this blog, Christopher commented that using attachEvent() on the iframe element works in IE. I could have sworn I tried this before but, due to his prompting, I whipped up another experiment. As it turns out, he’s completely correct. I had to dig through the MSDN documentation to eventually find a roundabout reference, but sure enough, it’s there. This led to a final code snippet of:
var iframe = document.createElement("iframe");
iframe.src = "simpleinner.htm";
if (iframe.attachEvent){
iframe.attachEvent("onload", function(){
alert("Local iframe is now loaded.");
});
} else {
iframe.onload = function(){
alert("Local iframe is now loaded.");
};
}
document.body.appendChild(iframe);
This code also works in all browsers and avoids any potential issues surrounding the timing of the readystatechange event versus the load event.
Wrap-up
After quite a bit of investigation, it appears that it is possible to determine when an iframe has loaded across all browsers regardless of the iframe page’s origin. This makes monitoring and error handling of iframed content a lot easier to manage. I’m thankful that all browser vendors saw the benefit of adding these events to the iframe element itself rather than relying on the iframe window object or expecting that we usually don’t care whether an iframe has been loaded or not.
Update (15 Sep 2009): Added section about attachEvent() based on Christopher’s comment.
Disclaimer: Any viewpoints and opinions expressed in this article are those of Nicholas C. Zakas and do not, in any way, reflect those of Yahoo!, Wrox Publishing, O'Reilly Publishing, or anyone else. I speak only for myself, not for them.
You can leave a response, or trackback from your own site.




26 Comments
Why not:
iframe.onload = iframe.onreadystatechange = function(){
if( this.readyState && this.readyState !== “complete” && this.readyState !== “loaded” ){
return;
}
alert(’Local iframe is now loaded’)
}
It makes it a bit easier to use, maintain and read
V1 on September 15th, 2009 at 9:16 am
Nice post.
I was just wondering if in final example ‘event’ in element event detection trick will work:
if (navigator.userAgent.indexOf("MSIE") > -1 && !window.opera){
iframe.onreadystatechange = function(){
if (iframe.readyState == "complete"){
alert("Local iframe is now loaded.");
}
};
} else {
iframe.onload = function(){
alert("Local iframe is now loaded.");
};
}
if ('onload' in iframe){
iframe.onload = function(){
alert("Local iframe is now loaded.");
};
} else {
iframe.onreadystatechange = function(){
if (iframe.readyState == "complete"){
alert("Local iframe is now loaded.");
}
};
}
Radoslav Stankov on September 15th, 2009 at 12:41 pm
Nicholas,
Just did not believe that IE does not support onload for iframes. Clearly remember that I used the technique many times, both declaratively (through HTML onload attribute) and imperatively using attachEvent.
So, I reproduced your code and it seems that only traditional event binding using onload property does not work in IE indeed (and it just so happend that I did not used it).
The other two ways to attach onload event work for me:
var iframe = document.createElement(”iframe”);
iframe.setAttribute(”onload”, “alert(’test1′)”);
iframe.attachEvent(”onload”, function() { alert(”test2″); });
iframe.src = “test.aspx”;
document.body.appendChild(iframe);
Is there any possibility, that you used “load” event name in the attachEvent() method instead of IE’s “onload”?
Alexei Gorkov on September 15th, 2009 at 2:41 pm
Good write up. The only thing that disappoints is `userAgent` sniffing : (
Instead, I would at least use an inference based on property (corresponding to intrinsic event handler) existence. I find it to be a little less fragile than inference based on a userAgent string.
`if (’onreadystatechange’ in iframe) { … }`
Have you, by any chance, seen - http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ ?
kangax on September 15th, 2009 at 2:52 pm
Hey Nicholas,
“onload” for iframes does work for me in Internet Explorer when using the attachEvent method.
var iframe = document.createElement("iframe");
iframe.src = "http://google.de";
iframe.attachEvent("onload", function(){
alert("Iframe is now loaded.");
});
document.body.appendChild(iframe);
Tested in IE 5.5-8.
Christopher on September 15th, 2009 at 2:55 pm
On an unrelated note, it might be worth mentioning that iframe’s `window` can also be accessed via DOM0 (and, IIRC, making its way into HTML5) `window.frames` object.
kangax on September 15th, 2009 at 3:00 pm
@kangax - UA sniffing doesn’t bother me like it does some people.
That is an interesting approach, though.
@Christopher - Interesting! I’ll need to look at that. I’ll have to look over the examples I have and see where I went wrong. Incidentally, a co-worker had mentioned getting this working in a standalone and then being unable to build upon it. I’ll dig in a little more, thanks for the heads up!
Nicholas C. Zakas on September 15th, 2009 at 7:05 pm
[...] This article by Nick Zakas, covering some technical issues in iframe loading, triggered me to surface a JQuery IFrame plugin I made a little while ago, which supports loading IFrames. (I did tweet it at the time, though I’ve since changed the location.) [...]
JQuery IFrame Plugin on September 15th, 2009 at 7:12 pm
@Christopher - I just confirmed, thanks a lot for pointing that out. I’ve updated the blog post to reflect your contribution.
Nicholas C. Zakas on September 15th, 2009 at 9:56 pm
@Alexei - somehow your comment got caught in moderation. I should have you tech edit my blog posts, too.
Nicholas C. Zakas on September 15th, 2009 at 10:01 pm
Yes , iframe supports load event for all browsers ,i have tested this feature many times before . only in IE when you attach you need to attach ‘onxx’ , i think the real weird thing is script load judgment , that’s where readystatechange in IE is used inevitably as your new book (Even faster websites) shows .
Thanks for your summarization
yiminghe on September 16th, 2009 at 3:22 am
Nicholas, I am always at your disposal :).
Alexei Gorkov on September 16th, 2009 at 4:27 am
Does attachEventListener work as well? If so, using jQuery, this should work, too:
$("").attr("src", "simpleinner.htm").load(function() {alert("iframe loaded!")
}).appendTo(document.body);
Jörn Zaefferer on September 16th, 2009 at 7:36 am
Nicholas-
This is really good simple code, thanks! I implemented it in a little test, and tested it across a bunch of browsers to good success. However, both Opera 9.64 and Opera 10RC1 fail to ever get the load event on the iframe. I can watch the console and the logs and it does load them, but the event never fires.
The only difference with my approach is that the iframe is initially hidden (display:none) and I show it when the load occurs. Is it possible this is the reason it fails in Opera?
Kyle Simpson on September 16th, 2009 at 4:27 pm
FYI: Just made the iframes display:block, still don’t get the load event fired for them.
Kyle Simpson on September 16th, 2009 at 4:54 pm
@Kyle - That’s very strange, I tested these on Opera and they worked okay for me. If my experiments work and your example does not, then it’s probably related to something else on the page.
Nicholas C. Zakas on September 16th, 2009 at 11:00 pm
OK, after further testing in Opera 9.64 and 10RC1 in windows, here’s what I’m seeing:
If I add more than one iframe to the page using the techniques described above, then the “load” for each one will not happen until ALL are loaded. In my test previously, I had a timeout script which would come along and delete an iframe if it didn’t load within 10 seconds, and then i had scripts on the server that would randomly make the response to the iframe take anywhere from 2 - 20 secs to load.
So, in all my tests, I was getting at least one iframe that wouldn’t load before the timeout, which meant then that one iframe was removed, and thus *none* of the handlers got fired, even for the iframes where clearly the content was loaded because it was visible.
If I remove the timeout, and just let them load as slowly as necessary, all 3 do eventually load, and once they do, the load handler for each fires in successsion immediately. But iframe1’s load handler won’t fire immediately when iframe1 actually loads, only when all 3 have loaded.
In all other browsers, the same code works fine and handlers are fired as soon as the content loads for each iframe respectively.
Any thoughts?
Kyle Simpson on September 17th, 2009 at 8:53 am
@kangax
The user-agent sniffing is a very poor inference indeed. “MSIE” being the user-agent is totally unrelated to
onreadystatechange.A “load” event is standard for
FRAMESETbut not forIFRAME. In Gecko and IE (and recent versions of Webkit), attachEvent/addEventListener can be used to add a non-standard “load” event callback on the iframe element. Just be aware that it is non-standard and doesn’t work in “all browsers” (not Safari 3.1.2) and onload does not seem to work in Safari 2.x.I’m not working on Mashups (thankfully) so I don’t really have to assess the needs of the program and find a way that works and is widely supported.
@kangax (again) yes, another good call. Reading the
framescollection is more widely supported thaniframe.contentWindowand much more widely supported thaniframeEl.contentDocument.parentWindowEasy to remember (and type), so usingframes["myIframeName"] a good choice.(though reading/modifying the
onloadof theiframe’scontentWindowshould not work cross-domain, and the same goes forframes["myIframeName"].onload(should be a security error).)@Cristopher, I believe the answer you provided was also provided before on comp.lang.javascript, and it does seem to work in a few browsers.
@Nicholas, Why no test?
Garrett Smith on September 18th, 2009 at 8:44 pm
Great post!
Are there a way to find out what was the HTTP status (401, 500, 404, 403, etc) for the iframed request?
Sergey on September 30th, 2009 at 11:21 am
I can see onload event fired from iframe on both IE and Firefox if the content is html. Try loading pdf content into iframe and see that the onload will NEVER fire on either IE or Firefox.
Luan on October 13th, 2009 at 11:38 am
[...] http://www.nczonline.net/blog/2009/09/15/iframes-onload-and-documentdomain/ [...]
Tolsdorf.NET » Iframe Security and the onload event on November 10th, 2009 at 10:50 pm
Very helpful information by OP. The comments by Alexei and Christopher were exactly what I was looking for.
Just wanted to add my snippet which uses feature sniffing instead of browser sniffing so it’s more future-proof.
var iframe = document.createElement("iframe"),callback = function () {
/* Event handling code here */
};
("addEventListener" in iframe) && iframe.addEventListener("load", callback, false)
|| ("attachEvent" in iframe) && iframe.attachEvent("onload", callback);
iframe.src = "http://www.google.com/";
document.body.appendChild(iframe);
Sebastiaan Deckers on November 28th, 2009 at 6:33 pm
[...] There are some security limitations for Iframe scripting, so if you need to manipulate the host site page in any additional way, you would need to include a script <tag> as well in the initial embed code to bypass the security restrictions. [...]
Creating embedabble widgets « techfounder on February 4th, 2010 at 11:36 pm
Interesting if it is possible to use cookies instead of URL as communication channel between cross-domain iframes.
For example between parent and child iframe with different domain names.
Miroslav Nikolov on February 8th, 2010 at 11:25 am
I just wanted to say that this saved me a world of headache, thanks so much for the post and for the ‘IE’s hidden onload support’ addition. I am beside myself that IE is still this backwards!
Ben on April 13th, 2010 at 3:48 pm
The second experiment (http://www.nczonline.net/experiments/javascript/iframes/onload2.htm) does not work on IE 6 in my tests, contrary to what you say. I cannot figure out how to make the onload property work in IE 6 on any object I’ve tried.
Andrew Pimlott on April 20th, 2010 at 7:15 pm
Leave a Comment