Lesson 0005 · ~12 minutes

Transfer one port

One skill: create a MessageChannel. Keep port1. Transfer port2. Then talk on the MessagePort.

Win for this lesson

You can say this: “I wait for ready. I transfer port2. I keep port1. After that, this conversation is on the port. The port has no targetOrigin.”

1. The problem

Any holder of your WindowProxy can send to the window.

You filter with event.origin.

You want one private conversation with one iframe.

A MessageChannel is two ports that are paired. (MDN)

2. How you open the port

const channel = new MessageChannel();
channel.port1.onmessage = (event) => {
  // event.data
};
otherWindow.postMessage({ type: "port" }, "https://widget.example", [
  channel.port2,
]);

You keep port1.

You transfer port2. You already know transfer from the Structured Clone topic.

After the transfer, this page cannot use port2. (MDN)

Wait for ready first.

The window message that carries the port still needs a real targetOrigin. Check event.origin and event.source on that message.

3. How the child holds the port

window.addEventListener("message", (event) => {
  if (event.origin !== "https://app.example") return;
  const port = event.ports[0];
  if (!port) return;
  port.onmessage = (e) => {
    // e.data — talk on the port now
  };
  port.postMessage({ type: "port-ready" });
});

The port is event.ports[0]. (MDN)

If you use addEventListener on the port, call port.start(). If you set onmessage, the browser starts the port. (MDN)

4. No targetOrigin on the port

port.postMessage(data) has no targetOrigin.

The port is the check. Who holds the port can send.

On a port message, event.origin is the empty string. (HTML Standard)

Do not treat that field as the child origin.

This is why the first window send still names a real targetOrigin. That send is how the child gets the port.

React, same rule
const iframeRef = useRef(null);
const portRef = useRef(null);

function givePort() {
  const channel = new MessageChannel();
  channel.port1.onmessage = (event) => {
    // event.data
  };
  iframeRef.current.contentWindow.postMessage(
    { type: "port" },
    "https://widget.example",
    [channel.port2],
  );
  portRef.current = channel.port1;
}

Call givePort after ready. Then send on portRef.current.

parent                 child
  |                      |
  |   ready              |
  | <--------------------|
  |                      |
  |  postMessage(...,    |
  |    [port2])          |
  | -------------------->|  event.ports[0]
  |                      |
  |  port1.postMessage   |
  | ====================>|  port.onmessage
  |                      |

5. Do these steps

The child below sends ready when it loads its listener.

  1. Wait until the ready chip is true.
  2. Click Transfer port2. The child port line must say held. The parent log must show a message from the port.
  3. Click Send on port. The child port: line must change.
  4. Click Send on window. The child window: line must change. The port line must not change for that send.
  5. Click Use port2 here. This page must throw. port2 is gone.

6. Practice

Do the five steps first. Then answer these questions.

Question A

You need a private MessageChannel with one iframe.

Question B

You already transferred port2. You call port2.postMessage here.

Question C

The window message carries a port. What do you keep?

Question D

A message arrives on the port.

Question E

You use addEventListener on the port.

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)

MDN — Using channel messaging. That sample waits for load. This track waits for ready, then transfers. Then HTML §9.4.1.2.