Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML5

HTML5 WebMessaging Experiment

4.83/5 (9 votes)
5 Sep 2011CPOL5 min read 57.8K   865  
HTML5 WebMessaging provides a simple, efficient, elegant and secure solution for Cross-Domain communication in client side, this article is an experiment for it.

Introduction

As web developers, sometimes we easily encounter one problem: Cross-Domain communication, conforming Same-Origin-Policy, JavaScript code cannot access code stay in different domain (or sub-domain) or protocol (HTTP/HTTPs) or port, so there was no direct (or I can say: simple) way to achieve Cross-Domain Communication. However, those kinds of requirements do happen: page A and page B are in different domains,  B is "embedded" in A, i.e., there is an "iframe" in page A whose "src" is page B's URL, now page A wants to control page B and vice-versa. 

By limiting the solution to be done by 100% client JavaScript, beforehand HTML5, there are a number of tricky "hacks", such as:

  1. URL long polling: Container page A changes the iframe page B's URL hash, and B periodically checks the hash, once the hash changed, it takes action according to the contracted hash value. [BTW, the pattern can be revised to be non-polling by HTML5 onchashchange event]
  2. CrossFrame, a Safe Communication Mechanism Across Documents and Across Domains.
  3. Window Size Monitoring: Update the iframe's window size once, and the containing window subscribes its "onresize" event then takes corresponding action(s). Google Mapplets adopted this pattern.

Well, personally I really don't like all of them... either inelegant, or violates the original functionalities of DOM elements, or too complicated. I believe many people don't like them too even the pattern inventors I bet... That why WHATWG created Cross-Domain communication in HTML5: Web Messaging.

As an HTML5 crazy advocator I like it very much, complete client communication, no server impact, efficient, secure (at least in theory).

How To

"Child" can be an iframe or an popup window by invoking window.open, "parent page" A contains source code like below:

JavaScript
<iframe id="ifr" src="http://domainB.com/B.htm"  onload="sendCommand();">
	No frame!
</iframe>
 
<script type="text/javascript">   
	function sendCommand() {
		var ifr = document.getElementById("ifr");
		ifr.contentWindow.postMessage("Hello", "http://domainB.com");
	}
</script>

Notice, make sure to post message only when the iframe is loaded, otherwise the contentWindow will be still in the same domain with the container page.

Child page B contains code like below:

JavaScript
<input type="button" value="Cross domain call"  onclick="sendMsg();" />

<script>
window.addEventListener("message", receiveMessage, false);

function receiveMessage(evt) {
    console.log("Page B received message from origin: %s.", evt.origin);
    console.log("Event data: %s", evt.data);
    //evt.source will be a window who sent the message,
    //it can be used to post message to it

    // Take action(s)
}
</script>

The demo code above is one direction: parent sends message to child (iframe), actually bi-directional message transfer can also be done, similar with "Parent control child", child page posts message to container window, the only difference is to call "parent.postMessage".

JavaScript
function receiveMessage(evt) {
    evt.source.postmessage("Hello caller");
    // or parent.postmessage("Hello parent");
}

Web Messaging Essential

In a nutshell, HTML5 Web Messaging is a suite of JavaScript API exposed by web browser, to communicate between different browsing context, when JavaScript code in one browser tab/window tries to deliver a message to another tab/window, web browser locates the target tab/window under the specified domain, and file a MessageEvent (which inherits from DOMEvent) to the target tab/window, so if the target tab/window already subscribed the message event, it will gets notified, eventually the message got delivered through MessageEvent.data.

Live Demo

I've done a demo in my dev machine, I override my local hosts file to let Container.com, DomainA.com, DomainB.com and DomainC.com all point to 127.0.0.1:

127.0.1.1 Container.com
127.0.0.1 DomainA.com

127.0.0.1 DomainB.com
127.0.0.1 DomainC.com

I prepared a Container page which contains the code below:

HTML
<h3>HTML5 Cross-Domain post message demo</h3>

<p id="infoBar">
</p>

<div id="wrapperA">
		<input type="text" id="txtA" />
		<input type="button" value="Post Message"  
		onclick="postMsgToIfr('A');" />
		<iframe id="ifrA" src="http://DomainA.com/A.htm"></iframe>
	 </div>

<div id="wrapperB">
		<input type="text" id="txtB" />
		<input type="button" value="Post Message"  
		onclick="postMsgToIfr('B');" />
		<iframe id="ifrB" src="http://DomainB.com/B.htm"></iframe>
	 </div>

<div id="wrapperC">
		<input type="text" id="txtC" />
		<input type="button" value="Post Message"  
		onclick="postMsgToIfr('C');" />
		<iframe id="ifrC" src="http://DomainC.com/C.htm"></iframe>
	</div>

<div style="CLEAR: both">
	</div>

	<script type="text/javascript">
	window.addEventListener("message", receiveMessage, false);

	var infoBar = document.getElementById("infoBar");
	function receiveMessage(evt) {
		infoBar.innerHTML += evt.origin + ": " + evt.data + "";
	}

	function postMsgToIfr(domain) {
		switch (domain) {
			case "A":
				var ifr = document.getElementById("ifrA");
				ifr.contentWindow.postMessage(document.getElementById
				("txtA").value, "http://DomainA.com");
			break;
			case "B":
			    var ifr = document.getElementById("ifrB");
			    ifr.contentWindow.postMessage(document.getElementById
			    ("txtB").value, "http://DomainB.com");
			break;
			case "C":
			    var ifr = document.getElementById("ifrC");
			    ifr.contentWindow.postMessage(document.getElementById
			    ("txtC").value, "http://DomainC.com");
			break;
			default:
			    throw ("No such domain!");
		}
	}        
	</script>

And three pages: A.htm located in DomainA.com, B.htm located in DomainB.com, C.htm located in DomainC.com, physically they are all located under C:\inetpub\wwwrooot, while in the browser I manually type DomainA/B/C.com to make the cheat :), the code in the A/B/C pages are similar, shown below:

HTML
 <h4>DomainA/A.htm1</h4>

 <input type="button" value="Cross domain call"  onclick="doClick();" />

<div id="d"></div>

 <script>
     window.addEventListener("message", receiveMessage, false);
 
     function doClick() {
         parent.postMessage("Message sent from " + location.host, "http://container.com");
     }
 
     var d = document.getElementById("d");
     function receiveMessage(evt) {
         d.innerHTML += "Received message \"<span>" + evt.data + "</span>\" from domain: " 
				+ evt.origin + "";
     }
 </script>

I recorded a GIF image below to demonstrate the Cross-Domain messaging:

HTML5 Cross-Domain Messaging Demo

See the message passed through different domains in bi-direction? Isn't it cool?

MessageChannel

To support independent communication under different browsing context, HTML5 introduced Message Channel to post message independently, its official definition is shown below:

Communication channels in this mechanisms are implemented as two-way 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.

I spent about half a day investigating the Message Channel and finally got it working, my code is shown below.

Container page source code

JavaScript
 <iframe id="ifr" src="http://wayneye.me/WebProjects/HRMS/Opener.html"  
	önload="initMessaging()"></iframe>
 <input type="button" value="Post Message"  onclick="postMsg();" />
 
<div id="d"></div>


 <script>
 var d = document.getElementById("d");
 var channel = new MessageChannel();
 channel.port1.onmessage = function (evt) {
     d.innerHTML += evt.origin + ": " + evt.data + "";
 };
 
 function initMessaging() {
     var child = document.getElementById("ifr");
     child.contentWindow.postMessage('hello', 'http://wayneye.me', [channel.port2]);
 }
 
 function postMsg() {
     channel.port1.postMessage('Message sent from ' + location.host);
 }
 </script>

iframe page source code

JavaScript
<div id="info"></div>

 <input type="button" value="Post Message"  önclick="postMsg();" />
 <script>
 var info = document.getElementById("info");
 var port = null;
 window.addEventListener("message", function (e) {
 console.log(e);
     if(e.ports && e.ports.length > 0) {
         port = e.ports[0];
         port.start();
         port.addEventListener("message", function (evt) {
             info.innerHTML += "Received message \"" + evt.data + "\" 
				from domain: " + evt.origin + "";
         }, false);
     }
 }, false);
 
 function postMsg() {
     if(port) {
         port.postMessage("Data sent from " + location.host);
     }
 }
 </script>

The entire process can be described as:

  1. Container page (A) embedded an iframe whose src is pointing to a page (B) in different domain.
  2. Once the iframe loaded, the container posts a message to page B with a MessagePortArray.
  3. Page B received the message as well as the array containing a list of MessagePort objects.
  4. Page B registers onmessage event on port instance.
  5. Page B invokes port.postmessage to send a message to page A through this MessageChannel.
At this timestamp, I seems only Opera correctly supports MessageChannel, Google Chrome and IE10 Platform Preview 2 failed to deliver the ports array, Safari cannot gets the onmessage event fired. Firefox 7.0 beta event doesn't support MessageChannel object.
Note: Ports can also enable communication between HTML5 Web Workers.

One more thing (plagiarize Steve Jobs :) ), to use Message Channel one noticeable point is, web developer should explicitly close the channel once there is no need, otherwise there will be a strong reference between two pages, as W3 official page emphasizes below:

Authors are strongly encouraged to explicitly close MessagePort objects to disentangle them, so that their resources can be recollected. Creating many MessagePort objects and discarding them without closing them can lead to high memory usage.

Further Reading

Originally posted at Wayne's Geek Life http://WayneYe.com: http://wayneye.com/Blog/HTML5-WebMessaging-Experiment/.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)