Lesson 0004 · ~10 minutes
One skill: do not send data to a new iframe at once. Wait until
the child sends
ready.
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.”
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.
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.
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.
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.
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
The child below starts with no listener.
load may fire. The ready flag
must be false.
true.
false. Send again. The new child must not show
that send.
Do the five steps first. Then answer these questions.
You set iframe.src. You send data at once.
The child sent ready. You want to send init data.
The iframe element fires load.
The child sent ready. Then the iframe reloads.
You have only the iframe load event.
ready.
event.origin and
event.source on the ready message.
load is not ready.
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.