It started out innocently enough: the LinkedIn Platform needed to run our Professional Plugins on third party sites. To do the basic process of resizing iframes (to fit our plugins), we needed a way to talk across domains. With HTML5, we gained access to postMessage(), which was a nice standard way of talking between domains. For the browsers such as IE6 and IE7, libraries such as Apache Shindig and easyXDM have found a way to use Visual Basic objects (and later Flash) to coerce older browsers into supporting a compatible interface. Internet Explorer 8, however, has a set of quirks in its window.postMessage() which will frustrate users of home-grown solutions and libraries alike. Inevitably, another developer is going to run into these problems.
If you’re using popup windows alongside a cross domain library, you’ll probably be calling methods in window.opener to avoid having to create multiple listeners for the window.postMessage object. While this nuance isn’t postMessage specific, if you’re using an RPC implementation in a library, you can expect your success() and error() methods to get mangled.
// child window
function childFunction() {}
window.opener.parentFunction(childFunction);
// parent window
function parentFunction(f) {
alert(typeof f); // object
}
To get around this, you can either serialize the function using toString or make sure that parentFunction doesn’t depend on a typeof check. Even though in the parentWindow it looks like an object, it will still begin with “function” when serialized. As an added check, you can verify the existence of call() and apply() before treating it like a callable object. On some level, what Internet Explorer is doing is correct, as Functions are Objects just like everything else. Since the Parent and Child window both have different window objects, their Function objects would also be different.
window.opener.Function === Function; // false
~~As of Internet Explorer 9, the typeof operator properly displays the object as Function, implying it’s callable. Therefore, the fix can be localized to IE 6/7/8 using a conditional comment.~~
If you’re adventurous and forgo a library for cross domain communication, do not expect it to talk to popup windows in Internet Explorer 8. The worst part about this is that it’s a known bug in postMessage for which the current solution is to simply workaround and pray for higher IE 9 adoption. Thankfully, the workaround is to use window.opener to reach into an iframe, which can then make use of window.postMessage properly. There are only two problems with this approach. In order to reach window.opener, the popup window and iframe will need to be on the same domain or set their document.domain property to be identical. The second problem is a bit more vexing, as reaching through window.opener triggers the first bug mentioned above. The workarounds are still usable, but if you’re passing anything more complex than a primitive between windows, expect some problems to crop up.
To be safe, it’s worth wrapping the things in window.postMessage as a string using JSON. String postMessage calls are definitely less buggy, and certainly reduce your implementation’s complexity since there’s only one object type moving about.
The rules for postMessage security are pretty straightforward. When calls are made, the origin is derived from the window object used to initiate the postMessage call. The below code highlights the IE8 inconsistency. For whatever reason, reaching through window.opener again causes issues, and the domain is changed to the initial script entry point into postMessage versus the page where the code actually resides.
// popup window (popup.example.com)
document.domain = "example.com";
window.opener.postMessageInterface("string");
// window that opened the popup (iframe.example.com)
// just contains normal postMessage()
document.domain = "example.com";
function postMessageInterface(str) {
window.top.postMessage(str);
}
// implementor.com
window.addEventListener("message", function receive(evt) {
// IE 8
alert(evt.origin); // popup.example.com
// Every Other Browser
alert(evt.origin); // iframe.example.com
}, false);
The workaround to this is actually in window.setTimeout. For IE8, we need to break the link between the call to window.opener and the call to postMessage, so that iframe.example.com is used instead for the origin. The resulting code just wraps the postMessage call.
function postMessageInterface(str) {
window.setTimeout(function() {
window.top.postMessage(str);
}, 0);
}
The need to make use of this workaround is even more important if you’re using document.domain to add a CDN to the equation. Even though it’s been set in the example, the values are ignored for postMessage (even though those exact same rules allowed window.opener to be reached in the first place).
Thankfully, the above is instantly dated material. IE9 fixes all these problems with windows and openers and postMessage calls, so all we have to do is convince the internet to update again. The outlook is bright though. IE 9 is being adopted 5x faster than IE 8, and Windows 7 sets the average consumer up on a nice updating path into the future. We’ll only have to cope with these things for a little while longer. This time, it really will get better.
Update: I spoke too soon it seems. As Guy and Shane pointed out in the comments, the IE blog article has since been updated to say the issues persist in IE9. As a result, we’ll be working around these issues for a while more.