iframe相关及跨域解决方案

26,536 阅读6分钟

[toc]

获取iframe的window、document

页面:


<iframe name="frameSon" id="frame" width="300px" height="300px" src="http://b.laihua.com:4444" frameborder="1"></iframe>

  1. 通过contentDocument或者contentWindow

const frame = document.getElementById('frame')

const fwindow = frame.contentWindow

const fdoc1 = frame.contentDocument

const fdoc2 = frame.contentWindow.document

20210724172850.png

  1. 通过window.frames和iframe的name属性.这种方式直接获取到的是iframe页面的window对象

const frame = window.frames['frameSon']

console.log(`frame:`,frame);

console.log(`frame.document:`,frame.document);

20210724172625.png

读取或者调用iframe内部的方法

如果要读取或者操作iframe中的内容,要确保在iframe加载完成后再进行操作,如果iframe还未加载完成就开始调用里面的方法或变量,会产生错误。判断iframe是否加载完成有两种方法:

  1. 通过上面第一种方式获取到得iframe是iframeDOM元素,可以直接通过frame.onload回调来判断iframe加载完成

  2. 通过上面第一种方式获取到得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,在父页面中调用这个方法发现跨域了:

20210724175304.png

这种情况下,父页面和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>

调用结果成功:

20210724175939.png

两个页面域名完全不同

如果两个网页不同源,就无法拿到对方的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)

20210724181519.png

可以通过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.originevent.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()

}

})

}

执行结果:

20210725225754.png

如果子页面执行完方法需要向父页面作出通知,可以在子页面中通过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);

})

}

执行结果:

20210725230139.png

关于postMessage的安全问题

如果不希望从其他网站接收message,不要为message事件添加任何事件侦听器。 这是一个完全万无一失的方式来避免安全问题。

如果确实希望从其他网站接收message,始终使用MessageEvent.originMessageEvent.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)()

})

}

结果:

20210725233902.png

同样的,子窗口也可以改变父窗口的片段标识符。


parent.location.href= target + "#" + hash;

REF

(9条消息) postMessage安全性问题_Exploit的小站~-CSDN博客

前端跨域常见解决方案(包括反向代理)_哔哩哔哩_bilibili

window.postMessage - Web API 接口参考 | MDN

利用JS对iframe父子(内外)页面进行操作的方法教程_javascript技巧_脚本之家

浏览器同源政策及其规避方法 - 阮一峰的网络日志