Lesson 0004 · ~10 minutes

Wait for ready

One skill: do not send data to a new iframe at once. Wait until the child sends ready.

Win for this lesson

You can say this: “I hold contentWindow at once. That is not enough. I wait for ready. I check event.origin and event.source. A reload clears ready.”

1. The problem

You have an iframe element.

You already hold contentWindow.

You send data at once.

The child has no listener yet.

The data does not arrive. There is no error.

postMessage queues a task. If no listener is there when the task runs, the event is gone. (HTML Standard)

The browser does not keep the data for a later listener.

2. load is not ready

The iframe element fires load when the document has loaded.

That is not the same as a listener.

The child can add the listener after load.

The first document can be about:blank.

If you send with a real targetOrigin while the window is still about:blank, the browser discards the data. There is no error. That is the same rule as lesson 1.

3. The ready message

The child adds its listener first.

Then the child sends ready to parent.

The parent checks the message. Then the parent sends data.

// child
window.addEventListener("message", onMessage);
parent.postMessage({ type: "ready" }, "https://app.example");

// parent
window.addEventListener("message", (event) => {
  if (event.origin !== "https://widget.example") return;
  if (event.source !== iframe.contentWindow) return;
  if (event.data?.type !== "ready") return;
  iframe.contentWindow.postMessage(
    { type: "init", q },
    event.origin,
  );
});

You reuse lesson 3: event.origin is the string. event.source is the handle.

React, same rule
const iframeRef = useRef(null);
const [ready, setReady] = useState(false);

useEffect(() => {
  function onMessage(event) {
    if (event.origin !== "https://widget.example") return;
    if (event.source !== iframeRef.current?.contentWindow) return;
    if (event.data?.type === "ready") setReady(true);
  }
  window.addEventListener("message", onMessage);
  return () => window.removeEventListener("message", onMessage);
}, []);

function onFrameLoad() {
  setReady(false);
}

useEffect(() => {
  if (!ready) return;
  iframeRef.current.contentWindow.postMessage(
    { type: "init" },
    "https://widget.example",
  );
}, [ready]);

load sets ready to false. The new document must send ready again.

4. A reload clears ready

The WindowProxy can stay the same.

The document is new. The listener is gone.

The old ready flag is for the old document.

Set ready to false on load. Wait again.

parent holds contentWindow
    |
    |  send now  →  no listener  →  data is gone
    |
    |  child: add listener, send ready
    v
parent checks origin + source
    |
    |  send init
    v
child receives
    |
    |  iframe load (new document)
    v
ready is false again

5. Do these steps

The child below starts with no listener.

  1. Look at the log. load may fire. The ready flag must be false.
  2. Click Send data now. The child must still say “No message yet.”
  3. In the child, click Add listener and send ready. The ready flag must be true.
  4. Click Send data now. The child must show the data.
  5. Click Reload child. The ready flag must be false. Send again. The new child must not show that send.

6. Practice

Do the five steps first. Then answer these questions.

Question A

You set iframe.src. You send data at once.

Question B

The child sent ready. You want to send init data.

Question C

The iframe element fires load.

Question D

The child sent ready. Then the iframe reloads.

Question E

You have only the iframe load event.

7. Remember

Ask your teacher If a sentence is not clear, ask. If a lab step does not match the text, ask. That is part of the method.

Primary source (read next)

HTML — window post message steps. The browser queues a task. Then it fires message. There is no queue for a listener that does not exist yet.