Two approaches:
parent, top, iframe.contentWindow).window.postMessage + message event handler.Short examples:
// Parent → iframe (cross-origin)
iframeEl.contentWindow.postMessage({ type: 'PING' }, 'https://child.example');// iframe → parent
window.addEventListener('message', (e) => {
if (e.origin !== 'https://parent.example') return;
if (e.data?.type === 'PING') e.source?.postMessage({ type: 'PONG' }, e.origin);
});// Same-origin: from iframe
parent.doSomething?.();— Always validate event.origin and use exact targetOrigin (not *).
Short:
// storage event (same-origin)
window.addEventListener('storage', (e) => console.log(e.key, e.newValue));
localStorage.setItem('msg', 'hi');// BroadcastChannel (same-origin)
const ch = new BroadcastChannel('app');
ch.onmessage = (e) => console.log(e.data);
ch.postMessage({ type: 'PING' });Same origin:
parent/top.iframeEl.contentWindow.Cross origin:
postMessage and listen to message.targetOrigin and validate event.origin.// Parent → iframe
iframeEl.addEventListener('load', () => {
iframeEl.contentWindow.postMessage({ type: 'INIT' }, 'https://child.example');
});// iframe: receive
window.addEventListener('message', (e) => {
if (e.origin !== 'https://parent.example') return;
if (e.data?.type === 'INIT') e.source?.postMessage({ ok: true }, e.origin);
});event.origin, data type, and message schema.targetOrigin: "*", prefer exact origin.type, payload, requestId.<!-- iframe restrictions -->
<iframe src="https://child.example"
sandbox="allow-scripts allow-same-origin"
referrerpolicy="no-referrer"></iframe>Bidirectional channel with ports:
const { port1, port2 } = new MessageChannel();
iframeEl.contentWindow.postMessage({ type: 'CHANNEL' }, 'https://child.example', [port2]);
port1.onmessage = (e) => { /* handle */ };load.origin validation or using *.postMessage).postMessage with strict origin checks and a clear protocol.window.parent/window.frames:// Direct calls
parent.doSomething?.(); // from iframe
iframeEl.contentWindow.doTask?.(); // from parentlocalStorage + storage:// Send
localStorage.setItem('command', 'REFRESH');
// Receive in another window/iframe of same origin
window.addEventListener('storage', (e) => { if (e.key === 'command') {/* ... */} });BroadcastChannel:const channel = new BroadcastChannel('ui-sync');
channel.onmessage = (e) => {/* ... */};
channel.postMessage({ type: 'READY' });BroadcastChannel or localStorage+storage.postMessage.event.origin, type/payload.targetOrigin (not *).sandbox.