[toc]
获取iframe的window、document
页面:
<iframe name="frameSon" id="frame" width="300px" height="300px" src="http://b.laihua.com:4444" frameborder="1"></iframe>
- 通过
contentDocument
或者contentWindow
:
const frame = document.getElementById('frame')
const fwindow = frame.contentWindow
const fdoc1 = frame.contentDocument
const fdoc2 = frame.contentWindow.document
- 通过
window.frames
和iframe的name
属性.这种方式直接获取到的是iframe页面的window对象:
const frame = window.frames['frameSon']
console.log(`frame:`,frame);
console.log(`frame.document:`,frame.document);
读取或者调用iframe内部的方法
如果要读取或者操作iframe中的内容,要确保在iframe加载完成后再进行操作,如果iframe还未加载完成就开始调用里面的方法或变量,会产生错误。判断iframe是否加载完成有两种方法:
-
通过上面第一种方式获取到得iframe是iframeDOM元素,可以直接通过
frame.onload
回调来判断iframe加载完成 -
通过上面第一种方式获取到得iframe是iframe的window对象,但并没有onload方法,所以可以用当前页面的
window.onload
回调或者document.readyState=="complete"
来判断
例如,我们要调用子iframe中的test
方法,两种方式:
第一种方式:
const frame = document.getElementById('frame')
const fdoc = frame.contentDocument || frame.contentWindow.document
frame.onload = () => {
console.log(`frame.contentWindow.test:`, frame.contentWindow.test);
frame.contentWindow.test()
}
第二种方式:
const frame = window.frames['frameSon']
const fdoc = frame.document
window.onload = () => {
console.log(`frame.test:`, frame.test);
frame.test()
}
主域相同、二级域名不同的两个页面获取对方dom导致的跨域问题
主页面是a.baidu.com
,以iframe的形式引入了b.baidu.com
子页面。子页面中声明了一个全局方法test
,在父页面中调用这个方法发现跨域了:
这种情况下,父页面和iframe主域相同,只是二级域名不同,可以通过两个页面同时同时把document.domain
设置为相同的主域来解决:
另外,通过给主域相同、二级域名不同的页面设置相同的
document.domain
,也可以解决cookie的跨域问题。Cookie 是服务器写入浏览器的一小段信息,只有同源的网页才能共享。但浏览器允许通过设置document.domain
共享 Cookie。
父页面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>page1</h1>
<iframe name="frameSon" id="frame" width="300px" height="300px" src="http://b.baidu.com:4444"
frameborder="1"></iframe>
</body>
<script>
document.domain = 'baidu.com'
const frame = window.frames['frameSon']
const fdoc = frame.document
window.onload = () => {
console.log(`frame.test:`, frame.test);
frame.test()
}
</script>
</html>
iframe子页面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>page2d</h1>
</body>
<script>
document.domain = 'baidu.com'
function test(){
console.log(`222:`,222);
}
</script>
</html>
调用结果成功:
两个页面域名完全不同
如果两个网页不同源,就无法拿到对方的DOM。例如iframe引入和window.open
方法打开的窗口,它们之间的窗口无法通信。
例如,父页面是baidu.com
,子页面是zijie.com
,父页面向调用子页面中的全局方法test
。
子页面中声明了test
全局方法:
function test(){
console.log(`222:`,222);
}
父页面通过iframe引入子页面并尝试调用其test
方法:
<body>
<h1>page1</h1>
<iframe
name="frameSon"
id="frame"
src="http://zijie.com:4444"
width="300px"
height="300px"
frameborder="1">
</iframe>
</body>
<script>
const frame = window.frames['frameSon']
const fdoc = frame.document
window.onload = () => {
console.log(`frame.test:`, frame.test);
frame.test()
}
</script>
结果报错跨域:
(index):54 Uncaught DOMException: Blocked a frame with origin "http://baidu.com:3333" from accessing a cross-origin frame.
at window.onload (http://baidu.com:3333/:54:42)
可以通过postMessage
、片段标识符、window.name
来解决,下面主要介绍前两个:
window.postMessage
window.postMessage
方法允许跨窗口通信,不论这两个窗口是否同源。
大概的实现逻辑是:
一个窗口可以获得对另一个窗口的引用(比如iframe的contentWindow属性、执行window.open返回的窗口对象、或者是命名过或数值索引的window.frames),调用这个窗口引用上的postMessage
方法分发一个MessageEvent 消息。然后子窗口中通过监听message
事件来获取父窗口传递的消息即可。
子页面向父窗口发送消息同理,不同一点是子窗口可以通过MessageEvent.source
来获取父窗口的引用。
语法:
otherWindow.postMessage(message, targetOrigin);
otherWindow
:要给其发送消息的目标窗口的引用;
第一个参数message
: 具体的消息内容;
第二个参数targetOrigin
:指定哪些窗口能接收到消息事件。可以指定接收消息的窗口的源(origin),即"协议 + 域名 + 端口"。也可以设为"*",表示不限制域名,向所有窗口发送。 在发送消息的时候,如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配targetOrigin提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送。这个机制用来控制消息可以发送到哪些窗口。
监听message
时间的时候,可以通过MessageEvent
对象获取下面三个参数:
MessageEvent.source:发送消息的窗口引用
MessageEvent.origin: 发送消息的来源origin
MessageEvent.data: 消息内容
看一下如何通过postMessage
解决上面出现的跨域问题:
父页面可以获取到子页面的引用后,调用其postMessage
方法并将需要调用的方法通过参数传递过去:
window.onload = () => {
frame.postMessage({ fn: 'test' }, "http://zijie.com:4444")
}
子页面中监听message
时间,并对event.origin
和event.data
作出判断后,调用相应的方法:
function test(){
console.log(`222:`,222);
}
window.onload = function(){
window.addEventListener('message', e => {
console.log(`e:`,e);
if(e.origin === 'http://baidu.com:3333' && e.data.fn === 'test'){
test()
}
})
}
执行结果:
如果子页面执行完方法需要向父页面作出通知,可以在子页面中通过event.source
获取父页面的引用,调用其poseMessage
方法,然后父页面中通过监听message
时间实现。
子页面:
function test(){
console.log(`222:`,222);
}
window.onload = function(){
window.addEventListener('message', e => {
console.log(`e:`,e);
if(e.origin === 'http://baidu.com:3333' && e.data.fn === 'test'){
test()
// 向父页面发出通知
e.source.postMessage('执行完毕', '*')
}
})
}
父页面:
window.onload = () => {
frame.postMessage({ fn: 'test' }, "http://zijie.com:4444")
// 接受子页面通知
window.addEventListener('message', e => {
console.log(`e.data:`,e.data);
})
}
执行结果:
关于postMessage
的安全问题
如果不希望从其他网站接收message,不要为message事件添加任何事件侦听器。 这是一个完全万无一失的方式来避免安全问题。
如果确实希望从其他网站接收message,始终使用MessageEvent.origin
和 MessageEvent.source
属性验证发件人的身份。 任何窗口(包括例如evil.example.com)都可以向任何其他窗口发送消息,
当使用postMessage
将数据发送到其他窗口时,始终指定精确的目标origin,而不是*。 恶意网站可以在我们不知情的情况下更改窗口的位置,因此它可以拦截使用postMessage发送的数据。
片段标识符fragment identifier
片段标识符(fragment identifier)指的是URL的#号后面的部分。比如 “example.com/x.html#frag… 的“#fragment”。如果只是改变片段标识符,页面不会重新刷新。
父页面把需要调用的方法以hash的形式加在iframe地址后面:
const frame = document.getElementById('frame')
window.onload = () => {
// 通过hash改变iframe的src,不会导致刷新
frame.src = `http://zijie.com:4444#test`
}
子页面监听hashchage
事件来获取当前地址的hash值:
function test(){
console.log(`222:`,222);
}
window.onload = function(){
window.addEventListener('hashchange', e => {
console.log(`e:`,e);
// 通过location.hash获取到父页面传递过来的信息
console.log(`window.location.hash:`,window.location.hash);
const fn = window.location.hash.slice(1)
eval(fn)()
})
}
结果:
同样的,子窗口也可以改变父窗口的片段标识符。
parent.location.href= target + "#" + hash;
REF
(9条消息) postMessage安全性问题_Exploit的小站~-CSDN博客
前端跨域常见解决方案(包括反向代理)_哔哩哔哩_bilibili
window.postMessage - Web API 接口参考 | MDN