iframe 解析
基本概念
先来看个例子:
<iframe src="demo.html" height="300" width="500" name="demo" scrolling="auto" sandbox="allow-same-origin"></iframe>
iframe 常用属性:
-
frameborder: 是否显示边框,1(yes),0(no)
-
height: 框架作为一个普通元素的高度,建议在使用 css 设置。
-
width: 框架作为一个普通元素的宽度,建议使用 css 设置。
-
name: 框架的名称,
window.frames[name
]时专用的属性。 -
scrolling: 框架的是否滚动。yes,no,auto。
-
src: 内框架的地址,可以使页面地址,也可以是图片的地址。
-
srcdoc: 用来替代原来 HTML body 里面的内容。但是 IE 不支持, 不过也没什么卵用
-
sandbox: 对 iframe 进行一些列限制,IE10+支持
我们通常使用 iframe 最基本的特性,就是能自由操作 iframe 和父框架的内容(DOM). 但前提条件是同域. 如果跨域顶多只能实现页面跳转 window.location.href = xxx。
A:<iframe id="mainIframe" name="mainIframe" src="/main.html" frameborder="0" scrolling="auto" ></iframe>
B:<iframe id="mainIframe" name="mainIframe" src="http://www.baidu.com" frameborder="0" scrolling="auto" ></iframe>
使用 A 时,因为同域,父页面可以对子页面进行改写,反之亦然。
使用 B 时,不同域,父页面没有权限改动子页面,但可以实现页面的跳转这里,我们先从简单的开始,当主页面和 iframe 同域时,我们可以干些什么。
获取 iframe 里的内容
主要的两个 API 就是 contentWindow 和 contentDocument:
iframe.contentWindow, 获取 iframe 的 window 对象。iframe.contentDocument, 获取 iframe 的 document 对象。这两个 API 只是 DOM 节点提供的方式(即 getELement 系列对象)。
var iframe = document.getElementById("iframe1");
var iwindow = iframe.contentWindow;
var idoc = iwindow.document/iframe.contentDocument;
console.log("window",iwindow);//获取iframe的window对象
console.log("document",idoc); //获取iframe的document
console.log("html",idoc.documentElement);//获取iframe的html
console.log("head",idoc.head); //获取head
console.log("body",idoc.body); //获取body
另外更简单的方式是,结合 Name 属性,通过 window 提供的 frames 获取。
<iframe src ="/index.html" id="ifr1" name="ifr1" scrolling="yes">
<p>Your browser does not support iframes.</p>
</iframe>
<script type="text/javascript">
console.log(window.frames['ifr1'] == document.getElementById("ifr1").contentWindow);
// true
</script>
其实 window.frames['ifr1']
返回的就是 window 对象,这里就看你想用哪一种方式获取 window 对象,两者都行,不过本人更倾向于第二种使用 frames[xxx].
因为,字母少啊喂~ 然后,你就可以操控 iframe 里面的 DOM 内容。
在 iframe 中获取父级内容
同理,在同域下,父页面可以获取子 iframe 的内容,那么子 iframe 同样也能操作父页面内容。在 iframe 中,可以通过在 window 上挂载的几个 API 进行获取.
window.parent 获取上一级的window对象,如果还是iframe则是该iframe的window对象
window.top 获取最顶级容器的window对象,即,就是你打开页面的文档
window.self 返回自身window的引用。可以理解 window===window.self
使用 iframe 进行异步请求
话说在很久很久以前,我们实现异步发送请求是使用 iframe 实现的~! 怎么可能!!!
真的史料为证(自行 google), 那时候为了不跳转页面,提交表单时是使用 iframe 提交的。现在,前端发展尼玛真快,websocket,SSE,ajax 等,逆天 skill 的出现,颠覆了 iframe, 现在基本上只能活在 IE8,9 的浏览器内了。 但是,宝宝以为这样就可以不用了解 iframe 了,而现实就是这么残酷,我们目前还需要兼容 IE8+。所以,iframe 实现长轮询和长连接的 trick 我们还是需要涉猎滴。
iframe 提交表单
const requestPost = ({url, data}) => {
// 首先创建一个用来发送数据的iframe.
const iframe = document.createElement('iframe')
iframe.name = 'iframePost'
iframe.style.display = 'none'
document.body.appendChild(iframe)
const form = document.createElement('form')
const node = document.createElement('input')
// 注册iframe的load事件处理程序,如果你需要在响应返回时执行一些操作的话.
iframe.addEventListener('load', function () {
// 这里可以这样获取返回内容
console.log(iframe.contentDocument.body.innerText)
})
form.action = url
// 在指定的iframe中执行form
form.target = iframe.name
form.method = 'post'
for (let name in data) {
node.name = name
node.value = data[name].toString()
form.appendChild(node.cloneNode())
}
// 表单元素需要添加到主文档中.
form.style.display = 'none'
document.body.appendChild(form)
form.submit()
// 表单提交后,就可以删除这个表单,不影响下次的数据发送.
document.body.removeChild(form)
}
// 使用方式
requestPost({
url: 'http://localhost:9871/api/iframePost',
data: {
msg: 'helloIframePost'
}
})
iframe 长轮询
如果写过 ajax 的童鞋,应该知道,长轮询就是在 ajax 的 readyState = 4 的时,再次执行原函数即可。 这里使用 iframe 也是一样,异步创建 iframe,然后 reload, 和后台协商好, 看后台哥哥们将返回的信息放在哪,然后获取里面信息即可. 这里是直接放在 body 里.
var iframeCon = docuemnt.querySelector('#container'),
text; //传递的信息
var iframe = document.createElement('iframe'),
iframe.id = "frame",
iframe.style = "display:none;",
iframe.name="polling",
iframe.src="target.html";
iframeCon.appendChild(iframe);
iframe.onload= function(){
var iloc = iframe.contentWindow.location,
idoc = iframe.contentDocument;
setTimeout(function(){
text = idoc.getElementsByTagName('body')[0].textContent;
console.log(text);
iloc.reload(); //刷新页面,再次获取信息,并且会触发onload函数
},2000);
}
这样就可以实现 ajax 的长轮询的效果。 当然,这里只是使用 reload 进行获取,你也可以添加 iframe 和删除 iframe 的方式,进行发送信息,这些都是根据具体场景应用的。另外在 iframe 中还可以实现异步加载 js 文件,不过,iframe 和主页是共享连接池的,所以还是很蛋疼的,现在基本上都被 XHR 和 hard calllback 取缔了,这里也不过多介绍了。
iframe 跨域
iframe 就是一个隔离沙盒,相当于我们在一个页面内可以操控很多个标签页一样。有了解过的童鞋应该知道,iframe 解决跨域也是很有一套的。
iframe 跨域通讯之 document.domain
对于主域相同子域不同的两个页面,我们可以通过 document.domain + iframe 来解决跨域通信问题。举个 🌰,http: //www.foo.com/a.html
和 http: //script.foo.com/b.html
两个文件中分别加上 document.domain = ‘foo.com’
,指定相同的主域,然后,两个文档就可以进行交互。
//b.html是以iframe的形式嵌套在a.html中
//www.foo.com上的a.html
document.domain = 'foo.com';
var ifr = document.createElement('iframe');
ifr.src = 'http://script.foo.com/b.html';
ifr.style.display = 'none';
document.body.appendChild(ifr);
ifr.onload = function(){
var doc = ifr.contentDocument || ifr.contentWindow.document;
// 在这里操纵b.html
alert(doc.getElementsByTagName("h1")[0].childNodes[0].nodeValue);
};
//script.foo.com上的b.html
document.domain = 'foo.com';
默认情况下 document.domain 是指 window.location.hostname. 你可以手动更改,但是最多只能设置为主域名。 通常,主域名就是指不带 www 的 hostname, 比如: foo.com
, baidu.com
。 如果,带上 www 或者其他的前缀,就是二级域名或者多级域名。通过上述设置,相同的 domain 之后,就可以进行同域的相关操作。另外还可以使用 iframe 和 location.hash,不过由于技术 out 了,这里就不做介绍了。
iframe 跨域通讯之 postMessage
如果你设置的 iframe 的域名和你 top window 的域名完全不同。 则可以使用 CDM(cross document messaging)进行跨域消息的传递。该 API 的兼容性较好 ie8+都支持.
<iframe src="http://tuhao.com" name="sendMessage"></iframe>
//当前脚本
let ifr = window.frames['sendMessage'];
//使用iframe的window向iframe发送message。
ifr.postmessage('give u a message', "http://tuhao.com");
//tuhao.com的脚本
window.addEventListener('message', receiver, false);
function receiver(e) {
if (e.origin == 'http://tuhao.com') {
if (e.data == 'give u a message') {
e.source.postMessage('received', e.origin); //向原网页返回信息
} else {
alert(e.data);
}
}
}
当 targetOrigin 接受到 message 消息之后,会触发 message 事件。 message 提供的 event 对象上有 3 个重要的属性:data、origin、source。
- data:postMessage 传递进来的值
- origin:发送消息的文档所在的域
- source:发送消息文档的 window 对象的代理,如果是来自同一个域,则该对象就是 window,可以使用其所有方法,如果是不同的域,则 window 只能调用 postMessage()方法返回信息
iframe 的安全问题
iframe 出现安全性有两个方面,一个是你的网页被别人 iframe,一个是你 iframe 别人的网页。 当你的网页被别人 iframe 时,比较蛋疼的是被钓鱼网站利用,然后 victim+s 留言逼逼你。所以为了防止页面被一些不法分子利用,我们需要做好安全性措施。
防嵌套网页
在前端领域,为了防止网站被钓鱼,可以使用 window.top 来防止你的网页被 iframe。
if(window != window.top){
window.top.location.href = correctURL;
}
这段代码的主要用途是限定你的网页不能嵌套在任意网页内。如果你想引用同域的框架的话,可以判断域名。
if (top.location.host != window.location.host) {
top.location.href = window.location.href;
}
当然,如果你网页不同域名的话,上述就会报错。所以,这里可以使用 try…catch…进行错误捕获。如果发生错误,则说明不同域,表示你的页面被盗用了。可能有些浏览器这样写是不会报错,所以需要降级处理。这时候再进行跳转即可.
try{
top.location.hostname; //检测是否出错
//如果没有出错,则降级处理
if (top.location.hostname != window.location.hostname) {
top.location.href =window.location.href;
}
}
catch(e){
top.location.href = window.location.href;
}
这只是浏览器端,对 iframe 页面的权限做出相关的设置。 我们还可以在服务器上,对使用 iframe 的权限进行设置.
X-Frame-Options
X-Frame-Options 是一个响应头,主要是描述服务器的网页资源的 iframe 权限。目前的支持度是 IE8+(已经很好了啊喂)有 3 个选项:
-
X-Frame-Options: DENY 拒绝任何 iframe 的嵌套请求
-
X-Frame-Options: SAMEORIGIN 只允许同源请求,例如网页为
foo.com/123.php
,則foo.com
底下的所有网页可以嵌入此网页,但是foo.com
以外的网页不能嵌入 -
X-Frame-Options: ALLOW-FROM
http://s3131212.com
只允许指定网页的 iframe 请求,不过兼容性较差 Chrome 不支持
X-Frame-Options 其实就是将前端 js 对 iframe 的把控交给服务器来进行处理。
//js
if(window != window.top){
window.top.location.href = window.location.href;
}
//等价于
X-Frame-Options: DENY
//js
if (top.location.hostname != window.location.hostname) {
top.location.href =window.location.href;
}
//等价于
X-Frame-Options: SAMEORIGIN
该属性是对页面的 iframe 进行一个主要限制,不过,涉及 iframe 的 header 可不止这一个,另外还有一个 Content Security Policy, 他同样也可以对 iframe 进行限制,而且,他应该是以后网页安全防护的主流。
CSP 之页面防护
内容安全策略(CSP)用于检测和减轻用于 Web 站点的特定类型的攻击,例如 XSS 和数据注入等。
通过 CSP 配置 sandbox 和 child-src 可以设置 iframe 的有效地址,它限制适 iframe 的行为,包括阻止弹出窗口,防止插件和脚本的执行,而且可以执行一个同源策略。
我们可以在 html 头部中加上<meta>
标签
<meta http-equiv="Content-Security-Policy" content="child-src 'unsafe-inline' 'unsafe-eval' www.easonwong.com">
或者通过 HTTP 头部信息加上 Content-Security-Policy 字段
具体这里不细说,详情见我的另一片关于 csp 的文章。
sandbox
sandbox 就是用来给指定 iframe 设置一个沙盒模型限制 iframe 的更多权限. sandbox 是 h5 的一个新属性,IE10+支持(md~). 启用方式就是使用 sandbox 属性:
<iframe sandbox src="..."></iframe>
这样会对 iframe 页面进行一系列的限制:
1. script脚本不能执行
2. 不能发送ajax请求
3. 不能使用本地存储,即localStorage,cookie等
4. 不能创建新的弹窗和window
5. 不能发送表单
6. 不能加载额外插件比如flash等
看到这里,我也是醉了。好好的一个 iframe,你这样是不是有点过分了。不过,你可以放宽一点权限。在 sandbox 里面进行一些简单设置
<iframe sandbox="allow-same-origin" src="..."></iframe>
常用的配置项有:
配置 | 效果 |
---|---|
allow-forms | 允许进行提交表单 |
allow-scripts | 运行执行脚本 |
allow-same-origin | 允许同域请求,比如 ajax,storage |
allow-top-navigation | 允许 iframe 能够主导 window.top 进行页面跳转 |
allow-popups | 允许 iframe 中弹出新窗口,比如,window.open,target=”_blank” |
allow-pointer-lock | 在 iframe 中可以锁定鼠标,主要和鼠标锁定有关 |
可以通过在 sandbox 里,添加允许进行的权限.
<iframe sandbox="allow-forms allow-same-origin allow-scripts" src="..."></iframe>
这样,就可以保证 js 脚本的执行,但是禁止 iframe 里的 javascript 执行 top.location = self.location。
哎,其实,iframe 的安全问题还是超级有的。比如 location 劫持,Refers 检查等。 不过目前而言,知道怎么简单的做一些安全措施就 over 了,白帽子们会帮我们测试的。