Problems with window.postMessage on Chrome

user1437328 picture user1437328 · Sep 28, 2012 · Viewed 28.1k times · Source

I have been stuck on this for hours.

I have a.html on http://example.com that contains an iframe with src to b.html on http://subdomain.example.com. a.html has some JS code to postMessage to the iframe.

The code to postMessage is simple:

iframe_window.postMessage('message', iframe_element.src)

But this way, Chrome throws an error:

Unable to post message to http://subdomain.example.com. Recipient has origin null.

I have also tried:

iframe_window.postMessage('message', 'http://subdomain.example.com')

But NO LUCK!

This is the ONLY WAY it works:

iframe_window.postMessage('message', '*')

But I have heard '*' is not good to use.

No problems in Firefox.

Answer

Luke Channings picture Luke Channings · Dec 15, 2012

It looks like this might be an issue with the child iframe not being loaded at the time the signal is sent, thus iframe.src doesn't have the proper value.

I did some testing and got the same error as you, but when I wrapped the postMessage call in a setTimeout and waited 100ms then there was no error, which tells me that this is an initialisation race condition.

Here's how I implemented a cleaner solution without the setTimeout:

Parent:

window.addEventListener("DOMContentLoaded", function() {

    var iframe = document.querySelector("iframe")
      , _window = iframe.contentWindow

    window.addEventListener("message", function(e) {

        // wait for child to signal that it's loaded.
        if ( e.data === "loaded" && e.origin === iframe.src.split("/").splice(0, 3).join("/")) {

            // send the child a message.
            _window.postMessage("Test", iframe.src)
        }
    })

}, false)

Child:

window.addEventListener("DOMContentLoaded", function() {

    // signal the parent that we're loaded.
    window.parent.postMessage("loaded", "*")

    // listen for messages from the parent.
    window.addEventListener("message", function(e) {

        var message = document.createElement("h1")

        message.innerHTML = e.data

        document.body.appendChild(message)

    }, false)

}, false)

This is a simple solution in which the child will signal to anyone that it's loaded (using "*", which is okay, because nothing sensitive is being sent.) The parent listens for a loaded event and checks that it's the child that it's interested in that's emitting it.

The parent then sends a message to the child, which is ready to receive it. When the child gets the message it puts the data in an <h1> and appends that to the <body>.

I tested this in Chrome with actual subdomains and this solution worked for me.