Tuesday, October 24, 2006

IE 7 and IFrame APIs Part 2

Microsoft was kind enough to look into the issue, and here is more information.

The "Navigate sub-frames across different domains" preference is designed to address this security report:

IE 7 has shipped with automatically turning this preference on.

There is a workaround to the issue: the two iframes that do the communication must both have a parent frame from their domain loaded.

So, the original setup I use in the XHR IFrame Proxy situation looks like the following. The dashes indicate the level of nestedness (Top Window contains IFrame 1, IFrame 1 contains IFrame 2):

Top window (Domain A)
--IFrame 1 (Domain A)
----IFrame 2 (Domain B)

Where IFrame 1 and IFrame 2 communicate by setting each other's fragment identifiers (location.hash in JavaScript). When the communication is done, IFrame 1 tells the top window what the result is.

What broke was IFrame 2 (on Domain B) setting IFrame 1's location (via parent.location).

The following iframe structure works in IE7:

Top window (Domain A)
-- IFrame W (Domain B)
---- IFrame 1 (Domain A)
------ IFrame 2 (Domain B)

Notice the introduction of IFrame W. Now, apparently since IFrame W (on Domain B) owns IFrame 1, it is OK for IFrame 2 to change the location of IFrame 1.

Here is a test of this structure (thanks in large part of Microsoft).

This test uses something I personally have not done before: using window.open() to grab named iframes. Interesting technique. I'm curious to see if it is required when I patch IFrame XHR for IE 7.

So, the end result is that *another* IFrame is needed to accomplish the task. I'll be looking at modifying XHR IFrame Proxy code to use this extra frame, but only for IE 7. The down side is that now, in addition to the remote server placing xip_server.html on their server, they will have to place another HTML file too (the file for IFrame W). If the code bloat is not too much, I might consider making xip_server.html smart enough to act as IFrame W content as well as being the content for IFrame 2.

So, while it works, I still ask Microsoft to consider the following:

1) It seems like this change to address the security issue (http://secunia.com/advisories/11978/) seems like it should not affect setting parent/child frames. Firefox seems to have addressed the security issue without breaking subframe communication. I know that is not entirely a fair comparison, but it seems like the code change in IE to protect against a specific issue has too broad of an effect.

2) Even with this preference on, allow changing of fragment identifiers. It is a non-destructive change to the parent frame, and the parent frame can choose to ignore it. It is also a very solid, useful tool for cross-domain communication that works for today's browsers.
If you want to get fancy, consider supporting a security setting on the iframe, that allows Domain A to say it trusts content from Domain B to change its location. Ideally this sort of configuration of an iframe would be some sort of standardized access control scheme that other browser makers could implement if they so desired.


Troy said...

There is a workaround if the hosting page and IFRAME know each other's exact URL. Instead of using location.hash just set location directly. This example seems to work fine. Any issues you can see aside from having to know the exact URLs?


<button onclick="say()">Say Something</button> <p>Child IFRAME last said : <span id="output"></span></p>

var childUrl = "http://b/child.html";

document.write('<iframe id="child" src="' + childUrl + '"></iframe>');

function say() {
document.getElementById("child").src = childUrl + "#" + Math.floor(1000 * Math.random()) }

function() { document.getElementById("output").innerHTML = decodeURIComponent( location.hash.substring(1)); },


<button onclick="say()">Say Something</button>
<p>Parent last said : <span id="output"></span></p>

<script type="text/javascript">
var parentUrl = "http://a/parent.html";

function say() {
parent.location = parentUrl + "#" + encodeURIComponent(new Date());

function() { document.getElementById("output").innerHTML = location.hash; },


troy said...

Ok yeah, if I had read your earlier posting I'd see that you already showed this example. Apologies.

Justin said...

Howdy. I would *really* love to get this solution working -- we're trying to do a cross-site mashup that could use the functionality.

My problem is, the example as shown doesn't work for me, in either IE6 or 7. Specifically, it's failing in the window.open() call -- IE6 is claiming "Access Denied" when it tries to do this, presumably because it's trying to access a frame that is owned by a different domain.

Am I missing something? Or did MS screw us over in some subsequent security patch, by restricting this particular mode of access? Any ideas you can provide would be greatly welcomed...

James said...

Justin: I do see an "Access Denied" message now for IE 6 for this test page. I'm using Windows XP SP 2 with IE 6 version:

However, that test page still works for me in IE 7 on Windows XP SP 2. IE 7 version number:

I'll dig into more later, and I'll post an update when I get more info.

Justin said...

Hmm. Okay, I clearly misinterpreted something before. You're correct that it's working in IE7, although it isn't doing so in IE6. Which means that I have a plausible chance of a workaround, albeit a complicated one.

Thanks! This may well save our hash. (And if you figure out what's going on with that IE6 error, I'd love to know...)

Justin said...

FYI: I've managed to get it working in all my prime target browers (IE6 and 7 and Firefox) by switching the way the listener works. To get around the problems of ui2_workaround trying to get at uihidden, I've instead put the timer/listener into uihidden, and that is poking the values into ui2_workaround by using parent.parent.document. Clumsy, but seems to work.

Thanks for the help! I'd almost given up hope on this problem...

Puneet said...

We are also facing the problem of cross domain communication. We tried your soultion :
Top window (Domain A)
-- IFrame W (Domain B)
---- IFrame 1 (Domain A)
------ IFrame 2 (Domain B)

This works fine. But we have iframe architecture like :
Top window (Domain A)
--- IFrame 1 (Domain A)
--- IFrame 2 (Domain B)
Which does not work in IE 7.0 but when we changed this to following architecture then also it did not work.
Top window (Domain A)
-- IFrame W (Domain B)
---- IFrame 1 (Domain A)
---- IFrame 2 (Domain B)

Is it necessary that IFrame2 needs to be child of IFrame1 to support cross domain communication.

sky said...

You've got a Safari bug, which you can fix by replacing.


Without this change the top window can send messages, but doesn't receive them.

James said...

sky: Thanks for the Safari catch. I haven't updated the code, since I recommend using the simpler frame structure for Safari as shown on the simpler test page that works in all browsers except MSIE 6. I would only use the convoluted iframe structure described in this blog post for MSIE 7.

James said...

A follow-up to Justin's comment about the page listed in the blog entry not working with IE 6 -- I recommend using the simpler iframe setup that works for all browsers except IE 7.

Anonymous said...

I'm using a shopping cart that posts to a https page on another server for entering the Credit card info. Has anyone herre been successfull loading an https page into an iframe on an http page using this method? Any posts or examples you can send me to would be helpfull.

an_triskell said...

I have the same problem with payment by credit card.
Can you help me ?

oren said...

I have just tried the test page in two browsers under Windows XP SP2.

In Firefox, "Send UI Message" works but only the last message is seen in the inner frame with total messages always equals one.

In IE 7.0.5730.11, "Send UI Message" doesn't work at all.

In both cases "Send Pipe Message" seems to work, which is encouraging, as far as it goes...

"Once more into the breach", then...