iframe内嵌及跨域通信(iframe跨域 内嵌网页 iframe刷新重载 postMessage 事件监听)
iframe内嵌及跨域通信
iframe跨域 内嵌网页 iframe刷新重载 postMessage 事件监听
前言
对于iframe标签,现在都应该用的很少了因为它存在一些问题,比如安全问题或者能耗高,但最近笔者就使用了它做网页内嵌并跨域处理了数据,所以记录记录。
iframe基本概念
<iframe src="demo.html" height="300" width="500" name="demo" scrolling="auto" sandbox="allow-same-origin"></iframe>
iframe的一些基本属性:
src iframe页面地址,有同域跨域之分
height iframe高度
width iframe宽度
name iframe命名,可通过window.frames[xxx]被调用
scrolling iframe滚动模式
sandbox html5新特性,用于限制iframe的功能
使用iframe的正确姿势
可以通过以下 选择器来获取iframe节点(window.frames['xxx']的方式好像已经不能用了):
1 document.getElementById('iframeId') 2 3 document.getElementsByName('iframeName') 4 5 document.getElementsByClassName('iframeClassName') 6 7 document.getElementsByTagName('iframe') 8 9 document.querySelector('#iframeId') 10 11 document.querySelector('.iframeClassName')
我们可以通过contentWindow和contentDocument两个API获取iframe的window对象和document对象。
1 let iwindow = iframe.contentWindow; // 获取iframe的window对象 2 3 let idoc = iframe.contentDocument; // 获取iframe的document对象
iframe使用父级内容的正确姿势
我们通过window.self,window.parent,window.top这三个属性分别获取自身window对象,父级window对象,顶级window对象。
看图说话
所以:
iframe1.self === iframe1
iframe1.parent === iframe2
iframe2.parent === window
iframe1.top === window
同域/跨域
什么是同域什么跨域咧?同域跨域的区别在哪咧?我们一般会使用iframe来进行父子页面的通信,但父子页面是否同域决定了它们之间能否进行通信。
js遵循同源策略,即协议域名端口一致,否则都算跨域。
同源策略 是由Netscape提出的一个著名的安全策略,现在所有支持JavaScript 的浏览器都会使用这个策略。实际上,这种策略只是一个规范,并不是强制要求,各大厂商的浏览器只是针对同源策略的一种实现。它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。
跨域 简单的来说,指的是两个资源非同源。出于安全方面的考虑,页面中的JavaScript在请求非同源的资源时就会出 跨域问题 ——即跨域请求,这时,由于同源策略,我们的请求会被浏览器禁止。也就出现了 我们常说的 跨域 问题。
通过这个图可以进一步帮助我们理解同域和跨域。
iframe跨域通讯之document.domain
对于主域相同子域不同的两个页面,我们可以通过document.domain + iframe来解决跨域通信问题。
举个🌰,网页a(http://www.easonwong.com)和网页b(http://script.easonwong.com),两者都设置document.domain = 'easonwong.com'(这样浏览器就会认为它们处于同一个域下),然后网页a再创建iframe上网页b,就可以进行通信啦!
网页a
1 document.domain = 'easonwong.com'; 2 3 var ifr = document.createElement('iframe'); 4 5 ifr.src = 'http://script.easonwong.com'; 6 7 ifr.style.display = 'none'; 8 9 document.body.appendChild(ifr); 10 11 ifr.onload = function(){ 12 13 let doc = ifr.contentDocument || ifr.contentWindow.document; 14 15 // 在这里操纵b.html 16 17 }; 18 19
网页b
document.domain = 'easonwong.com';
iframe跨域通讯之postMessage
postMessage是html5的新特性,具体介绍看传送门。
postMessage介绍
兼容性 IE8以上
我们可以通过html5这个新特性进行iframe间的跨域通信,使用postMessage进行数据传递,通过Message监听通信事件。举个🌰
网页a
1 document.domain = 'easonwong.com'; 2 3 var ifr = document.createElement('iframe'); 4 5 ifr.src = 'http://script.easonwong.com'; 6 7 ifr.style.display = 'none'; 8 9 document.body.appendChild(ifr); 10 11 // 发送数据 12 13 ifr.postmessage('hello, I`m a', 'http://script.easonwong.com');
网页b
1 // 监听message事件 2 3 window.addEventListener('message', receiver, false); 4 5 function receiver(e) { 6 7 if (e.origin == 'http://www.easonwong.com') { 8 9 if (e.data == 'hello, I`m a') { 10 11 e.source.postMessage('hello, I`m b', e.origin);信息 12 13 } 14 15 } 16 17 }
iframe的安全问题
iframe小广告
很让我们讨厌iframe的一点,就是很多网站都会有各种让人防不胜防的小广告,它们大多就是用通过iframe实现的,本来想点击某个播放按钮,结果直接跳到不知道去了哪个新世界去了。
所以我们一定要注意在用iframe的同时,要防止我们被iframe了。
防嵌套页面操作
在前端领域,我们可以通过window.top来防止我们页面被嵌套。
if(window != window.top){
window.top.location.href = myURL;
}
或者通过window.location.host来检测是否跨域了
if (top.location.host != window.location.host) {
top.location.href = window.location.href;
}
而后端也可以做对应的防范措施,通过设置X-Frame-Options响应头来确保自己网站的内容没有被嵌到别人的网站中去,也从而避免了点击劫持 (clickjacking) 的攻击。
CSP
内容安全策略(CSP)用于检测和减轻用于 Web 站点的特定类型的攻击,例如 XSS 和数据注入等。
MDN CSP
通过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字段
现在回头来看看业务上的使用:
逻辑上简单来说就是,用一个iframe标签从a网页跳到另一个b网页,并且在b页面上操作后将数据拿回a网页,效果代码如下:
HTML:
1 <el-dialog title="选址" 2 3 width = "70%" 4 5 height = '500px' 6 7 :visible.sync="addrSelectDialogVisible" 8 9 :before-close="handleCloseAddrSelectDialog" 10 11 :modal-append-to-body="false"> 12 13 <div class="addr-select-box"> 14 15 <!-- start of 详情--> 16 17 <iframe 18 19 src="http://134.96.249.135:8088/ass/webLogin.do?areaCode=571" frameborder="0" width="100%" height="100%" ref="iframeId"> 20 21 </iframe> 22 23 <!-- end of 详情--> 24 25 </div> 26 27 </el-dialog>
1 //注册监听选址的消息 2 3 registerSelectAddrMessage( ){ 4 5 let self = this ; 6 7 if (navigator.appName=="Microsoft Internet Explorer"&&(navigator.appVersion.match(/8./i)=="8."||navigator.userAgent.indexOf("MSIE 8.0")>0||navigator.appVersion.match(/7./i)=="7.")){ 8 9 window.attachEvent('onmessage',function(e){ self.handleSelectAddrMessage( e.data );}); 10 11 } 12 13 else { 14 15 window.addEventListener('message',function(e){ self.handleSelectAddrMessage( e.data );},false); 16 17 } 18 19 }, 20 21 //监听选址信息 22 23 handleSelectAddrMessage( addrMessageData ){ 24 25 let self = this ; 26 27 //转换为标准的JSON字符串 28 29 let addrMsgObj = {}; 30 31 if( typeof addrMessageData === 'string' && addrMessageData.indexOf( 'c3Code' ) > 0 ){ 32 33 var reg = /({\S*?:)|(,\S*?:)/g 34 35 var formatedJSONStr = addrMessageData.replace( reg , function( a , m1 , m2 ){ 36 37 if( m1 ){ //如果第一个分组匹配上 38 39 return '{"' + a.slice( 1 , -1 ) + '":'; 40 41 } 42 43 else{ 44 45 return ',"' + a.slice( 1 , -1 ) + '":'; 46 47 } 48 49 } ); 50 51 formatedJSONStr = formatedJSONStr.replace( /\'/g , '"' ); 52 53 let addrMsgObj = {}; 54 55 addrMsgObj = JSON.parse( formatedJSONStr ); 56 57 if( addrMsgObj.c3Code ){ 58 59 self.formReviewList[self.listIndex].resourceC3 = self.cityCodeMap[ addrMsgObj.c3Code ] ; 60 61 self.formReviewList[self.listIndex].resourceC4 = addrMsgObj.c4name ; 62 63 self.formReviewList[self.listIndex].resourceAddress = addrMsgObj.address ; 64 65 self.formReviewList[self.listIndex].resourceAddressId = addrMsgObj.addressId ; 66 67 self.formReviewList[self.listIndex].resourcePort = addrMsgObj.epon + ',' + addrMsgObj.gpon + ',' + addrMsgObj.exchName ; 68 69 //关闭选址的对话框 70 71 self.addrSelectDialogVisible = false ; 72 73 self.listIndex = '' ; 74 75 } 76 77 } 78 79 }, 80 81 82 83 created(){ 84 85 let self = this; 86 87 self.getWorkOrderList(); 88 89 //监听选址的事件响应 90 91 self.registerSelectAddrMessage(); 92 93 },
为了每次进入时都是新的b页面,做个重载,第一种方式没行通,用了第二种。
重载:
if( self.$refs.iframeId ){
// 第一种重载必须同域
// self.$refs.iframeId.contentWindow.location.reload( true );
// 第二种每次赋值路径,可同域可跨域
self.$refs.iframeId.src = 'http://134.96.249.135:8088/ass/webLogin.do?areaCode=571'
};
效果:
所以在代码里做了监听** ★,°:.☆( ̄▽ ̄)/$:.°★ **。
再举个小栗子
father.html通过iframe包含了son.html
father.html
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8" /> 5 <script type="text/javascript"> 6 function say(){ 7 alert("这是father的say()"); 8 } 9 function callChild(){ 10 myFrame.window.say(); 11 myFrame.window.document.getElementById("button").value="调用结束" 12 } 13 </script> 14 </head> 15 <body> 16 <input id="button" type="button" value="调用son.html中的函数say()" onclick="callChild()"/> 17 <iframe name="myFrame" src="son.html"></iframe> 18 </body> 19 </html>
son.html
1 <html> 2 <head> 3 <meta charset="utf-8" /> 4 <script type="text/javascript"> 5 function say() { 6 alert("这是son的say()"); 7 } 8 function callParent() { 9 parent.say(); 10 parent.window.document.getElementById("button").value = "调用结束"; 11 } 12 </script> 13 </head> 14 <body> 15 <input id="button" type="button" value="调用father.html中的say()函数" onclick="callParent()" /> 16 </body> 17 </html>
方法是如何调用的?获取子页面或父页面的window对象,在通过对象调用方法。
vue文件里的写法:
在方法调用前,以下点必须要注意!!!