Web browsers, for security and privacy reasons, prevent documents in different domains from affecting each other; that is, cross-site scripting is disallowed.
That means that communication between frames, tabs, and windows was restricted for security reasons. If browsers allowed you to access content loaded into other frames and tabs, site could steal information another site using scripting. So, attempting to retrieve or modify content loaded from another source raises a security exception and prevents the operation.
But there are cases where you want content from different sites to be able to communicate inside the browser, such as for mash-ups.
To meet this need, HTML5 allows Cross-Document Messaging and Channel Messaging.
In this post, you will learn:
- How to send a message to an iFrame using Cross-Document Messaging.
- Describe several security considerations in using Cross-Document Messaging.
- How to send and receive a message using Channel Messaging.
- Describe the function of ports when using Channel Messaging.
HTML5 allows Cross-Document Messaging specified by the W3C, which enables secure cross-origin communication across iframes, tabs, and windows. It defines a postMessage API as a standard way to send messages and an EventListener to receive them.
The API adds a new method to every window (including the current window, popups, iframes, and frames) that allows you to send textual messages from your current window to any other – regardless of any cross-domain policies that might exist.
Warning. Use of this API requires extra care to protect users from hostile entities abusing a site for their own purposes. I’ll explain in a section that follows.
Most browsers support cross-document messaging, including Firefox 18+, Chrome 24+ Safari 5.1+, Opera 12.1+ iOS Safari 6, Opera Mini, Android, Blackberry. In IE 8+ it works only in frames/iframes (not other tabs/windows). Also in IE an object cannot be sent using postMessage.
The same specification also offers a Channel Messaging that enable independent pieces of code (e.g. running in different browsing contexts) to communicate directly.
Cross-Document Messaging
To send a cross-document message, use postMessage() to the contentWindow of the iFrame. Include the message, and the
var o = document.getElementsByTagName('iframe')[0]; o.contentWindow.postMessage('Hello world', 'http://b.example.org/');
Messages can:
- Be structured objects, such as nested objects and arrays.
- Contain JavaScript values (strings, numbers, Dates, etc).
- Contain certain data objects such as File Blob, FileList, and ArrayBuffer objects.
Objects listed in transfer are transferred, not just cloned, meaning that they are no longer usable on the sending side.
If the origin of the target window doesn’t match the given origin, the message is discarded, to avoid information leakage. To send the message to the target regardless of origin, set the target origin to “*“. To restrict the message to same-origin targets only, without needing to explicitly state the origin, set the target origin to “/”.
The windows object of the page receiving the message can then receive the message and display it.
window.addEventListener('message', receiver, false); function receiver(e) { if (e.origin == 'http://example.com') { if (e.data == 'Hello world') { e.source.postMessage('Hello', e.origin); } else { alert(e.data); } } }
The receiver in the preceding example checks to see if the domain is the expected domain, and then looks at the message. It posts back an “Hello” message back to the originator if the message was “Hello world”.
The receiver can either display the message to the user, or responds to by sending a message back to the document which sent the message in the first place.
The scripts running in the target browsing context must have time to set up listeners for the messages. So do not expect the send a message right when the sending window is displayed.
Cross Document Messaging Sample
This HTML posts a message to the iFrame.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Post Message</title> | |
</head> | |
<body> | |
<form id="the-form" action="/"> | |
<input type="text" id="my-message" value="Your message"> | |
<input type="submit" value="postMessage"> | |
</form> | |
<iframe id="my-iframe" src="postMessageTarget.html"></iframe> | |
<script> | |
window.onload = function () { | |
var iframeWin = document.getElementById("my-iframe").contentWindow, | |
form = document.getElementById("the-form"), | |
myMessage = document.getElementById("my-message"); | |
myMessage.select(); | |
form.onsubmit = function () { | |
iframeWin.postMessage(myMessage.value, "/"); | |
return false; | |
}; | |
}; | |
</script> | |
</body> | |
</html> |
This HTML receives the data, checks the origin, and displays the message as innerHTML (NOT as content that could be executed.)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Display Message</title> | |
<script> | |
function displayMessage(evt) { | |
var message; | |
if (evt.origin !== "http://nextechu.com") { | |
message = "You are not worthy"; | |
} | |
else { | |
message = "I got " + evt.data + " from " + evt.origin; | |
} | |
document.getElementById("received-message").innerHTML = message; | |
} | |
window.addEventListener("message", displayMessage, false); | |
</script> | |
</head> | |
<body> | |
<p id="received-message">I've heard nothing yet</p> | |
</body> | |
</html> |
Security Warnings
Take care when you are using this API to prevent basic forms of spoofing. Here are some points to consider.
- Check the incoming domain. If you are expecting a message from a specific domain, set of domains, or even a specific url, please remember to verify the .domain or .uri properties as they come in, otherwise another page can spoof this event for malicious purposes.
- Check the message itself. Just because a string is coming in, as a message, doesn’t mean that it’s completely safe. You take the message and insert it onto your page using .textContent. If the message contained a script tag and you inserted it into your page using .innerHTML, it would execute immediately.
- No wildcard origins. You should not use the wildcard keyword (*) in the targetOrigin argument in messages that contain any confidential information.
- Denial of service. When you accept messages from any origin you should consider the risks of a denial-of-service attack. An attacker could send a high volume of messages. IIf the receiving page performs expensive computation or causes network traffic to be sent for each such message, the attacker’s message could be multplied into a denial-of-service attack. Authors are encouraged to employ rate limiting (only accepting a certain number of messages per minute) to make such attacks impractical
Channel Messaging
Internet Explorer 10 and Windows Store apps using JavaScript introduce support for channel messaging. Channel messaging enables code in different browsing contexts to communicate directly via ports.
To see if channel messaging is supported, check the window object:
if (window.MessageChannel) { // channel is supported } else { // not supported }
You can communicate between windows, but also with Web Workers.
To open a channel, you create a channel object. The channel object contains both port1 and port2 endpoints. Typically, one port is kept as the local port, and the other is sent to the remote window or worker. One or more ports can enable communication between workers.
Communication channels in this mechanisms are implemented as two-ways pipes, with a port at each end. Messages sent in one port are delivered at the other port, and vice-versa. Messages are asynchronous, and delivered as DOM events.
To create a connection, known as two entangled ports in the spec, the MessageChannel() constructor is called. The sender passes the first message in array of ports – one is kept local, the other is sent to the remote code.
var channel = new MessageChannel(); otherWindow.postMessage('hello', 'http://example.com', [channel.port2]);
To send messages, you send a postMessage on the port.
channel.port1.postMessage('hello');
To receive messages, you listen for message events.
channel.port1.onmessage = handleMessage; function handleMessage(event) { // message is in event.data // ... }
About Ports
Messages sent in one port are delivered at the other port, and vice-versa. Messages are asynchronous, and delivered as DOM events.
You can think or ports as a way to expose limited capabilities to other objects in the system. They could participate in :
- Weak capability system, where the ports are merely used as a convenient model within a particular origin, or
- Strong capability model, where they are provided by one origin provider as the only mechanism by which another origin consumer can effect change in or obtain information from provider.
Local Storage Alternative
Carlos Aguayo proposes an alternative to cross windows messaging using LocalStorage events. See his post Cross Window Messaging with HTML5 LocalStorage Events.
References
- Web Messaging specification.
- An Introduction to HTML5 web messaging on dev.opera.
- Channel messaging (Windows)
Example Code
- Robert Nyman’s sample postMessage
- Wayne Ye’s HTML5 WebMessaging Experiment
- Message Channel object (Internet Explorer) for a channel message example.
Sample Code
Sample code is available in the DevDays GitHub repository. See https://github.com/devdays/html5-tutorials