Simple cross-domain messaging
This blog post explains how to implement a backwards compatible version of window.postMessage() to handle all your cross-domain messaging needs. If you’re in a hurry, you can skip directly to the demo or just grab the following files:
Background
One of the trickiest things you will ever run into on the web is the same origin policy. The same origin policy basically limits how scripts and frames on different domains can talk to each other. The same origin policy is an important part of your security on the web. For example, it prevents someone from being able to steal your password from another frame on the page. The annoying thing is there are sometimes perfectly valid reasons for frames on different domains to need to talk to one another. One good example of this would be the Facebook Connect library where facebook.com needs to be able to communicate with non-facebook.com domains. Over the years we’ve developed a series of hacks to work around this browser limitation. Some developers have used flash while others have relied on a window.location.hash hack. Facebook worked around it by getting people to install a cross domain communication channel. It got pretty ridiculous until the browser makers finally decided to give us a way to do cross-domain messaging without all the nonsense. The result was window.postMessage() which is supported by the latest browsers like Firefox 3, Safari 4, Chrome and IE 8. Unfortunately, as usual we’re going to need a backwards compatible version before we can take advantage of this new functionality.
I found a couple great examples of people who have tackled this already. Luke Shepard wrote xd.js which is part of the open-sourced Facebook Connect code. I also found Ben Alman’s jQuery plugin which does a really nice job. Both of these scripts are great, but neither fits quite right with my needs. For one, I wanted the smallest possible script written in pure JavaScript. I’m a fan of jQuery, but since I’ll be installing this code on other people’s domains I can’t assume that jQuery will be available and while I could load it up it’s important to keep the file size small. So what I did was start with Ben’s code and took out all of the jQuery dependencies. Here is the result:
The code
var XD = function(){
var interval_id,
last_hash,
cache_bust = 1,
attached_callback,
window = this;
return {
postMessage : function(message, target_url, target) {
if (!target_url) {
return;
}
target = target || parent; // default to parent
if (window['postMessage']) {
// the browser supports window.postMessage, so call it with a targetOrigin
// set appropriately, based on the target_url parameter.
target['postMessage'](message, target_url.replace( /([^:]+:\/\/[^\/]+).*/, '$1'));
} else if (target_url) {
// the browser does not support window.postMessage, so use the window.location.hash fragment hack
target.location = target_url.replace(/#.*$/, '') + '#' + (+new Date) + (cache_bust++) + '&' + message;
}
},
receiveMessage : function(callback, source_origin) {
// browser supports window.postMessage
if (window['postMessage']) {
// bind the callback to the actual event associated with window.postMessage
if (callback) {
attached_callback = function(e) {
if ((typeof source_origin === 'string' && e.origin !== source_origin)
|| (Object.prototype.toString.call(source_origin) === "[object Function]" && source_origin(e.origin) === !1)) {
return !1;
}
callback(e);
};
}
if (window['addEventListener']) {
window[callback ? 'addEventListener' : 'removeEventListener']('message', attached_callback, !1);
} else {
window[callback ? 'attachEvent' : 'detachEvent']('onmessage', attached_callback);
}
} else {
// a polling loop is started & callback is called whenever the location.hash changes
interval_id && clearInterval(interval_id);
interval_id = null;
if (callback) {
interval_id = setInterval(function() {
var hash = document.location.hash,
re = /^#?\d+&/;
if (hash !== last_hash && re.test(hash)) {
last_hash = hash;
callback({data: hash.replace(re, '')});
}
}, 100);
}
}
}
};
}();
Usage:
There are two parts to using this code: posting and listening. Both are relatively simple. To post a message we call XD.postMessage with a message, a URL and the frame that we want to talk to. Notice that we start off by passing the URL of the parent page to the child frame. This is important so the child knows how to talk back to the parent.
src = 'http://joshfraser.com/code/postmessage/child.html#' + encodeURIComponent(document.location.href);
document.getElementById("xd_frame").src = src;
function send(msg) {
XD.postMessage(msg, src, frames[0]);
return false;
}
Setting up the listener on the child is also easy to do:
XD.receiveMessage(function(message){
window.alert(message.data + " received on "+window.location.host);
}, 'http://onlineaspect.com');
I recommend taking a look at this barebones example to understand better how the various pieces fit together. This is still a work in progress and I’d love any feedback you have on it. I’m particularly interested in adding Flash as an alternative method before falling back to fragments. This is what the Facebook code does and I like it because it eliminates the nasty polling every 100ms.
Got other thoughts on how to make this better? Let me know in the comments.


Nice and simple — great!
Also — Shindig, the open source implementation of Google's OpenSocial platform, has a inter-window rpc library that uses several transports for cross-domain messaging (postMessage, location hash, etc) – http://bit.ly/7XpgQv. However, it's rather an overkill for small-scale projects
. I think Flash support is in their to-be-implemented list.
I had a similar problem recently. I am building an API library for our user management/security system that our applications will work with. Well one of the things we wanted to implement was a single sign on system that required very little code change from our existing applications.
Here is my solution with some code samples: http://bottomupdesign.net/?p=103
This is an awesome idea and is fast too.
In non-postMessage scenario, I wish we could remove the setInvertal code (last part in receiveMessage function with 100 msec polling) once the response is being read by the parent server. So, if message goes from server B to server A by calling postMessage on server B, wish there's way to remove that checking.
Is there any way we can do that?
sure, this should be easy to add. just create a function that calls clearInterval(interval_id) to stop the loop once you're done.
I was just thinking over the non "postMessage" scenario. Can't we just use target.name to set the values instead of changing URL hash? That way the ugliness of URL will not be a problem. No?
I don't think that works cross-domain, but try it and let me know.
Yeah, it worked fine.
In postMessage, I changed the following:
target.name = message;
//target.location = target_url.replace(/#.*$/, '')…
and in the receiveMessage, I changed the following:
interval_id = setInterval(function() {
if (window.name !== last_hash) {
last_hash = window.name;
callback({ data: last_hash });
}
else {
clearInterval(interval_id);
}
Let me know if I've missed something to consider.
Josh,
FYI…In IE8, I get " Object doesn't support this property or method" on the following line:
toString.call(source_origin) === "[object Function]" && source_origin(e.origin) === !1)
Hi Josh,
Interesting solution! I tried recreating your demo on my server can't seem to get it to work. I'm not getting any errors and it appears that the XD.receiveMessage() is not getting called on either the parent or child window, when I add debugging statements to that method.
Here is my test url: http://jeffsittler.com/postmessage/parent.html
Any chance you could tell me what I'm doing wrong or missing?
Thanks!
Jeff
Hey Josh…
I sent a comment yesterday asking for help with the script but I got it working. I was accessing my sites without the www. in the url and had that in the code but it wasn't working. I added www. to the urls and it started working. Thanks for the great script!
Jeff
to fix that error, replace
toString
with
Object.prototype.toString
@rachit
change toString to Object.prototype.toString to fix that problem.
Your problem is that you aren't being consistent with your use of the "www" subdomain. In this case,http://www.domain.com != domain.com
Hi Josh:
I'm trying to solve a cross-domain communication issue for two weeks.
Recently I saw this page, I think this is the solution I'm looking for a long time.
But to verify it really works, I test the demo page(http://onlineaspect.com/uploads/postmessage/parent.html) on various of various browsers,
including firefox chrome safari opera and IE of course. In most conditions it works, but it get stuck in IE(version 8.0).
I check out the developer tool of IE, and it shows that the error occurs in the file "postmessage.js" at line 58.
The source code: "if ((typeof source_origin === 'string' && e.origin !== source_origin)"
The error message: "Object does not support this attribute or method".
I have no idea what's wrong, could you figure out how to fix it?
Thanks a lot!
As people have commented before, the solution is to change toString to Object.prototype.toString. I haven't had a chance to update the code yet, but that should do the trick.
easyXDM (http://easyxdm.net/) is also a good suggestion, it supports XDM using a number of techniques, and it also supports RPC! There's an article about how it works at http://msdn.microsoft.com/en-us/scriptjunkie/ff80... check it out!
Josh, you did great work! This is what I needed – simple solution without bloat. Please just update the code with Object.prototype.toString – this will help people avoid problems.
Thanks for the reminder on that. I've updated the post and the JS file.
There is one drawback, if you use three layer of Iframe, it opens popup windows in IE7
Try it.
<!doctype html>
<html>
<head>
</head>
<body>
<iframe src="http://onlineaspect.com/uploads/postmessage/parent.html">
</iframe>
</body>
</html>