APIs: The Cross-document Messaging API - JUMP START HTML5 (2014)

JUMP START HTML5 (2014)

Chapter 31 APIs: The Cross-document Messaging API

The Cross-document Messaging API in HTML5 makes it possible for two documents to interact with each other without directly exposing the DOM. Just imagine the following scenario: Your web page has an iframe that is hosted by a different website. If you try to read some data from that iframe, the browser will be very upset and may throw a security exception. It prevents the DOM from being manipulated by a third-party document, thereby stopping potential attacks such as CSRF or cross-site scripting (XSS). But the Cross-document Messaging API never directly exposes the DOM. Instead, it lets HTML pages send messages to other documents through a message event.

The Cross-document Messaging API is useful for creating widgets and allowing them to communicate with third-party websites. For example, let’s say that you have a page that serves ads and you allow the end-users to embed this page in their websites. In this case, you can let users personalize the ads or modify the type of ads through the Cross-document Messaging API. Clients can send messages to your page and you can receive those messages too.

The JavaScript API

The Cross-document Messaging API revolves around a single method: window.postMessage(). As its name suggests, this method allows you to post messages to a different page. When the method is called, a message event is fired on the receiving document side. Before moving further, it’s crucial to understand the properties of the message event. There are three properties we’re interested in:

1. data: This holds the message being sent. You have already played with it in previous chapters (remember calling event.data in SSEs?).

2. origin: This property indicates the sender document’s origin; i.e the protocol (scheme) along with the domain name and port, something like http://example.com:80. Whenever you receive a message, you should always, always check that the message is coming from a trusted origin. I will explain how to do that in the next section.

3. source: This is a reference to the sender’s window. After receiving a cross-document message, if you want to send something back to the sender, this is the property you’ll use.

Basic Usage

You send a message to another document by calling window.postMessage(). This function takes two arguments:

· message: the actual message you want to send

· targetOrigin: a simple string indicating the target origin—an additional security feature (I’ll explain how this is useful in the next section)

The code looks like the following:

targetWindow.postMessage(message, targetOrigin);

You should note that targetWindow refers to the window to which you want to send a message. It may be a window you just opened through a call to window.open(), or it can also be the contentWindow property of some iframe.

Note: A Reference to an Open Window

window.open('a URL') returns a reference to the opened window. You can always call postMessage() on it.

Let’s build a very simple example. Say that we have a parent page that has an iframe inside it. The iframe’s src points to a third-party website that provides us with a random image.

This is the page referenced by the iframe in our parent page:

child.html

.f<!DOCTYPE html>

<html>

<head>

<title>A page that provides a random image</title>

</head>

<body>

<div id="container">

<img

↵src="http://randomimage.setgetgo.com/get.php?key=2323232"

↵id="image"

/>

<div>

</body>

</html>

So far, so good! Now let’s have a look at our parent page:

parent.html

<!DOCTYPE html>

<html>

<head>

<title>The Parent Document</title>

</head>

<body>

<iframe

↵src="child.html"

↵height="500" width="500" id="iframe">

</iframe>

<br/>

</body>

</html>

When you open up the parent page, you can see the image coming from the page child.html. For now, both parent.html and child.html are on the same server (localhost) for testing purposes. But they should ideally be on different servers.

But we don’t want to keep showing a static image to our users, nor reload our iframe. It would be really great if we could ask the page child.html to reload its image when a user hits a button on our parent page; i.e. parent.html.

Let’s start by adding a button to our parent page. We’ll also write a function that responds to the click event and sends a message to child.html.

parent.html

<!DOCTYPE html>

<html>

<head>

<title>The Parent Document</title>

</head>

<body>

<iframe

↵src="child.html"

↵height="500" width="500" id="iframe">

</iframe>

<br/>

<button id="reloadbtn">Reload</button>

<script>

document.getElementById("reloadbtn").

↵addEventListener("click", reload, false);

// reload handler

function reload(e) {

// is cross-messaging supported?

if (window.postMessage) {

document.getElementById('iframe').

↵contentWindow.postMessage(

Math.random()*1000, 'http://localhost'

);

}

else {

console.log('postMessage() not supported');

}

}

</script>

</body>

</html>

Note: Using Modernizr

If you’re using Modernizr to check browser compatibility, check for the property Modernizr.postmessage.

As you can see, when a user clicks on reload button our callback reload() executes. First, we ensure that postMessage() is supported by the browser. Next, we call postMessage() on the iframe’s contentWindow. The contentWindow property of an iframe is simply a reference to that iframe’s window. Here, our message is a simple random number (we will see why shortly). The second argument to postMessage() is http://localhost. This represents the targetOrigin to which the message can be sent. The origin of the iframe’s src and this argument must be same in order forpostMessage() to succeed. This is done so that other unintended domains cannot capture the messages. In this case, if you pass something else as the targetOrigin, postMessage() will fail.

Tip: targetOrigin

Think of targetOrigin as a way of telling the browser to which origin the message can be sent. You can also pass "*" as the targetOrigin. As you might have guessed, * is a wildcard that says the message can be sent to documents from any origin. But using a wildcard means loosening your security system by allowing the message to be sent to any origin. I recommend passing the exact origin as the second argument to postMessage() instead of the wildcard.

Now we have to receive the message in child.html and take appropriate action. Here’s the modified child.html this:

child.html

<!DOCTYPE html>

<html>

<head>

<title>A page that provides random image</title>

</head>

<body>

<div id="container">

<img

↵src="http://randomimage.setgetgo.com/get.php?key=2323232"

↵id="image"

/>

<div>

<script>

window.addEventListener('message', messageReceiver, false);

function messageReceiver(event) {

// can the origin can be trusted?

if (event.origin != 'http://localhost') return;

document.getElementById('image').src =

↵"http://randomimage.setgetgo.com/get.php?key=" + event.data;

console.log(

'source=' + event.source +

', data=' + event.data +

', origin=' + event.origin

);

}

</script>

</body>

</html>

First, we attach a callback to the message event. Whenever parent.html sends a message to child.html, this callback will be executed. The first and most important step is to check whether you are receiving messages from the intended origin. After adding an event listener to the messageevent, you can receive messages from documents of any origin. So, it’s recommended to always put a check inside your callback to ensure that the message is coming from a trusted origin.

Next, we retrieve the message from event.data. This particular API that we’re using for random images requires a different random number each time so that the generated image will be a unique one. That’s why we’re generating a random number on a button click (in parent.html) and passing that as a message to child.html. In child.html, we simply construct a new image URL with the help of the random number and update the image’s src. As a result, we can see a new image each time we click the reload button from the main page.

Note: Sending a Message Back

If you want to send a message back to parent.html, you can always use event.source.postMessage() inside your event listener in child.html. Consequently, you’ll also need an event handler in the parent page.

Detecting the Readiness of the Document

Most of the time, you’ll send messages to iframes embedded in your pages. But many times, you may also need to open a new window from your page and post messages to that. In this case, ensure that the opened window has fully loaded. Inside the opened window, attach a callback to theDOMContentLoaded event, and in that function send a message to the parent window indicating that the current window has fully loaded.

Tip: Getting a Reference

Inside the DOMContentLoaded event listener (in the opened window), you can get a reference to the window by accessing event.currentTarget.opener and calling postMessage() on it as usual. Tiffany Brown explains how to achieve this in an excellent tutorial.

Conclusion

This was the overview of the Cross-document Messaging API. By using this API, two cross-origin documents can securely exchange data. Because the DOM is not directly exposed, it’s now possible for a page to directly manipulate a third-party document.

The Cross-document Messaging API certainly gives you more power. But, as you know, with great power comes great responsibility! If you fail to use this API properly, you may end up exposing your website to various security risks. So, as discussed in this chapter, you should be very, very careful while receiving cross-document messages to avoid security risks. Similarly, while sending messages with window.postMessage(), don’t use * as targetOrigin. Instead, provide a single valid origin name.

Although it’s not possible to cover each and everything about the API in detail, this chapter gives you a head start. You should now be able to experiment with different things on your own. For further reading, I strongly recommend the following resources:

· the Mozilla Developer Network

· the W3C specification.

So, this brings us to the end of our tour through five of the most important and useful HTML5 APIs. I hope you've found this book a useful introduction to these powerful technologies. Do take a look at the sample project ideas that I have shared in the end of each chapter; I also encourage you to get creative and think of some other good use cases that can be implemented.

Happy coding!