Recently I created a web page that hosted an iFrame. The contents of the iFrame were served from another domain. That content would use JavaScript to hide and show some HTML elements. As the iFrame’s content changed, I didn’t want scroll bars to appear or some of the content to be hidden (if I applied scrolling="no"). I could have just made sure the iFrame was big enough to contain the maximum content... but I wanted a more dynamic solution that made the iFrame appear more integrated with its parent page. I needed the parent page to adjust the size of the iFrame as the content size changed.
This requires client side, cross domain communication between the iFrame and its parent page. Security restrictions prevent direct JavaScript calls between the two. HTML 5 introduces window.postMessage which is supported by FireFox 3, Opera 9.6 and Internet Explorer 8. This should solve this problem, but if you still need to support some older browsers, the standard approach is to use URL Fragments.
Basically, a page can change the fragment part of an iFrame’s Location URL (i.e. the part of the URL after the # symbol) without the iFrame reloading. So this part of the URL can be used to pass messages to the iFrame… if only the iFrame contents knew the URL had changed!
Most articles on using the URL Fragment technique advocate the target iFrame polling its Location to detect changes in the fragment… perhaps checking 5 times a second. Julien Lecomte describes a variant that creates throw away proxy iFrame’s. These can check their location within an onload event handler, extract the message, then make a call to the target iFrame, passing in the message. Once complete, the iFrame can be deleted. This means polling is no longer required. Also, when combined with caching, it should be fast and remove the risk of missing a message.
This post by Michael Mahemoff provides a good introduction to both techniques.
I came across these blog entries after I had come up with something similar, but with a slight twist… it may not be new but I didn’t find any other examples along these lines. I used a permanent proxy frame instead of throw away proxies. However, rather than polling the Location, I signalled a new “message” was ready to be processed by resizing the proxy iFrame. The JavaScript within the proxy iFrame registers a resize handler which when triggered reads the message. This approach is more immediate than both the polling and the dynamic iFrame techniques. I’m not 100% sure on whether threading within browser could allow messages to be lost in the case of several being sent close together, but I don’t believe so.
I’ve hosted an example of this working. The downside… like the other techniques… it doesn’t work in Opera. For a true cross browser solution, you should try to use window.postMessage first and fail over to URL Fragments.
How it works…
- The Parent Page hosts two iFrames. One contains the Content to be displayed, the second is a Proxy that is moved out of sight (both iFrames are served by the same domain);
- The Parent Page sends messages to the Content iFrame by changing the URL Fragment of the Proxy iFrame and signalling that a new message is available (by toggling the size of the Proxy):
function SendMessageToFrame(message) {
var elem = document.getElementById('innerFrameProxy');
elem.contentWindow.location = 'http://www.pierslawson.plus.com/Examples/
CrossDomain/InnerFrameProxy.html#' + message;
elem.width = elem.width > 50 ? 50 : 100;
}
- The Proxy registers a handler that is called as its size is changed. The handler inspects the URL Fragment and passes the message on to the Content iFrame (this is allowed as it is not a cross domain call). It is this call that fails with Opera… there does not appear to be a way for the Proxy iFrame to get a handle to the Content iFrame. Internet Explorer and Firefox can use parent.frames["hostFrame"] to find the Content iFrame.
Communication from the Content iFrame back to the Parent Page follows a similar technique:
- The Content iFrame loads a Parent Proxy iFrame that it can use to communicate with the Parent Page. Again, this Parent Proxy is moved out of sight.
- The Content iFrame sends messages to the parent page by changing the URL of the Parent Proxy iFrame and signalling that a new message is available by toggling the size of the Parent Proxy.
- The Parent Proxy registers a handler that is called as its size is changed. The handler passes the message on to the Parent Page using top.
These are the URLs for each part of the system if you want to see the full source:
Some notes on things that tripped me up along the way (due to me not having worked with JavaScript for a while):
- Set document.domain to a common base. At one stage my Parent Page was loaded using http://shouldersofgiants.co.uk and the Parent Proxy was using http://www.shouldersofgiants.co.uk. As these are actually different domains (www being a sub-domain) the Parent Proxy could not communicate with the Parent Page. The solution is to set the document.domain to a common parent domain… in this case http://shouldersofgiants.co.uk;
- When I first wrote parent.frames["hostFrame"].OnMessageFromParent(message); I was testing in Internet Explorer and forgot that it is forgiving as to whether the named element is identified by its id or its name attribute. Firefox will only search for elements with a matching name attribute;
- The proxy classes vary how they register for the onresize event. For Firefox and Opera, the event is only raised on the <body> element. However, registering an event on the body element with Internet Explorer results in the handler being called twice. There is much discussion of this “feature” online. The solution is to have a <div> within the body and for internet explorer to register an event against that instead;
- My original requirement was to size the iFrame according to the size of the contents… but what size to set the iFrame to initially? The only solution is to get the iFrame to send a message once it has finished loading (i.e. handle the onload event). However, this may fire before the Parent Proxy has loaded, so the message could be lost. The solution… have the proxy check for a message after it has loaded. This needs to happen asynchronously to the basic page loading. This achieved by having the proxy set a timeout to happen ASAP:
setTimeout('ForwardMessage();', 0);
- I could have reduced the chances of the previous problem occurring in the first place by caching the proxy content. However, there is one benefit for me in not caching the content… as my real application would be using ASP.Net, having the proxy content actually served by IIS, the user’s session is kept alive.
- Firefox does not raise an onresize event if the content changes size due to a JavaScript call (as opposed to the user changing the window size.
UPDATE 16 SEPT 2009: I have updated the example to work correctly on Internet Explorer 6 and Internet Explorer 7… I really should have checked better!