Skip to content

HTML5 - Web Messaging

Web messaging defines mechanisms that allow scripts in different browsing contexts (like separate windows, tabs, iframes, or workers) to communicate with each other, even if they originate from different origins (domains, protocols, or ports), overcoming the limitations of the Same-Origin Policy in a controlled way.

This enables secure cross-document and cross-origin communication, essential for scenarios like:

  • A page communicating with an embedded <iframe> from a different domain (e.g., interacting with an embedded map, video player, or advertisement).
  • A page opening a new window (window.open()) and exchanging data with it.
  • A page communicating with its Web Workers or Service Workers.
  • Different <iframe> elements on the same page communicating with each other via the parent page.

The most common form of web messaging uses the window.postMessage() method. It allows a window (or frame, or worker) to safely send data messages to another window (or frame, or worker).

To send a message, you need a reference to the target window object (otherWindow) and call its postMessage method:

otherWindow.postMessage(message, targetOrigin, [transfer]);
  • message: The data to send. This can be any object that can be structured cloned (strings, numbers, booleans, arrays, objects, Blobs, ArrayBuffers, etc.).
  • targetOrigin: A crucial security parameter. It specifies the origin (e.g., 'https://example.com', '*') that the target window must have for the message to be delivered. Use '*' only if you don’t need to restrict the recipient origin (less secure). Always specify a specific origin when sending sensitive data.
  • transfer (Optional): An array of Transferable objects (like ArrayBuffer, MessagePort, ImageBitmap) whose ownership is transferred to the receiving context, potentially improving performance by avoiding copying large data.

To receive messages, the target window needs to listen for the message event:

window.addEventListener('message', receiveMessage, false);
function receiveMessage(event) {
// 1. SECURITY CHECK: Verify the origin of the sender
if (event.origin !== 'https://expected-sender.com') {
console.warn('Message received from unexpected origin:', event.origin);
return; // Ignore messages from untrusted origins
}
// 2. Access the data
const receivedData = event.data;
console.log('Message received:', receivedData);
// 3. Optional: Access the source window (for sending replies)
const sourceWindow = event.source;
if (sourceWindow) {
// Example: Send a reply back
sourceWindow.postMessage('Message received successfully!', event.origin);
}
// 4. Optional: Access transferred MessagePorts (see Channel Messaging)
if (event.ports && event.ports.length > 0) {
// Handle MessagePorts
}
// Process the receivedData...
}

The event object in the handler provides:

  • data: The actual message object sent by postMessage.
  • origin: The origin of the window that sent the message (e.g., 'https://sender.com'). Crucial for security checks.
  • source: A reference to the window object that sent the message. Useful for sending replies.
  • ports: An array of MessagePort objects transferred with the message (used for Channel Messaging).

Example: Page Communicating with an Iframe

Section titled “Example: Page Communicating with an Iframe”

parent.html (Main Page):

<!DOCTYPE html>
<html>
<head><title>Parent Page</title></head>
<body>
<h1>Parent Page</h1>
<iframe id="childFrame" src="https://other-domain.com/child.html" width="400" height="200"></iframe>
<button onclick="sendMessageToChild()">Send Message to Iframe</button>
<div id="response"></div>
<script>
const childFrame = document.getElementById('childFrame');
const responseDiv = document.getElementById('response');
const childOrigin = 'https://other-domain.com'; // IMPORTANT: Set the correct origin
function sendMessageToChild() {
const message = { text: 'Hello from parent!', timestamp: Date.now() };
// Get the iframe's contentWindow
if (childFrame.contentWindow) {
console.log(`Sending message to ${childOrigin}:`, message);
childFrame.contentWindow.postMessage(message, childOrigin);
}
}
// Listen for messages from the child iframe
window.addEventListener('message', (event) => {
// SECURITY CHECK!
if (event.origin !== childOrigin) {
console.warn('Ignoring message from unexpected origin:', event.origin);
return;
}
console.log('Message received from iframe:', event.data);
responseDiv.textContent = `Response from iframe: ${JSON.stringify(event.data)}`;
});
</script>
</body>
</html>

child.html (Iframe Content - hosted on https://other-domain.com):

<!DOCTYPE html>
<html>
<head><title>Child Iframe</title></head>
<body style="background-color: lightblue;">
<h2>Iframe Content</h2>
<div id="received">Waiting for message...</div>
<script>
const receivedDiv = document.getElementById('received');
const parentOrigin = 'https://parent-domain.com'; // IMPORTANT: Set the correct origin of the parent
window.addEventListener('message', (event) => {
// SECURITY CHECK!
if (event.origin !== parentOrigin) {
console.warn('Ignoring message from unexpected origin:', event.origin);
return;
}
console.log('Message received from parent:', event.data);
receivedDiv.textContent = `Received: ${JSON.stringify(event.data)}`;
// Send a reply back to the parent window (event.source)
if (event.source) {
const reply = { confirmation: 'Message processed by iframe!' };
console.log(`Sending reply to ${event.origin}:`, reply);
event.source.postMessage(reply, event.origin);
}
});
</script>
</body>
</html>

Channel Messaging provides a more direct, two-way communication channel between two browsing contexts. It uses the MessageChannel API.

Creating a MessageChannel instance gives you two connected MessagePort objects (port1 and port2). One port remains in the originating context, while the other port is transferred to the target context using the optional third argument of postMessage.

Once the port is transferred, both contexts can communicate directly through their respective ports using port.postMessage() and listening with port.onmessage (or port.addEventListener('message', ...)). This avoids the need for window references and origin checks for subsequent messages on that specific channel.

Conceptual Example: Page to Iframe using MessageChannel

Section titled “Conceptual Example: Page to Iframe using MessageChannel”

parent.html:

// ... (get iframe reference as before)
function setupChannel() {
const channel = new MessageChannel();
const port1 = channel.port1;
const port2 = channel.port2;
// Send port2 to the iframe, transferring its ownership
if (childFrame.contentWindow) {
console.log(`Sending port2 to ${childOrigin}`);
childFrame.contentWindow.postMessage('init-channel', childOrigin, [port2]);
}
// Listen for messages on port1 (from the iframe)
port1.onmessage = (event) => {
console.log('Message received via channel port1:', event.data);
responseDiv.textContent = `Channel response: ${JSON.stringify(event.data)}`;
// Send message back via channel
port1.postMessage({ reply: 'Got your channel message!' });
};
// Start the port (needed for onmessage to fire)
port1.start();
}
// Call setupChannel() when iframe is loaded, e.g.:
// childFrame.addEventListener('load', setupChannel);

child.html:

// ... (setup parentOrigin)
let channelPort = null;
window.addEventListener('message', (event) => {
// SECURITY CHECK on origin!
if (event.origin !== parentOrigin) return;
// Check if this is the channel initiation message
if (event.data === 'init-channel' && event.ports && event.ports.length > 0) {
channelPort = event.ports[0];
console.log('Received channel port2');
// Listen for messages on the received port
channelPort.onmessage = (portEvent) => {
console.log('Message received via channel port2:', portEvent.data);
receivedDiv.textContent = `Channel msg: ${JSON.stringify(portEvent.data)}`;
};
// Start the port
channelPort.start();
// Send an initial message back through the channel
channelPort.postMessage({ status: 'Channel ready in iframe!' });
} else {
// Handle regular postMessage events if needed
console.log('Regular message received from parent:', event.data);
}
});

Channel Messaging is particularly useful for establishing dedicated communication lines, especially when dealing with multiple iframes or workers, as it avoids potential conflicts with general window message listeners.