iframe诡异的内容消失问题
问题描述
不得不承认,这是一个非常诡异的问题,以下步骤可以重现问题:
- 用IE打开这个测试页面,确认页面上有个iframe,里面显示着abc三个字符。
- 把这页面加进收藏夹。
- 重新打开IE。
- 从收藏夹再打开这页面 。
如果没出问题的话,你会发现页面上虽然还有iframe,但是abc消失了。更准确、详细地说,前后2次的页面主要有以下区别:
- 从视觉上来说,页面中的abc字符消失了。
- 从DOM结构上看,iframe中的body元素内没有任何内容。
- 从iframe的右键-属性上看,第一次页面上iframe的地址是父页面的URL,第二次则变成了about:blank。
以下是这个页面的源码,是从遇上问题的页面中不断分离、简化,最后形成的一个最简的重现方案:
!DOCTYPE html
html
head
meta charset="utf-8" /
titletest/title
/head
body
script type="text/javascript"
var text = 'abc',
script = 'var d = document; ' +
'd.open(\'text/html\', \'replace\'); d.write(parent.text); d.close();',
html = 'iframe id="abc" name="abc" ' +
'src="javascript:void((function() {' + script + '})())"/iframe';
document.write(html);
/script
/body
/html
解决方案
首先,这个页面虽然简单,但其中用到了几个很恶心人的东西:
- document.write。
- javascript:伪协议。
如果有办法避免使用这两者的话,就可以忽略这个问题。但是如果必须使用javascript:伪协议来向iframe中输出内容的话,将以上代码改为如下形式可以解决问题:
var iframe = document.createElement('iframe');
document.body.appendChild(iframe); //插入到需要的位置
iframe.contentWindow.location = 'javascript:void((function() {' + script + '})())';
具体的区别是,从直接使用document.write来输出iframe,变为了使用createElement创建iframe,随后使用iframe的location来采用javascript:伪协议输出具体内容。
起因
由于原本这段代码不是我写的,所以在发现这个问题的时候,我也有过疑问,为啥要这么写呢?难道下面的方式不是更好吗:
var iframe = document.createElement('iframe');
document.body.appendChild(iframe); //插入到需要的位置
iframe.contentWindow.document.write('abc');
然而这一段代码的注释中写是IE在修改了document.domain进行提权后,iframe会出现跨域问题。所以以下代码,其中在IE中是会报错(拒绝访问)的:
//假设当前域是www.tt.com
document.domain = 'tt.com'; //domain提权
var iframe = document.createElement('iframe');
document.body.appendChild(iframe); //插入到需要的位置
iframe.contentWindow.document.write('abc');
至于解决的办法,就是在iframe的src中,使用javascript:伪协议输出内容,当然输出的时候要注意,在iframe的document执行open以后,加上一句代码,把iframe的docuemnt.domain也进行提权,提升到和父页面相同,这样iframe和父页面就是同域的,可以进行交互了。
正是因为IE存在着这样的问题,为了解决这个问题,原有代码中使用了document.write输出带src属性的iframe元素,从而引发了另一个问题……
猜测
那么这个问题是因为什么原因引起的呢?首先对页面的执行过程进行分析,大致是这么几步:
- 解析script标签,执行内容。
- document.write向文档流中输出一个iframe元素。
- iframe中,使用document.write输出文字。
从页面的视觉效果而言,iframe是存在的,但iframe里面的内容消失了。这让人很自然地联想到,第2步已经执行了,但由于浏览器缓存iframe的内容等原因,第3步并没有执行。
为了测试这个情况,比较简单的方法就是在iframe的src里的javascript代码中添加一个断点,我的选择是在d.close()这一句之前,加上了一行代码:alert(d.body.innerHTML);
经过以上的修改,执行的结果是,成功地出现了alert对话框,并且innerHTML确实存在abc字符,另外更奇怪的是,经过alert,abc出现在了iframe中。
也就是说,第3步是确实地执行了,但是在没有alert的情况下,却没有在界面上产生任何效果。综合以上的原因,联想到alert函数的一个作用是将浏览器的UI Update队列进行flush操作,因此对于完全黑盒的IE浏览器,现阶段只能猜测,在这种特殊的使用方式之下,IE丢失了UI Update队列中的部分更新,导致iframe的document并没有得到更新,因此也保留了about:blank这种地址。