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);
}, 'https://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.

Comments

  1. @izuzak said at 7:02 am on January 15th, 2010:

    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.


  2. JChauncey said at 2:04 pm on January 20th, 2010:

    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


  3. Rachit said at 8:37 pm on February 24th, 2010:

    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?


  4. Josh Fraser said at 8:43 pm on February 24th, 2010:

    sure, this should be easy to add. just create a function that calls clearInterval(interval_id) to stop the loop once you're done.


  5. Rachit said at 9:05 pm on February 24th, 2010:

    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?


  6. Josh Fraser said at 9:07 pm on February 24th, 2010:

    I don't think that works cross-domain, but try it and let me know. 🙂


  7. Rachit said at 10:10 pm on February 24th, 2010:

    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.


  8. Rachit said at 7:40 pm on March 1st, 2010:

    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)


  9. jebaird said at 6:00 pm on March 12th, 2010:

    to fix that error, replace

    toString

    with

    Object.prototype.toString


  10. Jeff said at 6:06 pm on March 3rd, 2010:

    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


  11. Josh Fraser said at 1:49 pm on April 29th, 2010:

    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


  12. nhatnd said at 2:46 am on October 5th, 2010:

    Hi Josh, i tried in local but seem it not working? and don't has "www" in my domain


  13. Josh Fraser said at 5:58 pm on October 27th, 2010:

    can you post your code?


  14. Jeff said at 12:28 am on March 5th, 2010:

    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


  15. dogpants said at 1:50 pm on March 19th, 2010:

    @rachit

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


  16. zouber said at 4:14 am on May 14th, 2010:

    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(https://joshfraz.wpcomstaging.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!


  17. Josh Fraser said at 4:09 pm on May 14th, 2010:

    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.


  18. okinsey said at 6:38 am on July 4th, 2010:

    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!


  19. beautyaboveus said at 3:58 am on June 29th, 2014:

    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??


  20. Piotr said at 12:09 pm on July 16th, 2010:

    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.


  21. Josh Fraser said at 7:52 pm on July 16th, 2010:

    Thanks for the reminder on that. I've updated the post and the JS file.


  22. leducr said at 9:36 am on February 14th, 2011:

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


  23. Sergey said at 2:46 am on July 28th, 2010:

    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="https://joshfraz.wpcomstaging.com/uploads/postmessage/parent.html"&gt;
    </iframe>
    </body>
    </html>


  24. Hyipsuccess said at 7:10 am on July 30th, 2010:

    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.


  25. Thompson said at 12:18 pm on September 29th, 2010:

    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.


  26. Thompson said at 7:20 am on October 14th, 2010:

    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.


  27. Josh Fraser said at 7:26 am on October 14th, 2010:

    I saw this one just popped up a couple days ago. It's based on my code: http://delfeld.wordpress.com/2010/10/12/resize-if


  28. Thompson said at 7:50 pm on October 14th, 2010:

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


  29. idemdito said at 1:58 pm on November 8th, 2010:

    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?


  30. avi rubinstein said at 8:30 am on November 17th, 2010:

    great and simple , thanks !


  31. Trung said at 2:42 am on March 17th, 2011:

    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?


  32. Trung said at 7:27 pm on March 17th, 2011:

    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


  33. Trung said at 7:52 pm on March 17th, 2011:

    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 (https://joshfraz.wpcomstaging.com/uploads/postmessage/parent.html) on IE8, it always appear an like me!

    Please help!
    Trung


  34. Andrey said at 11:54 pm on May 13th, 2011:

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


  35. Josh Fraser said at 11:16 am on May 14th, 2011:

    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.


  36. Kar said at 5:32 am on March 23rd, 2015:

    Is there any solution for this issue?


  37. Roelof said at 7:48 am on June 8th, 2011:

    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.


  38. Steven said at 3:28 am on November 30th, 2011:

    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.


  39. Josh Fraser said at 3:35 am on November 30th, 2011:

    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.


  40. Steven said at 7:02 am on November 30th, 2011:

    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!


  41. Daniel Von Fange said at 6:02 am on December 28th, 2011:

    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…)


  42. faeb187 said at 11:45 am on January 9th, 2012:

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


  43. Josh Fraser said at 11:55 am on January 9th, 2012:

    you're welcome


  44. Garrett said at 12:10 am on May 1st, 2012:

    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!


  45. Tuan Jinn said at 1:01 am on July 17th, 2012:

    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!!!!!!!!!!!


  46. nilay said at 7:01 am on August 23rd, 2012:

    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.


  47. @ka0re said at 2:06 am on February 28th, 2013:

    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!


  48. zacharykane said at 1:36 pm on March 4th, 2013:

    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?


  49. rich4hacker said at 1:32 am on March 16th, 2013:

    nice post!

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


  50. rich4hacker said at 8:34 am on March 19th, 2013:

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


  51. rich4hacker said at 1:40 am on March 20th, 2013:

    OH! Good http://www.hack-fb-online.com


  52. JK. said at 3:57 pm on August 10th, 2014:

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


  53. sep said at 11:01 pm on August 30th, 2014:

    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,


  54. Krishna said at 5:09 am on December 17th, 2014:

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


  55. Jamil said at 10:13 am on July 13th, 2016:

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


  56. jerryperes said at 12:32 am on June 17th, 2017:

    Awesome and simple work. Thank you sharing.


  57. Tuấn Cầu Rào said at 8:20 pm on July 19th, 2017:

    Thanks