Backwards compatible window.postMessage()

You can find the latest version of this library on Github.

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

// everything is wrapped in the XD function to reduce namespace collisions
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.

// pass the URL of the current parent page to the iframe using location.hash
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:

var parent_url = decodeURIComponent(document.location.hash.replace(/^#/, ''));

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)

    • to fix that error, replace

      toString

      with

      Object.prototype.toString

  • Jeff

    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

    • 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 tried in local but seem it not working? and don't has "www" in my domain

  • 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

  • dogpants

    @rachit

    change toString to Object.prototype.toString to fix that problem.

  • 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.

  • okinsey

    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!

    • beautyaboveus

      I am confused. The stackoverflow guys sent me here saying that this solution is good. Here when I read the comments I found this technique is not stable. So what should I use? EasyXDM or this one??

  • Piotr

    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.

    • leducr

      Josh you only fixed the parent postmessage. On ie8 parent to child is broken

  • Sergey

    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"&gt;
    </iframe>
    </body>
    </html>

  • nice site thanks for the link, nice monitor 🙂 great design, well not bad service will take a look closer, well not bad site, not so many programs listed but looks quite great.

  • Thompson

    Any chance you'd show an example resizing an iframe based on it's content? This looks like the best way to do that (after LOTS of searching), but this is way over my head as far as adapting it to do that.

  • Thompson

    I would really love to see an iframe resizing example (displaying content on a different domain). Maybe it'd make for a good new post??! I've tried all the others 'solutions' that I could find over the last couple weeks without any successful result.

  • Thompson

    Cool. Thanks for the link! I'll take a look.

  • idemdito

    I'm wondering: how can i be 100% certain about the authenticity of the client?
    For example an attacking script can be run on a apache VirtualHost (name it example.com). The messages sent by the attacking script has the origin example.com so checking the origin of the message is not waterproof

    how can i secure that gap?

  • avi rubinstein

    great and simple , thanks !

  • Trung

    I have a problem like this:

    – From a site A for exmaple: http://site.com/caller.php . On the caller.php page I have a button. When you click on the button, It will call: "window.open (http://abc.com/rec.php)". I do some thing on the new windows and press close button on rec.php. And I want to send a message like this "DONE" back to caller.php. How can i do this?

    • Trung

      I have done!. the script run can be run on Firefox 3.x and Chrome. However, there is an error with IE 8. It appears an error in "postmessage.js" file:

      No such interface supported
      postmessage.js
      Line: 41
      Char: 17
      Code: 0

      Can any one help me!

      Many thanks

  • Trung

    Hi Josh.

    You did an amazing work. Base on your article I have solved my problem that I asked above. But I tested again your barebones example (http://onlineaspect.com/uploads/postmessage/parent.html) on IE8, it always appear an like me!

    Please help!
    Trung

  • Andrey

    Hi Josh,

    I'm getting "Invalid argument" on this line:

    target['postMessage'](message, target_url.replace( /([^:]+://[^/]+).*/, '$1'));

    when I am trying to send message from an iframe to parent window (IE8).

    • I've had a few people complain about IE8 issues. I'll make a note to dig into it when i get a free moment. If you beat me to the solution, let me know. I want to get this figured out.

      • Kar

        Is there any solution for this issue?

  • Roelof

    Hi,

    I was stuck with IE8 issues on your script. It turned out that IE empties window.location.hash somewhere before it's read. I solved it with a server-side solution.
    Thanks for the script. Nice.

  • Steven

    Your script does not seem to work when running IE8 or 7. Hoped your script would give a solution as I was having trouble with this earlyer myself..

    In my own case It seems like when trying to access the parentwindow by doing parent.window.postMessage > IE8 will throw an access denied error which I can catch and act upon appropriately. However when trying to send a message to the child window inside an iframe by using document.getElementById("myIframe").contentWindow.postMessage > this will throw no error, though nothing will hapen neither so I am left with an unhandled scenario. Didn't go indepth with your script but a quick test of your demo shows that showing the message from within the iframe works as with my own scenario and the message from the main page to the iframe does not work.

    • I've had multiple reports of issues w/ those 2 browsers (see the comments above). Hopefully I'll find the time to dig into this more soon.

      • Steven

        Hope so too. I have a workaround now (which has nothing to do with the script, but makes me not have to use it in these cases). thx anyway for this great share and for the quick reply!

  • Looks like Twitter and Disqus both use the easyxdm library – http://easyxdm.net/

    (I was needing to do this, and google sent me to your site first! Small world…)

  • you saved my day!! great script! thx a lot!

  • I'm blown away, this is amazing! Thank you for providing the script and so much info. I just tested the barebone example link in IE6 v6.0.2900.5512 and IE7 v7.0.5730.13 on Windows XP and it works great!!! Thanks again!

  • Tuan Jinn

    Brilliant, for my case it's a bit more complicated, but this is exactly what I need, I used easyXDM, but this is easier and cleaner… !!! Thank a lots!!!!!!!!!!!

  • we just came across a hurdle. We have two a frame in which we are injecting a new frame through chromes content script. bBut when posting meessage I get error stating domain are different.

  • This seems to be exactly what I was looking for: a lightweight solution using postMessage with a fallback using hash.. I'm going to try it out right away. Thanks for this post!

  • zacharykane

    Seems like this a very popular mini script! I've used it for a while now and the results are great.

    One question, of course.

    You pass the child the parent's URL right off the bat. by setting the src attribute. The child catches this in a variable but doesn't do anything with it. What's the point?

    I ask because I was wondering if the child could somehow determine the domain/url of the parent dynamically, rather than hard coding it in the .receiveMessage(..) method.

    Would something like window.parent.location work?

  • rich4hacker

    nice post!

    hack facebook password online http://www.hackfacebookpd.com

  • rich4hacker

    Nice post!
    hack facebook password online http://www.hackfacebookpd.com

  • rich4hacker
  • JK.

    Hi Josh, why do you check for window['postMessage'] but then call target['postMessage'] instead? It looks like a bug, shouldnt you be checking for target['postMessage']?

    You've got this:

    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'));
    }

    Shouldn't it be this?

    if (target['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'));
    }

  • sep

    This library works fine in all browsers except Firefox version 31. The whole page starts jumping, I noticed that the issue
    is caused by the iframe src like:
    src = 'http://joshfraser.com/code/postmessage/child.html#&#039; + encodeURIComponent(document.location.href);

    what is your advice to fix this issue.I have used this library for different customers and they are complaining that they are not able to use Firefox.

    Thanks,

  • Krishna

    Hi,

    There is some issue in Firefox 34, when execute given statement
    target['postMessage'](message, target_url.replace( /([^:]+://[^/]+).*/, '$1'));

    Error comes "DataCloneError: The object could not be cloned."

  • Jamil

    The barebones example appears to be broken. I see a 404 inside the iFrame.

  • Awesome and simple work. Thank you sharing.

  • Tuấn Cầu Rào

    Thanks