Search code examples
javascriptrace-conditionpostmessage

Is Window.postMessage susceptible to race conditions?


MDN's examples for Window.postMessage include:

const popup = window.open(/* popup details */);

// When the popup has fully loaded, if not blocked by a popup blocker:

// This does nothing, assuming the window hasn't changed its location.
popup.postMessage(
  "The user is 'bob' and the password is 'secret'",
  "https://secure.example.net"
);

This is as I would have expected: that you need to wait for the target window to register its message event handler before calling postMessage. Unfortunately, I can't find a way to do that. (Maybe something with my_iframe.contentWindow.addEventListener, if that's even possible?)

I did, however, find a comment suggesting you don't need to do that:

I don't think the event listener needs to be "ready". The messages should be saved until the event listener is added. – Barmar Feb 12, 2021 at 20:11

Which is it? Do we need to wait for the event handler to be registered, or don't we?


Solution

  • This is trivial to test.

    const addMessageListener = () => {
        addEventListener("message", (event) => {
            console.log("Recieved: " + event.data);
        });
    }
    
    const sendMessage = (message) => {
        console.log("Sending: " + message);
        postMessage(message, '*');
    };
    
    const wait = () => new Promise((resolve) => setTimeout(resolve, 500));
    
    const actions = async () => {
        sendMessage("Sent before adding listener");
        await wait();
        sendMessage("Sent IMMEDIATELY before adding listener");
        addMessageListener();
        sendMessage("Sent after adding listener");
    }
    
    actions();

    Messages are not queued waiting for a listener to be added, but synchronous code might block a message (the Sent IMMEDIATELY one in this example) being sent until a listener is added which could give the illusion of such a queue. That isn't behaviour you should depend on.


    that you need to wait for the target window to register its message event handler before calling postMessage. Unfortunately, I can't find a way to do that.

    The approach I would take would be to add a message listener to the opener which, when it gets a suitable message, will start sending messages to the document in the new window. Then the document (whose URL you pass to window.open) can send window.opener.postMessage("I am ready to accept your messages", origin) once it has set up its own event listener.