Mint the pair. Move both ends. The two iframes talk. Parent’s handles are spent.
Lesson 3 kept port1 and moved port2. That is the usual parent↔iframe stream.
The other shape is the reason the spec calls a port a capability: give each iframe one end, and they talk
without parent on the pipe. Parent is only the mint. After both transfers, posting on the original
handles finds no twin — the message is dropped, not thrown.
(HTML §9.4.1.2)
const { port1, port2 } = new MessageChannel();
iframeA.contentWindow.postMessage("init", origin, [port1]);
iframeB.contentWindow.postMessage("init", origin, [port2]);
// parent is done. A and B hold the twins.
Relaying every message through parent.postMessage would put parent in the middle: it would have to
trust or inspect each send. Handing a port is handing the capability instead.
Each window hop still takes targetOrigin. This lab uses "*" because both iframes are
srcdoc in a local file. Production names each iframe origin.
The received ports are live: they can close(), and they can be transferred onward. The original
handles cannot.
Two real iframe documents. Transfer both ends. Send from A — B echoes on the pipe. Then post on the original handles and watch them miss.
The lab needs JavaScript in this page.
Read
HTML Living Standard §9.4.1.2
(contacts iframe and game iframe: hand a port, do not proxy through parent).
The drop-when-no-twin rule is
§9.4.4
(targetPort is null → return).
If “why isn’t this just parent.postMessage to each iframe?” is still mushy — ask.
Next: lesson 6 — a worker talks to an iframe on a minted pair.
Pocket cards:
entangled ports,
port API,
transfer,
choose.