A port is a mailbox that starts shut. Posts wait. onmessage opens it. addEventListener does not, until start(). close() cuts the pair.
port.postMessage(data) has no targetOrigin. The payload is structured-cloned onto the other port’s
port message queue. That queue is born disabled: tasks sit there so they are not dropped before you attach a handler.
The first time you set onmessage, the spec enables the queue as if you had called start().
addEventListener("message", …) does not.
(HTML §9.4.1.1,
MDN start())
port.addEventListener("message", onMsg);
port.start(); // required
port.onmessage = onMsg; // start() implied
Once enabled, a queue is never disabled again.
close() is not a pause: it disentangles the pair. Later posts find no twin and return without delivering.
Call close() yourself so the ports can be collected; do not wait for GC.
(HTML §9.4.4,
§9.4.5,
MDN close())
This lab uses a real MessageChannel. The listener is wired with addEventListener only — no implied start.
Post, then start, then post live, then close and post again. Same-page use is still a teaching trick.
The lab needs JavaScript in this page.
Read the contacts-provider snippet and the paragraph under it:
HTML Living Standard §9.4.1.1 Examples.
That is the addEventListener vs onmessage / start() rule in one place.
Then skim start() and close() in
§9.4.4 Message ports.
If the silent-listener gotcha is still mushy — especially “I used addEventListener and nothing arrived” — ask.
Next: moving a port (transfer list, event.ports, detach, DataCloneError).
Pocket cards:
entangled ports,
port API.