Skip to content

window.postMessage

2013 January 16
by Richard Harrington

Some time ago at work, one of our partner companies needed us to use an iframe because our users needed to type their usernames and passwords for their accounts with that partner into our website, but our partner didn’t want us to have access to those usernames and passwords. So they asked for the whole thing to be conducted in an iframe, served by them but embedded in our site, and then their iframe would pass a token to us, which we could use to query their site for the user’s information.

At first they tried to pass the token by having us implement a globally available function called “setToken”, and then once the user had typed in their username and password and the server had converted those into a token for us to use, they would call the “setToken” function in our page from inside their iframe, like so:

parent.setToken(token)

And our “setToken” function would have looked something like this:

window.setToken = function(token) {
    // some code
    someDataStructureInOurSiteSomewhere[someUserId] = token;
    // some more code
}

(Note that we could have said just “setToken” or “var setToken” in place of “window.setToken” and it would have had the same effect for most intents and purposes — it would be declaring a global variable — but I like to make it absolutely clear when I’m polluting the global namespace, by explicitly stating that I’m adding a property to the window object).

Many of you will be aware, as neither I nor my counterpart at the other company was, that the above strategy won’t work at all. When two frames are being served from different domains, there are serious security concerns that prevent one frame from simply going into the other frame and executing one of its functions, such as “parent.setToken.” Not only can you not execute functions, you can’t even send messages in the form of strings. These are the same security concerns, in fact, that led our partner to insist on this iframe arrangement in the first place.

So what do you do if you need to pass information back and forth between frames being served from different domains? I did a bunch of research and discovered a crazy array of hacks to get around this cross-domain problem, most of which compromise security to one degree or another. They are well catalogued in this blog post.

But part of the HTML5 specification is a message-passing system intended specifically to address this very issue, centered around the window.postMessage method and its associated events. This method is supported by Firefox, Safari and Chrome going back pretty far, and IE back as far as IE8, which is all I have to support on the two projects I have going at work (and certainly in my own side projects).

Using window.postMessage, one frame can send a message to another frame as long as it can correctly identify the origin (the protocol plus the ‘//’ plus the domain, i.e. ‘http://www.somedomain.com’) of the target frame. The target frame then has to have a listener set up for ‘message’ events. The message event contains the message itself (which can be an object in decent browsers but must be a string if you want it to work in IE9 and below), and it also contains the origin of the sender, as well as a reference to the sender’s window object. The value of both of these last two things can be verified by the recipient, for added security.

The best description of how this works can be found here, and the full rundown on browser support can be found here (make sure to click on “Show all versions”). Last but not least, I have made a simple demo myself, which can be found here.

When the user clicks on the button to run my demo, the parent frame sends a message to the child frame, which prints a message and sends another one back to the parent frame. Here are the essential bits from the code for the demo:

1) The code in the parent page, html then JavaScript:

<button id="runDemo">Run the demo</button>
<iframe id="inlineFrame" src="http://childdomain.com/index.html"></iframe>
<p>
    After the child received the message, 
    it sent the following response back to the parent: 
    <span id="responseFromChild"></span>
</p>
var childOrigin = 'http://childdomain.com';
var childWindow = document.getElementById('inlineFrame').contentWindow;
 
// Listen for the response from the child
window.addEventListener('message', function(event) {
 
    // This 'if' block is optional, but good for security.
    if (event.origin !== childOrigin || event.source !== childWindow) {
        throw new Error('The domain of the child does not match 
                what the parent thought it was going to be.');
    }
 
    // Show that we've received the message
    document.getElementById('responseFromChild').innerHTML = event.data;
 
}, false);
 
// Activate the button
var demoButton = document.getElementById('runDemo');            
demoButton.addEventListener('click', function() {
 
    // Post a message to the child window
    childWindow.postMessage('Child, heed my call.', childOrigin);
 
}, false);

2) The code in the child page, html then JavaScript:

<p>
    Here is the message the child received from the parent: 
    <span id="messageFromParent"></span>
</p>
var parentOrigin = 'http://parentdomain.com';
var parentWindow = window.parent;
 
// Listen for the message from the parent
window.addEventListener('message', function(event) {
 
    // This 'if' block is optional, but good for security
    if (event.origin !== parentOrigin || event.source !== parentWindow) {
        throw new Error('The domain of the parent does not match 
                what the child thought it was going to be.');
    }
 
    // Show that we've received the message
    document.getElementById('messageFromParent').innerHTML = event.data;
 
    // Post a message back to the parent window
    parentWindow.postMessage('Right back atcha.', parentOrigin);
 
}, false);

And a Happy New Year to all!

Leave a Reply

Note: You may use basic HTML in your comments. Your email address will not be published.

Subscribe to this comment feed via RSS