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):

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!

Do you have a sample that works within a sub-frame? In other words, sample.htm is hosted in another domain (localhost for example) that contains an iframe with src set to your sample url. Other postings on the web suggest this should work but I can't seem to get it to work using chrome or firefox or IE7 (known issue there though).

You need to bear in mind which domains are talking to which. I would debug through or use alert messages to test which objects are actually valid in each step of the way. It could be that my code makes use of:

top.OnMessageFromChild(message);

to call the parent frame. Which in your case will cause the ParentFrameProxy to actually try and call your outermost page, not the "parent" frame. I'm guessing you will have to use an alternative to top..

GHreat script, unfortunately the resize doesn't work on Safari and Google Chrome...

Lef

February 5. 2010 15:06

Very interesting article that almost helped me But almost, because I wanted an universal solution that will work no matter where you put the iframes.

In this case you need to have server access to both domains to store the proxies, but what if you give iframe's code to someone and want everything to work just fine, when he/she inserts them into the page? - well in most of the cases the situation is just like that.

Anyway I am continuing my testings and if I find an universal solution will share it

If the other party is providing the parent and you are providing the content for an iFrame, the proxy for parent to iFrame communications must be served from your domain. Yes, the proxy for iFrame to Parent must be served from their frame. Only content served from the other party's domain is permitted to call JavaScript in the parent page. I'd be interested to see how you have worked around this.

Because I can't store parent proxy frame on the user's server I set a setTimeot function in the main page (where all iframes are).

This seTimeout checks for url changes. Once the url is updated with the needed data I call clearTimeot function to prevent its execution.

This handles child to parent communication without the need of parent proxy iframe (just child proxy).

Basically this can be used in most advertisement cases when you want someone to insert iframes (banners, text links or something more complex as it is in my case) in his website.

I do it that way because:1) I wanted back and forward buttons to work; 2) Content width of the iframes to be adjustable based on the width of the element, which contain the iframe;3) iFrames height to be adjustable based on the width of the content calculated in 2).

It worked pretty good for me.I have tested it on Mozilla Firefox 3.5, Chrome, IE6, 7, 8 - will test it on Safari and Opera later, but as you said probably it won't work in Opera.

Yes, that will work. It is probably the "usual" solution to this problem. It relies upon "polling" which, to make it appear instantaneous, requires a high polling rate which could be a drain on the client machine.

It is the technique I was looking to replace, I do mention it above and Michael Mahemoff had a post describing it fully (which appears to have been truncated recently).

It is good you can combine approaches to get a solution for your situation!

Instead of rolling your own support for this, why not use a verified solution like easyXDM?It works in all browser from IE6 and onwards providing <15ms transits for all postMessage capable browsers AND IE6/7 with NO dependencies!

I rolled my own because at the time, everybody else, including yourself, was using the polling technique It's good to see that you have now incorporated the resize event method.

I might still use my raw form over your libraray for visible iFrames (where I believe you still use polling). Also I'm not sure how your library would deal with the page within the iFriame POSTing the iFrame to another page (possibly in yet another domain). This is a situation I have had to deal with.

However, it is great you have wrapped all the techniques into a single library (who verified it?).

Piers

July 10. 2010 11:36

Yes, when using FIM, the resize approach is used whenever possible, but as you say, this is not possible when having visible iframes (to disturbing). The reason for this is that I didn't want to require an extra file just for the proxying.The solution easyXDM provide as a fallback, where it requires an extra file is the NameTransport which is way faster than FIM - it's actually almost as fast as postMessage. The existence of this transport is the reason why FIM with helper documents has been removed from easyXDM (it once had it).

easyXDM doesn't like it when you navigate the inner frame as it looses the 'link', but this is easily worked around by having a new iframe inside hosted one where all the navigation/posting occurs.

Remember that easyXDM doesn't just enable communication where it fragments, enqueues messages etc, but it makes sure that all messages are authentic, that they cannot be eavesdropped on etc.

By verified I meant that it has been thoroughly tested in all sorts of browsers, and that it has been adopted by quite a few 'demanding' users

I know it is something totally differnt but why not scrape the content of that third party and display it the way you like. That way you have much more control over it. Unless of course that third party doesn't allow their content to be scraped

This is an amazing article, things put together perfectly......I would like to thank you for that.

I have an issue with cross domain communication...Let me try to explain it so that you can shed some light on it...ISSUE: I have a CHAT application which resides on chat.ourserver.com and i am calling that application into my actual website www.ourserver.com as an IFRAME.

Now, i have a link on the main parent page as <a href="##" onclick="Javascript:jqcc:chatapp:chatwith('#userid#');">Chat with this user</a>where the function chatwith() resides on chat.ourserver.com.This link when clicked, is supposed to open chat window on the iframe itself.

When i have the same link on chat.ourserver.com with test userID and it works perfectly.

We have this working just fine for the first event/message passed from child to parent, but it seems as though the resize handler gets dropped after the first resize event. So no subsequent messages get passed.

I have implemented this solution on my site, it works well with ie, ff, and chrome, but it doesn't on safari. The method ForwardMessage in the proxy is never called.

bobobbo

March 24. 2011 22:24

Nice article but what if you don't have control over the main domain . . . i.e. You only give users a JS code that creates an IFrame on their site (Domain) and this iframe has content from your domain and you need to change the size of the iframe from within the content ( Please notice that you don't have control over the main domain i.e. there's no Parent Proxy . .