Cross-Domain Browser Window Messaging with HTML5 and Javascript

Originally posted on Carbon Five’s Blog.

We’ve previously covered how JSONP and CORS allow thick-client web applications to circumvent the same origin policy preventing requests to servers in different domains. However, cross-domain interaction is also blocked on the client-side; browser windows loaded with different sites have limited access to each other in order to prevent security breaches. Sadly, this also prevents any communication between thick-clients of web applications that do know of and trust each other … unless they use the Window#postMessage method introduced in HTML5. TL;DR &raquo

A Problem

I recently faced the following dilemma on a project. We were working on two applications with feature-rich thick clients that were on different domains. It was required that navigating to a path on one application would automatically launch a child window opened to a page in the other application, or reuse the same child window if it already existed. This functionality could easily be fulfilled by calling the window.open method with a targeted window name, except for an additional criteria; if the child window was already open we did NOT want it to reload the page as this would trigger other events on our server!

Why was this a problem? Because calling Window#postMessage with a URL WILL load it, even if the child window exists and is already displaying that URL. You can prevent this by passing null as the URL parameter. But now if the child window did not exist it will be created with no content!

“No problem,” I thought. “I’ll just detect that situation by checking the child window location property or its document.URL; if they were undefined, I could call window.open again, but this time pass in the real URL to load.”

Unfortunately this was where I ran into the cross-domain access issue. Accessing those properties would always log errors and return undefined regardless of if the child window was properly loaded or empty because the second application was on separate domain. Likewise, the child window could not talk back to the parent window through the window.opener property.

How could I query the other window to see if it was already loaded? I found the answer in Window#postMessage.

Sending data through postMessage()

Introduced a few years ago in Firefox and now supported by all major browsers, postMessage allows documents to communicate with each other through their containing window instances. While it doesn’t provide full access to another window’s model, it gives us a framework to establish communication.

The window sending the event obviously need a reference to the receiving window. We have a variety of ways to do this:

The last example is what I will be using in my solution; more on that later.

With the reference, the sending window can now call postMessage on the receiver, passing the data they want to send and the domain that is permitted to receive it.

You can specify the wild-card '*' character as the second parameter to mean any domain, but this is frowned upon as an opening for security breaches.

Handling 'message' Events

In order to receive the events, the code in the other window must register a handler for 'message' events on its window:

While not strictly enforced, the first thing a handler should do to lessen potential security issues is verify the domain of the message sender with the Event#origin property:

The handler is then able to access the sent data through the Event#data property.

It also receives a reference to the sending window in the Event#source property. It can use this to post messages back:

Of course, the original sender must be listening for its own 'message' events in order to receive them!

David Walsh provides an excellent basic example illustrating all of this. I highly recommend you check it out.

A Solution

Back to my original problem of only loading a child window if it was newly created, but preventing a reload otherwise. I decided to use window#postMessage as the basis of a “keep-alive” system, pinging the child and reloading only if no response was received.

You can see the implementation below of the opening code that would be run in the parent window:

As before, I would call window.open with a null URL which will either return an existing window without reloading its contents or create an empty one. I then use postMessage to ping the child window in the other domain and wait a second for a response.

In the client-side code of the second application, I would setup a listener for 'message' events on the window.

If the child window is already loaded with the second application, the listener will handle any pings from the parent window and send the correct response; the parent window would then take no further action. But in the case of a new, empty child window the listener would not even exist; the correct response would never be sent back and so the parent window would eventually call window.open again, this time passing in the full URL. Thus we got the desired behavior!

I could also clean up code by using a library like jQuery to normalize listening for the message event across browsers:

Conclusion

HTML5’s Window#postMessage provides a simple framework for thick-clients of different domains to communicate on the client-side with each other, without having to round-trip through the servers. As long as we verify messages are sent to and originate from trusted domains we can safely exchange data and trigger functionality. In fact, you could open your thick clients to new interactions that goes beyond the traditional browser functionality by establishing a protocol with other applications.

tl;dr

  • Send messages to other windows Window#postMessage; be sure to specify the domain
  • Receive messages by listening for 'message' events on the window
  • Verify the sender of the message by checking the domain in the Event#origin property.
  • Read the message sent in the Event#data property.
  • A reference to the window that sent the message is found in the Event#source property.