Using Javascript postMessage to Talk to iFrames
Tony Pitale, Former Viget
Article Category:
Posted on
To javascript, iFrames are typically black boxes. Javascript now allows cross-document communication thanks to the postMessage function. Here's how I used postMessage to get the height and width of a document in an iFrame.
In the course of experimenting with click tracking and heatmaps I needed to discern the size of the content on a page loaded within an iFrame in order to resize a canvas that I was overlaying.
A first attempt way be to go right at the information as if it was contained within our own page. It might look something like this:
iframeBody = document.getElementById('content').contentWindow.document.body; iframeHeight = iframeBody.scrollHeight; iframeWidth = iframeBody.scrollWidth;
This, of course, fails (on the first line) with an error message similar to: "Unsafe JavaScript attempt to access frame with URL http://domain.com/ from frame with URL file:///index.html. Domains, protocols and ports must match."
Thankfully, as part of the draft HTML5 specification we get cross-document messaging thanks to the method postMessage. Here's how we can use it to ask for the height and width of our iFrame document.
Our code will have two parts. I will refer to them as the origin and remote. The origin is the site that has an iFrame and the remote will be the site loaded into the iFrame. Both the origin and remote must listen for, and send, messages.
First, let's set up our listener on the remote side, and later we'll handle the respondToSizingMessage
.
respondToSizingMessage = function(e) { if(e.origin == 'http://origin-domain.com') { // e.data is the string sent by the origin with postMessage. if(e.data == 'sizing?') { e.source.postMessage('sizing:'+document.body.scrollHeight+','+document.body.scrollWidth, e.origin); } } } // we have to listen for 'message' window.addEventListener('message', respondToSizingMessage, false);
We can see here that if the request comes from a domain we control, and it asks for the sizing, we will post a message to the source/origin with our own height and width. This message will in turn be received by the listener we will add next on the origin. An important note: the origin check is optional. I often leave it out because I don't care who knows the sizing of the document body. Otherwise, you could check a whitelist of domains using something like jQuery's $.inArray
.
Next, we should set up the listener on the origin side, to handle the reply message from the remote:
handleSizingResponse = function(e) { if(e.origin == 'http://remote-domain.com') { var action = e.data.split(':')[0] if(action == 'sizing') { resizeCanvas(e.data.split(':')[1]); } else { console.log("Unknown message: "+e.data); } } } window.addEventListener('message', handleSizingResponse, false);
Now, the origin can send a message to the URL of the remote (matching the URL of the iFrame):
iframe = document.getElementById('content'); iframe.contentWindow.postMessage('sizing?', 'http://remote-domain.com');
Finally, the listener on the remote will respond to the message event it was set up to receive with respondToSizingMessage.
Using iFrames has often been a frustrating experience. The security, in place for good reasons, makes them somewhat of a black box to javascript. Hopefully, this new feature will provide a clear and safe method of interacting with iFrames. I could see how this feature might even lead to new and more powerful uses of iFrames, as tunnels or proxies to interact with sites on other domains. Let me know in the comments if you have any other ideas as to how we might use postMessage
in new and interesting ways.