记一次与iframe之间的抗争
iframe这个标签之前了解过这个东西,知道它可以引入外来的网页,但是实际开发中没有用到过。这一次有一个需求是说准备要在网页中嵌套另外一个网站,用iframe这个标签,让我测试一下这个可不可以在自己的网页中对引入进来的iframe框架进行操作,操作dom和css的一些东西。让我做出一个小案例看看可不可以,我信誓旦旦保证说可以的,我试过!!!
就这样交代给我之后信心满满的就开始了我的验证。
什么是同源?
同域名、 同端口、 同协议
网上是有好多这个的解释的,给出一张图片。 看下面这张图片。 引用来自 浏览器的同源策略
我直接新建了一个文件夹,在里面写了两个html页面的文件,举例说明是a.html和b.html,然后让其中的一个a.html文件中用iframe标签的src去引入b.html文件,在里面去互相操作他们的css样式和DOM元素。
a.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
html,body{
height: 100%;
}
body{
background: pink;
}
#iframe1{
width: 400px;
height: 400px;
margin: auto;
background: blue;
}
</style>
</head>
<body> 这里是父文档
<input type="button" id="btn1" value="改变子文档的颜色">
<input type="button" id="btn2" value="删除span1">
<input type="button" id="btn3" value="改变span2的颜色"> <input type="button" id="btn4" value="修改子文档中的link标签">
<br />
<br />
<hr />
<iframe id="iframe1" src="b.html" frameborder="0"> </iframe> <script>
// 只有同服务器下 同域名下才可以操作 不能更改别人的网页。。
var oBtn1 = document.getElementById('btn1');
var oIframe1 = document.getElementById('iframe1'); function fn(){
document.body.style.background = 'green';
} oBtn1.onclick = function () {
console.log(oIframe1.contentWindow); // ---这个东西是子文档中的window对象
console.log(oIframe1.contentDocument); // ---- 这个东西是子文档中的document对象
oIframe1.contentWindow.document.body.style.background = 'yellow'; }; btn2.onclick = function () {
var span1 =oIframe1.contentWindow.document.querySelector('.span1');
console.log(span1);
span1.parentNode.removeChild(span1);
}; btn3.onclick = function () {
var span2 =oIframe1.contentWindow.document.querySelector('.span2');
span2.style.color = 'red';
};
btn4.onclick = function () {
var iFrameWindow = oIframe1.contentWindow;
console.log(iFrameWindow.document.getElementsByTagName('link'));
var link0 = iFrameWindow.document.getElementsByTagName('link')[0];
console.log(link0.parentNode.removeChild(link0));
} </script> </body>
</html>
b.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
body{
background: yellowgreen;
}
</style>
<link rel="stylesheet" href="1.css">
<link rel="stylesheet" href="2.css">
</head>
<body>
<h1 id="h1">这里是子文档1</h1>
<span class="span1">span1标签</span> <br>
<br>
<span class="span2">span2标签</span>
<span class="span3">span3标签</span>
<br />
<hr />
<input type="button" id="btn1" value="改变父文档的颜色"> <script>
var oH1 = document.getElementById('h1');
var oBtn1 = document.getElementById('btn1');
oH1.onclick = function () {
alert('子文档中的点击事件,我可以改变父文档');
console.log(window.parent); // -----这个parent对象是父文档中的 window对象 }; oBtn1.onclick = function () {
(function (window,document) {
document.body.style.background = 'skyblue';
})(window.parent, window.parent.document);
}; </script>
</body>
</html>
样式如下
上面的两个代码中用到了一个东西,在a.html文件中 用到了iframe标签元素的 .contentWindow 和 .contentDocument 这两个东西,它们两个分别是子文档也就是b.html中的window对象和document对象,那么你说知道了这两个东西要去操作它里面的东西还不简单吗。
在 b.html文件中的 window.parent 这个东西是a.html的window对象,那么它同样也可以去操作a.html中的元素了。所以交给我的任务我感觉完成了,就去问他,这样可以。然后我给他看了一下这个东西,后来了解到这两个不是同一个域名下的,这两个网站不是在一起的,然后我就回来又来调试。
不同端口下的调试
我经常用的编辑器是webstrom,它这个东西会自启动一个 127.0.0.1:63342的端口,我又用node做了一个简单的监听 3000端口的服务器,在网页上面打开了。
还是同样的代码吧,只不过把ifreme上面的src改为了我3000端口的网页。
但是这次浏览器给了我一个惊喜,因为我感觉吧只有后端才会存在跨域什么的问题,没有想过前端的这些东西。
它的打印出来的window对象都变了,好多都是false了,和之前在同一个页面下面的东西都不一样了~~
因为一看到 origin cross-origin就感觉是跨域的那种问题。
得了吧,去百度,google查到底怎么办吧。我一直相信以我现在的水平遇到的问题其他的人同样也有人会遇到过。
这一查不要紧,感觉看得好多文章开辟除了新的天地,真的是,在文章底部会给出参考文章,昨天我只看到了一种解决方案,并且将它付诸于实践了,但是由于想要搞明白今天又找到了几种解决方案,但是并没有去试验。
还是要讲一讲同源对哪些行为有限制?
随着互联网的发展,同源策略 越来越严格。目前,如果非同源,共有三种行为受到限制。
1. Cookie、localStorage和 IndexDB无法读取
2. DOM无法获得
3. AJAX 请求不能发送
虽然这些限制是必要的,但是有时很不方便,合理的用途也会受到影响。
这个问题难道就没有办法解决了吗?有的
Cookie解决方法
Cookie 是服务器写入浏览器的一小段信息,只有同源的网页才能共享。但是,两个网页一级域名相同,只是二级域名不同,浏览器允许通过设置document.domain
共享 Cookie。
举例来说,A网页是http://w1.example.com/a.html
,B网页是http://w2.example.com/b.html
,那么只要设置相同的document.domain
,两个网页就可以共享Cookie。
document.domain = 'example.com';
现在,A网页通过脚本设置一个 Cookie。
document.cookie = "test1=hello";
B网页就可以读到这个 Cookie。
var allCookie = document.cookie;
注意,这种方法只适用于 Cookie 和 iframe 窗口,LocalStorage 和 IndexDB 无法通过这种方法,规避同源政策,而要使用下文介绍的PostMessage API。
另外,服务器也可以在设置Cookie的时候,指定Cookie的所属域名为一级域名,比如.example.com。
Set-Cookie: key=value; domain=.example.com; path=/
这样的话,二级域名和三级域名不用做任何设置,都可以读取这个Cookie。
上面这种方法暂时还没有去试验,等试验过后再来修改一下这里,因为自己都不知道行不行。
iframe
如果两个网页不同源,就无法拿到对方的DOM,上面的第二个例子我已经去试验过了,也看到报错信息了。
就是父窗口运行下面的命令,如果iframe窗口不是同源,就会报错。
document.getElementById("myIFrame").contentWindow.document
// Uncaught DOMException: Blocked a frame from accessing a cross-origin frame.
上面命令中,父窗口想获取子窗口的DOM,因为跨域资源导致报错。
反之亦然,子窗口获取主窗口的DOM也会报错。
window.parent.document.body
// 报错
前面讲的这些实际上我第二个例子试验过了,下面也就是我遇到问题的几种解决方法。
参考的他人的文章找到的,对于完全不同源的网站,目前有三种方法,来解决我遇到的问题。
1. 片段标识符
片段标识符呢也就是哈希值#,我们都知道当网址资源#前面不变,后面的部分变化的时候网页是不会刷新的,如果不清楚的话,可以看一下我之前写过的一篇文章 浅谈SPA ,里面有详细的介绍#。
就是父文档和子文档之间要交互时,就去改变hash值 也就是#后面的部分,然后两者再相互去监听hash变化的事件,再去自己做一些处理就好了
window.location.hash; // 这个是可以获取hash值的 window.onhashchange = function(){
// 这个是hash值改变会触发这个函数
}
举一个小例子可以去试验一下
父窗口可以把信息,写入到子窗口的片段标识符
父窗口中的代码
为了好看吧,就不用博客园自带的那个代码了,就这样把子文档的url地址给改变了吧,因为#不会刷新网页,而子文档中也可以监听到这个#值的改变,所以子文档中
浏览器中打印的东西
在这里可以看到了,我们传递过去的数据信息为 #changeColor,在子页面中可以判断#后面的带的东西,再去执行自己的逻辑。
同样的子文档给父文档传递数据
我们不是可以拿到父文档的那个 window.parent吗,就用这个去改变就可以了,但是 BUT!!!
我在子页面中使用的时候
btn1.onclick = function () {
console.log(parent.location);
}
这是什么嘛,你父文档都可以改子文档了,问什么这个还是要 block frame with啥啥啥的,我本来以为可以成功的,这个有知道解决方法的大佬可以帮帮忙吗。嘿嘿,暂时先这样吧,父文档已经可以给子文档传递信息了,我的解决方法也不是这种,暂时先把这个错误问题放一放,以后有解决方法了,会来这里修改的。
2. window.name
浏览器窗口有window.name属性,这个属性最大的特点是,无论是否同源,只要在同一个窗口里面,前一个窗口设置了这个属性后,后一个网页可以读取它。
父窗口先打开一次子窗口,载入一个不同源的网页,将网页信息写入window.name属性。
window.name = data;
接着,子窗口跳回一个与主窗口同域的网址
location = 'http://parent.url.com/xxx.html'
然后主窗口就可以读取子窗口的window.name了
var data = document.getElementById('myFrame').contentWindow.name;
这种方法的优点是,window.name
容量很大,可以放置非常长的字符串;缺点是必须监听子窗口window.name
属性的变化,影响网页性能。
3.跨文档通信API postMessage
我用到的解决方法是这种方法,感觉它和Vue之间的组件传值一样,不说话了,直接上代码,测试吧,记得那个子文档是 3000端口的页面
父文档
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
body{
background: pink;
}
#iframe1{
width: 100%;
height: 400px;
}
</style>
</head>
<body> <input type="button" id="btn1" value="改变子文档的东西">
<input type="button" id="btn2" value="删除span1的颜色">
<input type="button" id="btn3" value="改变span2的颜色">
<br />
<hr />
<!--<iframe id="iframe1" src="http://localhost:3000" frameborder="0"></iframe>-->
<iframe id="iframe1" src="http://localhost:3000" frameborder="0"></iframe>
<script>
var oIframe1 = document.getElementById('iframe1'); var a = function fn(){
document.body.style.background = 'green';
}; btn1.onclick = function () {
console.log('传递的数据是','messageInfo');
oIframe1.contentWindow.postMessage('changeColor',"http://localhost:3000"); };
btn2.onclick = function () {
//oIframe1.contentWindow.postMessage('changeSpan2Color',"http://localhost:3000");
oIframe1.contentWindow.postMessage('deleteSpan1',"http://localhost:3000")
};
btn3.onclick = function () {
oIframe1.contentWindow.postMessage('changeSPan2Color',"http://localhost:3000")
}; window.addEventListener('message',function (res) {
console.log(`这里是父文档`);
if(res.data == 'GetWhiteLabel')
document.body.style.background = 'yellow';
}) </script>
</body>
</html>
子文档
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
body{
background: skyblue;
}
</style>
</head>
<body>
<h1>这里是我的html页面呢</h1>
<span class="span1">span1标签</span>
<span class="span2">span2标签</span> <br>
<input type="button" id="btn1" value="改变父文档的东西">
<script>
<!---->
window.onload = function () {
let parent = window.parent;
window.addEventListener('message',function (res) {
console.log(`******************这里是子页面的接收到的消息*************`);
console.log(res);
switch (res.data) {
case 'changeColor':
document.body.style.background = 'green';
break;
case 'deleteSpan1':
var oSpan1 = document.querySelector('.span1');
oSpan1.parentNode.removeChild(oSpan1);
break;
case 'changeSPan2Color':
var oSpan2 = document.querySelector('.span2');
oSpan2.style.color = 'red';
break; } });
btn1.onclick = function () {
parent.postMessage("GetWhiteLabel","*");
}
}
</script>
</body>
</html>
还是同样的页面吧,实现一样的功能。
上面有的地方写的不太好,存在一些安全问题,这个正是我现在正在做的地方,
oframe.contentWindow.postMessage(data,origin,false); //这个是postMessage的API
// 发送的数据 子文档地址 false // 在子文档中去监听那个message的变化
window.addEventListener("message",function(res){
console.log(res.data); //这个东西就是发送过去的数据
// 去根据传过来的不同的数据 再去做相应的判断
})
子文档给父文档传数据的方式
parent.postMessage('message',origin,false); // 同样也是类似的 // 在父文档中去监听那个message的值
window.addEventListener("message",function(res){
console.log(res.data); //这个东西就是发送过去的数据
// 去根据传过来的不同的数据 再去做相应的判断
});
这样做可以实现,就是有一些安全问题,就是所有人如果查看你网站的源码的话肯定会看到这个东西的,你写的这么随意,其他任何网站只要引入你的子文档,然后就可以通过自己去写一些东西去改变你的子文档。
另外的一个缺点就感觉是 拓展性不好,你还需要去拿到子文档的网站,还需要再去修改它的源代码,感觉特别麻烦,如果有好几十个页面还要写好几十个吗。
所以想到了一种传值的方法,不穿那个要判断的东西,把要修改的元素的html代码的函数给传过去,就是子文档去定义一个接口去接收,父文档把要执行的事件都传过去,然后子文档写一个执行事件的接口。这里就会出现刚才第一个那个安全问题了,更为严重,因为你不知道要执行的是什么事件。
现在有一个想法就是后台做一个认证,就像微信的那个access_token认证一样的东西,加密,每次去操作子文档的时候,都要去请求,然后在子页面监听到这个事件之后解密再两个之间去对比,如果一样了才去执行,这个暂时还不知道如何去下手。
本文感觉特别有用的解决办法和参考的文章有如下还有更多的没有发,如果你遇到了同样的问题,看了我的解决不了问题,可以去看看下面的文章,当然还可以给我留言,必回复。
stackoverflow网页中的问答 博客园园友的文章 关于iframe的实践 阮一峰大佬的 浏览器同源政策及其规避方法
如果有更好的解决办法还望可以告知,谢谢!
如果你阅读了本文章有了一些收获,我会感到非常开心,由于能力有限,文章有的部分解释的不到位,希望在以后的日子里能慢慢提高自己能力,如果不足之处,还望指正。
记一次与iframe之间的抗争的更多相关文章
- 主页面、iframe之间调用以及传值
主页面.iframe之间的调用和传值,无非就是两个交互形式: 主页面与子页面的交互 子页面之间的交互 接下来要讲的是四种交互传值的方式:利用postMessage方法传值.DOM操作传值.URL方式传 ...
- iframe之间操作记录
1.watch.js (function ($) { $.fn.watch = function (callback) { return this.each(function () { //缓存以前的 ...
- 【基础】iframe之间的切换(四)
案例: 打开http://mail.126.com/,定位登录输入框时,却总是定位不到元素,后来发现,登录的内容在一个iframe中. 一.由主页面切换至iframe dr.switchTo().fr ...
- iframe之间通信问题及iframe自适应高度问题
下面本人来谈谈iframe之间通信问题及iframe自适应高度问题. 1. iframe通信 分为:同域通信 和 跨域通信.所谓同域通信是指 http://localhost/demo/iframe/ ...
- javascript 中contentWindow和 frames和iframe之间通信
iframe父子兄弟之间通过jquery传值(contentWindow && parent),iframe的调用包括以下几个方面:(调用包含html dom,js全局变量,js方法) ...
- window之间、iframe之间的JS通信
一.Window之间JS通信 在开发项目过程中,由于要引入第三方在线编辑器,所以需要另外一个窗口(window),而且要求打开的window要与原来的窗口进行js通信,那么如何实现呢? 1.在原窗口创 ...
- 父窗口与iFrame之间调用方法和元素
父窗口与iFrame之间调用方法和元素 父窗口调用子窗口: 调用元素 js格式: var obj=document.getElementById("iframe的name").co ...
- JS观察者设计模式:实现iframe之间快捷通信
观察者设计模式又称订阅发布模式,在JS中我们习惯叫做广播模式,当多个对象监听一个通道时,只要发布者向该通道发布命令,订阅者都可以收到该命令,然后执行响应的逻辑.今天我们要实现的就是通过观察者设计模式, ...
- JS备忘--子父页面获取元素属性、显示时间,iframe之间互相调用函数
//页面加载完成后执行 $(function () { getHW();}); //当用户改变浏览器大小改变时触发 $(window).resize(function () { setHW(); }) ...
随机推荐
- [Swift]LeetCode680. 验证回文字符串 Ⅱ | Valid Palindrome II
Given a non-empty string s, you may delete at most one character. Judge whether you can make it a pa ...
- [Swift]LeetCode840. 矩阵中的幻方 | Magic Squares In Grid
A 3 x 3 magic square is a 3 x 3 grid filled with distinct numbers from 1 to 9 such that each row, co ...
- 一文掌握 Linux 性能分析之网络篇
本文首发于我的公众号 Linux云计算网络(id: cloud_dev),专注于干货分享,号内有 10T 书籍和视频资源,后台回复「1024」即可领取,欢迎大家关注,二维码文末可以扫. 这是 Linu ...
- GoJS学习笔记
GoJS 和 GO 语言没有关系,它是一个用来创建交互式图表的 JavaScript 库. 基础概念 GraphObject 是所有图形是抽象基类,基本上 GoJS 中,万物皆 GraphObject ...
- python之读取配置文件模块configparser(一)基本操作
configparser模块是读取类ini文件使用,其有固定的读取格式如下: [section1] option11 = value11 option12 = value12 .... [sectio ...
- Python内置函数(61)——str
英文文档: class str(object='') class str(object=b'', encoding='utf-8', errors='strict') Return a string ...
- 并发编程(十四)—— ScheduledThreadPoolExecutor 实现原理与源码深度解析 之 DelayedWorkQueue
我们知道线程池运行时,会不断从任务队列中获取任务,然后执行任务.如果我们想实现延时或者定时执行任务,重要一点就是任务队列会根据任务延时时间的不同进行排序,延时时间越短地就排在队列的前面,先被获取执行. ...
- Android--操作图片Exif信息
前言 在Android系统中,图片文件在内存中以像素点的二维数组加载,存放像素信息,还会在开头加上一些额外的照片拍摄参数信息,这些信息就是Exif.Android2.0之后,媒体库加入了操作图片Exi ...
- [Python]peewee 使用经验
peewee 使用经验 本文使用案例是基于 python2.7 实现 以下内容均为个人使用 peewee 的经验和遇到的坑,不会涉及过多的基本操作.所以,没有使用过 peewee,可以先阅读文档 正确 ...
- ADO.NET中COMMAND对象的ExecuteNonQuery、ExcuteReader和ExecuteScalar方法
1.ExecuteNonQuery方法.该方法执行更新操作,即与UPDATE.INSERT.DELETE等语句有关的操作,在这种情况下,返回值是命令影响的行数.对其他语句,如SET或CREATE,则返 ...