HTML5 WebMessaging In Essence

Download HTML5_Cross-Domain_WebMessaging_Demo.zip - 2.97 KB

As a web developer, sometimes we are easy to 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 does happen: page A and page B are in different domain,  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 vise-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 containning 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 complecated. 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 invocking window.open, "parent page" A contains source code is like below:

 <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 post message only when the iframe is loaded, otherwise the contentWindow will be still in same domain with the container page.

Child page B contains code like below:

 <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 trasfer can also be done, similar with "Parent control child", child page posts message to container window, only different is call "parent.postMessage".

 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 trys 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 code below:

 <h3>HTML5 Cross-Domain post message demo</h3>
<p id="infoBar"></p>
<div id="wrapperA">
    <input type="text" id="txtA" /><br />
    <input type="button" value="Post Message" onclick="postMsgToIfr('A');" />
    <br />
    <iframe id="ifrA" src="http://DomainA.com/A.htm"></iframe>
</div>
<div id="wrapperB">
    <input type="text" id="txtB" /><br />
    <input type="button" value="Post Message" onclick="postMsgToIfr('B');" />
    <br />
    <iframe id="ifrB" src="http://DomainB.com/B.htm"></iframe>
</div>
<div id="wrapperC">
    <input type="text" id="txtC" /><br />
    <input type="button" value="Post Message" onclick="postMsgToIfr('C');" />
    <br />
    <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 + "<br />";
    }

    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 new error("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, phisically they all located under C:\inetpub\wwwrooot, while in the browser I manually type DomainA/B/C.com to make the cheatSmile, the code in the A/B/C page are similar, showing below:

 <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 style=\"color:red\">" + evt.data + "</span>\" from domain: " + evt.origin + "<br />";
    }
</script>

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

HTML5 Cross-Domain Messaging Demo

See the message passed through different domain 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 showing below:

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. 

I spent about half a day in investigating the Message Channel and finally got it worked, my code is showing below:

Container page source code

 <iframe id="ifr" src="http://wayneye.site/WebProjects/HRMS/Opener.html" onload="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 + "<br />";
};

function initMessaging() {
    var child = document.getElementById("ifr");
    child.contentWindow.postMessage('hello', 'http://wayneye.site', [channel.port2]);
}

function postMsg() {
    channel.port1.postMessage('Message sent from ' + location.host);
}
</script>

iframe page source code

 <div id="info"></div>
<input type="button" value="Post Message" onclick="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 + "<br />";
        }, 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 JobsSmile), to use Message Channel one noticable 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 emphasized 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

HTML5 Web Messaging

window.postMessage - MDN Docs

HTML5 Demo: postMessage (cross domain)

HTML5′s window.postMessage AP

Internet Explorer 10 Platform Preview: HTML5

http://ithelp.ithome.com.tw/question/10057709

 

Also posted on CodeProject: http://www.codeproject.com/KB/HTML/HTML5-WebMessaging.aspx

Tags:

Categories:

Updated:

Leave a comment