Tuesday, June 06, 2006

Cross Domain Frame Communication with Fragment Identifiers (for Comet?)

A page is served from a different domain than the URL for an iframe in that page. Normally cross domain, cross frame communication is prohibited for security reasons. However, the two frames can communicate with each other by using fragment identifiers (the hash part of an URL, like http://some.domain.com/path/to/page.html#fragmentIdentifier).

Since fragment identifier changes don't reload the page, state can be maintained in each of the frames.

This could be used to allow cross domain usage of an API that uses Comet as its communication with the server. Or for UI that a third party wants to embed it, but still allow some stateful communication with the hosting page.

The limitations:
  • Communication is limited to the size of fragment identifiers. I'm not sure on the max size for all browsers, but I would think the same limitation on the size of GET URLs probably hold here too. So the max size for the full URL should probably be kept under around 1KB.
  • Using the iframe may cause issues with the back button.
Test page.


hapybrian said...

that looks cool, as a workaround.
Would be nice if there were a clean
way to communicate though, eh?

Anonymous said...

Good find. This could be used on widget sites that want to sandbox third party content inside of an iframe. The main problem with this is that the content in the iframe cant specify the necessary size of the iframe to show it all. With this hack, the content page could report the pizel sizes in the fragment identifier and the parent page could poll looking for changes. When a change is found, it can resize the frame.

Anonymous said...

Nice cross domain solution...
Examples are easy to follow...

Anonymous said...

Nice post.

But I would like to know that will it work under IE7.
This same functionality is used by dojotoolkit.org which is used by meebo.com etc., But they anounced that there is a problem in IE7. Will it work in IE7.

Thank you,

James said...

Yes, I fixed the code for IE 7 in the Dojo toolkit for Dojo 0.4.1. For a description of the IE 7 issue, see two later blog posts:

1) IE 7 Breaks IFrame APIs that use parent.location

2) IE 7 and IFrame APIs Part 2

Die Preetzer Abelings said...

The test example uses a timer in the receiver frame to check for new messages. That means messages may be delayed.

What about the following solution to instantly get the new message?

1. the receiver frame defines an onfocus event handler which gets the message out of location.hash
2. the sender frame calls parent.frames['receiver'].location = ...
3. the sender frame calls parent.frames['receiver'].focus()

Step 3 works x-frame as far as I tested it.

James said...

Hacim: that is a neat idea!

Although, I wonder about issues where you might steal the user's focus away from something they had focused in the UI (say they like using the keyboard to tab through the UI).

I wonder if blur() would work also. That might preserve the user's page focus and still avoid polling.

I'm in the middle of some other work at the moment, but I definitely want to explore this approach.

I filed this trac ticket for the xdomain iframe communication module I have in Dojo. Feel free to track progress there (you can add your email address to the cc: field if you use the guest/guest login).

Andrés Testi said...

Hi James. I'm working in a GWT extension library named BlackPill, and this includes a tiny text compressor. What do you think about compress the text included in the server messages?
Compression can save lot of bytes in the URL. URL is limited to 2KB (in IE almost) and when you try to send UNICODE, escapes reduces a lot the real text size.
Please, try to compress a text between 1000 and 2000 characters in my compressor to evaluate the idea.
UNICODE is not supported yet, but it's not needed a great work to support it. The compression output is placed in URL valid characters ( that is [0-1][a-z][A-Z] + -) , without need of adding escape secuences.
The test is placed here: http://www.juglar.org/tests/blackpill/CompressionTest.html
I will release a DOJO based API to use the compressor whitout GWT.
Thanks in advance!

Andrés Testi said...

I'm working too in a special feature to optimize compression of JSON trees. ;)

James said...

Andres: it is an interesting idea, but I'm not sure it is worth the tradeoff of having to download the compression JS to both iframes. Since they are are on different domains, they cannot share the code, but I suppose they could both reference the same URL for it. That might help the cost a bit.

I'm also concerned about the cost of doing the compression.

It might be useful for very large data transfers, but I am hesitant to introduce it into this already complicated iframe hack.

But keep up the good work, and feel free to add the utility to Dojo by creating a Trac ticket (use guest/guest account). Dojo requires a CLA before accepting new code and patches, so be sure to fill that out first.

François said...
This comment has been removed by the author.
François said...

I was considering this technique, but did not realize, until tested, that it broke the back button. Since the URL is modified with each comm., 10 comm., for example, between the sites would mean 11 back button clicks to actually get to the previous page!

James said...

François: If I recall correctly, there should be no back button history interference as long as you add the iframe element after the page loads, and if you destroy it when you are down doing the data transfer.

The downside with this approach means creating and destroying the iframe(s) for each IO call. This can lead to leaks, particularly in IE, where it does not seem to clean up things correctly.

Setting the location of the iframe(s) to "javascript:false" then waiting a moment before destroying the iframes may help (someone reported that iframe cleanup suggestion on a Dojo forum, but I have not verified it).

François said...

James: Thanks!

Will try this approach.

Anonymous said...

What is the potential for abuse using this technique?

Unknown said...

here's a potential use of this technique: auto-fitting an iFrame to match the height of the remote content:


Anonymous said...

I notice the remote code hosted inside the iframes for this seem to be down, is there any chance of them coming back anytime soon? I can get the single iframe test to work but not the nested iframe solution, firefox moans about the 'inner iframe' being not allowed to access the 'outer iframe' via parent.location :(

James said...

Steve, thanks for the heads up on the missing pages. There was a server move a while ago and some things did not got transferred. Unfortunately in my lame attempt to restore the files I deleted more. I will get the files replaced in the couple of days.

In the meantime, it should be noted that some modern browsers, like IE8, have new restrictions on cross-frame communication using fragment IDs. It is best to use HTML5's postMessage support for the browsers that support it (Firefox 3+, Safari 3+ and IE8+).

James said...

Steve, turns out I was able to find a backup. I restored the test page, and put a note about considering postMessage for modern browsers.

Anonymous said...

Oh wow James, that's fantastic, thanks for fixing that so speedily! But yes as you say, the dual iframe method still not working in FF3. Luckily in the setup I'm using, I can change the iframe to a script I own on the main window's domain, and its address is then able to be read. If only I'd thought of that last night!

Thanks again!

Sheeba said...

I was trying the example and it worked when parent.location was set with a hardcoded url. But when I try to modify parent.location as

parent.location = parent.location + '#pipe'; its giving me "Access denied" error.

Is there a way to get around this other than passing parent.location to IFrame src url as this could be very long.

Appreciate any help.


James said...

Sheeba: no, there is not a way to use parent.location since that is locked down due to browser security restrictions.

Anonymous said...

I am running ASP web application with nested frames -top ,navigator.The navigator frame has 2 frames right and left. In the left frame I have site navigation menu and clicking link any link on the site navigation should open the respective page in right frame. Everything works fine until I click a link which opens a new window with framesets on another domain. Then if I go to my website and click link ,all the future link clicks open in new window. Appreciate your help

Unknown said...

I was reading your source code and I can't figure out how
function sendPipeMessage(){ parent.location = "http://www.tagneto.org/blogcode/xframe/ui.html#pipeMessage_" + (new Date()).getTime();

is valid. I try to say do alert(parent.location) and i get an access denied. does it work for you since you are hardcoding the URL? I can't seem to access it at all.....

James Burke said...

cisbrane: that is correct, the browser security policies will not allow you to read properties from the other frame. In the parent, you can do the alert there, have it watch the location and if it changes, do the alert.

You can however set the value. But note that there are some related posts to this area (in particular the rules have changed for IE), and I strongly recommend using postMessage instead of this kind of hack now.

Anonymous said...

I am trying to figure something out.
Basically lets say I create an iframe on siteA. On siteA I set the iframe src to siteB. When I goto siteA and it loads the iframe up, siteB does a redirect to siteC. Now on siteA is it possible to grab the siteC url as now the iframe src value has changed to siteC. Or because of cross domain restrictions, if I attempt to read the iframe src address, all that will return is siteB's address.

James Burke said...

Anonymous: because of cross site security policies, you will not be able to read the location of the iframe if it has siteB or siteC in it. You may be able to read the iframe's src attribute, but in practice that is not very useful, it just tells you want you specified in the HTML page, but not what actual page is loaded in the iframe.