Eliminate all other factors, and the one which remains must be the truth. —Sherlock Holmes
For the past couple of days, I have had to deal with a couple of fascinating issues reproducible only in Internet Explorer 8 (I used IE9 with compatibility mode).
Let's say you have two pages u2014 hosted on example.com and sub.example.com u2014 and the latter opens the former in an iframe. You also want the iframed page to communicate with its parent by calling a callback using window.parent.callback().
The code above works fine in Chrome, Firefox, Opera and Internet Explorer 9. However, IE8 raises a Permission Denied error when the child page is trying to communicate with its parent. Removing the document.domain part from the child doesn't fix the issue and doesn't make a lot of sense since both parties must opt into the same domain to communicate with each other.
The only hint I had was the fact that we had already implemented a working cross-domain tunnel using a similar technique elsewhere. So, by eliminating all the differences, I found out that due to some mysterious bug, Internet Explorer 8 does not allow a top level domain to communicate with its subdomain. Using alias.example.com (or any other alias) instead of example.com fixes the issue.
Another thing we noticed is that easyXDM u2014 our cross-domain messaging library of choice u2014 refused to pass messages back to the parent page but only after the child page sent at least one AJAX request. As soon as you made a request, easyXDM would start to silently fail at passing messages.
It took me a few debugger statements and console.log calls to find that the problem was in the code below (modified to fit the page):
/**
* Resolves the origin from the event object
* @private
* @param {Object} event The messageevent
* @return {String} The scheme, host and port of the origin
*/
function _getOrigin(event) {
if (event.origin) {
// This is the HTML5 property
return event.origin;
}
if (event.uri) {
// From earlier implementations
return getLocation(event.uri);
}
if (event.domain) {
// This is the last option and will fail if the
// origin is not using the same schema as we are
return location.protocol + "//" + event.domain;
}
throw "Unable to retrieve the origin of the event";
}
/**
* This is the main implementation for the onMessage event.
* It checks the validity of the origin and passes the message
* on if appropriate.
* @private
* @param {Object} event The messageevent
*/
function _window_onMessage(event) {
var origin = _getOrigin(event);
if (origin == targetOrigin &&
event.data.substring(0, config.channel.length + 1) == \
config.channel + " ") {
pub.up.incoming(
event.data.substring(config.channel.length + 1), origin);
}
}
The problem was with IE8 using document.domain value as the origin for all postMessage events after the first AJAX request. Since it was not the case in all other major browsers, including the current version of Internet Explorer, I assumed it was an IE8 browser bug.
To fix that, I was thinking about passing all valid origins to easyXDM, but then I saw a tweet by @jakobo that said, "If window.postMessage in IE8 is the disease, you should know that window.setTimeout is the cure." And he was right: wrapping callbacks in setTimeout rolls the postMessage origin property back to what it is supposed to be.