Lesson 0001 · ~12 minutes
One skill: when you hold a
WindowProxy,
you send data with
postMessage. You do not call a function on the other window.
You can say this: “I do not call a function on the iframe. I use
postMessage. I set
targetOrigin. I check event.origin.”
You have a page.
The page has an iframe.
The iframe shows a widget from a different site.
You want a value from the widget.
You cannot read the widget document.
You cannot call a function on the widget.
The
same-origin policy
blocks that access.
postMessage
is the path that the browser allows.
An origin is a scheme, a host, and a port.
Example:
https://app.example and
https://widget.example are different origins.
Two pages have the same origin only when the scheme, the host, and the port all match.
If two pages have the same origin, they can read each other.
If two pages have different origins, they cannot.
You get a handle to the other window in one of these ways:
iframe.contentWindowwindow.parentwindow.openerwindow.open(...)That handle is a WindowProxy.
A WindowProxy is not the other window.
If the origins are the same, you can use the WindowProxy as the
window. You can read document.title.
If the origins are different, the browser blocks most access. A
read of document throws SecurityError.
You can still send data with postMessage.
iframeRef.current.contentWindow.postMessage(
{ type: "set-filter", q },
"https://widget.example",
);
// Wrong: iframeRef.current.contentWindow.setFilter(q)
The ref holds a WindowProxy. You send data. You do not call
setFilter.
Write this:
otherWindow.postMessage(data, targetOrigin);
data is the value. The browser makes a
structured clone
of the value. You already know that copy from the Structured Clone
topic.
The copy arrives later. This is not a function call.
targetOrigin
is the origin that may receive the data.
If the other window does not have that origin, the browser discards the data. The browser does not throw an error. (HTML Standard)
targetOrigin, the default is
"/". That means “the same origin as you”.
"*" means any origin. Do not use
"*" with secrets.
your page
|
| postMessage(data, "https://widget.example")
v
iframe (receives only if its origin is https://widget.example)
Add a listener:
window.addEventListener("message", (event) => {
if (event.origin !== "https://widget.example") return;
// use event.data
});
The event has three fields that matter here:
data — the copy of the valueorigin
— the origin of the sender
source — the WindowProxy of the senderAny window that has your WindowProxy can send data to you (MDN).
You must check event.origin. If
event.origin is not the origin you expect, return.
To reply, write this:
event.source.postMessage(reply, event.origin);
iframe
|
| message event: data, origin, source
v
your page (you check event.origin, then you use event.data)
The box below is a real iframe. Do the steps in order. Look at the log after each step.
SecurityError. You still have a WindowProxy. You do
not have the document.
event.origin in the log.
This lesson file uses the file:// protocol. The origin
of this page is the text null. The origin of the
sandbox child is also the text null. A string check
cannot tell the two origins apart. In production you name a real
https:// origin
(HTML §9.3.2.1).
Do the five steps first. Then answer these questions.
You have iframe.contentWindow for a widget on
https://widget.example. You need one field from
that page.
You send a session token with
targetOrigin: "*". The popup goes to
https://evil.example before the data arrives.
A page adds window.addEventListener("message", …)
and does not read event.origin.
You send data with
targetOrigin: "https://widget.example". The iframe
is now on https://other.example.
You call otherWindow.postMessage(payload) with no
second argument.
targetOrigin. A wrong origin discards the data.
The default is "/", not "*".
event.origin. If you skip that check, any
holder of your WindowProxy can send data to you.
MDN — Window.postMessage(). Read “Security concerns” and “The dispatched event”. Then read HTML §9.3.2.1.