Another Cross Domain iFrame Communication Technique

August 17, 2009 23:34 by admin

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!

kick it on DotNetKicks.com


Comments (43) -

August 18. 2009 12:15

Michael Mahemoff

Well done Piers, this looks like a great step forward in this area. I'll update my post when I get a chance to mention your work.

Michael Mahemoff

August 18. 2009 15:38

Zach Leatherman

Well done!  This approach will be very useful in getting rid of the polling I was doing.

Thanks for the detailed summary!

Zach Leatherman

August 24. 2009 10:03

Demon

Hi Piers Lawson,

Goog article.

Have you any idea how to make it working in IE7?

Demon

August 25. 2009 07:39

demon101

Yes, the best solution on benalman.com/projects/jquery-postmessage-plugin/

demon101

September 16. 2009 22:00

Piers

I've just fixed the sample to work with IE6 and IE7... DOH!

Piers

November 3. 2009 09:48

Shaung

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

Shaung

November 3. 2009 13:54

Piers

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..

Piers

January 4. 2010 10:02

Lef

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

Lef

February 5. 2010 15:06

Miroslav Nikolov

Very interesting article that almost helped me Smile 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 itSmile

Miroslav Nikolov

February 5. 2010 21:58

Piers

I'm not sure what you mean by "server access"? Surely if someone is able to put the code into a page they can put together all the pieces required. Let me know what problem you are having.

Piers

February 7. 2010 12:34

Miroslav Nikolov

What I mean is that you need to have access (to upload files) to both servers.

For example if I want someone to put my iframes onto his page (of course without knowing his domain) and use your method I will need access to his server to upload one of the proxy frames.

What I am searching is an universal solution that will allow both direction communication (from parent to child iframe and vice versa).

So it is easy to make parent to child communication, but once you can't upload files to the user's server you can not implement child to parent communication (you don't have the parent proxy).

Anyway I found a solution that solves this problem and what the user needs to display the content of my frames is just to insert them along with a piece of javascript code.

I will write an article when I have time with some more details about implementation.

Miroslav Nikolov

February 7. 2010 20:09

Piers

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.

Though you could use the new window.postMessage!

Piers

February 8. 2010 10:26

Miroslav Nikolov

Well I did it that way:

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.

Miroslav Nikolov

February 8. 2010 13:38

Piers

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!

Piers

March 1. 2010 21:34

Rachit

Piers,
I just tested your page on Opera 10.x and I thought it should work. Is it still the case that it won't work?

Thanks!

Rachit

March 2. 2010 23:15

Piers

@Rachit - I'm afraid I haven't spent time sorting this for Opera 10.x However, doesn't that version support window.postMessage? If so... that is the future!

Piers

April 15. 2010 15:58

NIck

Hi

I seem to be experiencing problems using this within https (parent is http and child is https)

This is only a problem with ie7 though

NIck

April 18. 2010 21:48

Piers

@Nick... I'm afaraid I didn't try the HTTP scenario... I guess extra levels of protection are kicking in?

Piers

April 21. 2010 15:41

Radu

@Miroslav - could you show us how you did it, an example?

I'm having a similar problem with the parent iframe and you could save me big time with this.

Thank you!

Radu

May 5. 2010 19:34

Garland Pope

I implemented this technique using jQuery which should come closer to ensuring cross-browser compatibility.

In the inner frame, I set the url and width of the proxy iframe.

$("#RecFrame")
  .attr("src", newURL)
  .width($("#RecFrame").width() > 50 ? 50 : 100);

In the proxy, I use a resize() handler to know when to send the data back to the top page.

$(window).resize(function() {
    window.parent.parent.receiveData(window.location.hash);
});

Garland Pope

May 26. 2010 14:50

Manish

I have searched lot of article but your is easy and worked in all browser.

Manish

June 8. 2010 14:40

Øyvind Sean Kinsey

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!

In addition, it has a fully fledged RPC support ;)

It can be found at http://easyxdm.net/

Øyvind Sean Kinsey

July 10. 2010 01:01

Piers

@Øyvind Sean Kinsey

I rolled my own because at the time, everybody else, including yourself, was using the polling technique Wink 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

Øyvind Sean Kinsey

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 Smile

Øyvind Sean Kinsey

July 10. 2010 11:38

Øyvind Sean Kinsey

By the way, take a look at msdn.microsoft.com/en-us/scriptjunkie/ff800814.asp, in this article I explain some of the basics behind easyXDM.

Øyvind Sean Kinsey

August 5. 2010 09:29

Lens

If you only want to control the scrolling of an crossdomain parent iframe, you could use Futta's frameMagic.js:

http://futtta.be/frameMagic/

Lens

August 16. 2010 23:26

Edwin

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  

Edwin

October 23. 2010 17:08

Sam

Hi Piers,

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.

Could you help me out in this issue if this makes senseSmile

Thanks in advance,
Sam.

Sam

November 3. 2010 03:32

Sarah

Why use a permanent proxy frame instead of throw away proxies?

Sarah

November 8. 2010 22:54

findchris

Hi there.

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.

Any idea why this might be happening?

Thanks,
Chris

findchris

November 9. 2010 00:41

findchris

To answer my own question (above):  Ensure that when you clear the hash on the URL, that you keep the '#' intact, else it loses the onresize handler.

Hope that helps someone.

findchris

December 29. 2010 01:01

David

Thanks alot, works like a charm.

David

March 12. 2011 20:31

Alkaline Water

Good to know there are new and better ways to do iframe. good article.

Alkaline Water

March 15. 2011 10:37

bobobbo

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

Shehabic

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 . .


also the Hash thing Stoppped working in IE8 recently (Last Week) . . . .

Shehabic

June 2. 2011 17:43

φωτοβολταικα

Amazing thread! thanks for the information Smile

φωτοβολταικα

June 7. 2011 16:23

Acer Chrome Book

its very nice post great job and thanks for sharing keep it-up.

Acer Chrome Book

June 19. 2011 00:18

Drukarnia

Thanks alot, works like a charm.

Drukarnia

June 23. 2011 18:14

facebook status

What a post, good stuff works well

facebook status

July 4. 2011 22:33

uploadshack

Thanks for this, very nice code. I hate it when you can scroll when in an iframe

uploadshack

July 14. 2011 13:00

arne karlsson

no idea why people still use iframes, doesn't seem to serve any purpose at all. you can do the same with some decent php, but better for seo and everything.

arne karlsson

September 27. 2011 17:28

Erik Oberhausen

This helped a great deal.

Thanks!!

Erik Oberhausen

November 9. 2011 15:26

merrillreddy

Excellent. this what i am looking for.

merrillreddy

Pingbacks and trackbacks (2)+

Add comment

  Country flag

biuquote
  • Comment
  • Preview
Loading