iframe与主窗口通信

iframe,窗口,通信 · 浏览次数 : 235

小编点评

**内容简介** 本文介绍了 Postmate 库,一个简化主窗口和 iframe 之间的跨域通的 JavaScript 库。使用 Postmate 库,可以实现在主窗口中获取和发送来自 iframe 的消息,以及在 iframe 中获取和发送主窗口的消息。 **主要功能** * 建立和处理主窗口和 iframe 之间的跨域通。 * 支持通过 iframe 发送和接收消息。 * 通过事件通知主窗口发送来自 iframe 的消息。 * 允许在 iframe 中获取主窗口的属性和方法。 * 提供一个简单的方法实现跨域通。 **使用教程** * 在 HTML 文件中包含 Postmate 库的脚本。 * 创建一个 iframe 与主窗口交互。 * 在 iframe 中获取主窗口的属性和方法。 * 在主窗口中接收来自 iframe 的消息。 **代码示例** ```javascript // postmate.js const Postmate = require('postmate'); const handshake = new Postmate({ container: document.querySelector('#parent'), url: './children.html' }); handshake.then(child => { child.on('some-event', data => console.log(data)); child.call('changeParentMessage', parentMessage); }); ``` **结果** 当在 HTML 文件中包含 Postmate 库的脚本时,在 iframe 中获取主窗口的属性和方法时,可以获得以下结果: * 主窗口的属性和方法(如名称、年龄等) * iframe 的属性和方法(如名称、属性等) * iframe 中发送的消息 **结论** Postmate 库是一个方便的工具,可以实现在主窗口中获取和发送来自 iframe 的消息。它提供了一个简单的方法实现跨域通,可以使在 HTML 文件中包含 Postmate 库的脚本时,在 iframe 中获取和发送主窗口的属性和方法。

正文

1. 引言

<iframe> 元素是 HTML 中的一个标签,用于在当前页面中嵌入另一个页面

使用 <iframe> 可以实现以下功能:

  1. 嵌入其他网页:可以将其他网页嵌入到当前页面中,例如显示地图、视频、文档等
  2. 嵌入本地页面:可以将其他页面或组件嵌入到当前页面中,以实现模块化和复用

嵌入的iframe和主网页之间具有一定的独立性,无法像正常的一个网页上下文一样访问内容,如何通信成为一个问题

本文主要记述iframe和主窗口之间的通信方式,包含内置方法和使用Postmate库

2. 概述

iframe标签在JS中定义为HTMLIFrameElement,而HTMLIFrameElement.contentWindow返回当前HTMLIFrameElementWindow对象,可以使用这个Window 对象去访问这个 iframe 的文档和它内部的 DOM

Window对象可以接收消息(onmessage - Web API 接口参考 | MDN (mozilla.org))和发送消息(window.postMessage - Web API 接口参考 | MDN (mozilla.org)

另外,Window对象还存在父对象(window.parent - Web API 接口参考 | MDN (mozilla.org)),可以通过window.parent访问父对象

利用上述的方法与特性,就可以实现iframe和主窗口之间的通讯

思路之一是:将需要对方访问的属性和方法挂载到window对象上,从而主窗口通过document.querySelector('iframe').contentWindow.<xxx>访问iframe,iframe通过window.parent.<xxx>访问主窗口的属性或方法

思路之二是:主窗口向iframe发送信息postMessage(),并监听子对象的信息,iframe监听主窗口信息并向主窗口发送信息

由于安全原因与浏览器限制,非同源URL(同地址同端口)不可以使用第一种方法,第二种方法均适用

3. 内置方法

3.1 初始准备

笔者准备了这样两个网页,分别叫parent.htmlchildren.html

parent.html内容如下:

<!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>
  <style>
    html,
    body {
      width: 100%;
      height: 100%;
      margin: 0;
      padding: 0;
    }

    #parent {
      width: 100%;
      height: 100%;
      display: flex;
      flex-direction: row;
    }

    iframe {
      width: 80%;
      height: 100%;
    }

    #panel {
      width: 20%;
      height: 100%;
      background: white;
      display: flex;
      flex-direction: column;
    }

    code {
      font-size: large;
    }
  </style>
</head>

<body>
  <div id="parent">
    <iframe src="./children.html" frameborder="0"></iframe>
    <div id="panel">
      <h3>主窗口</h3>
        <code></code>
    </div>
  </div>

  <script>

  </script>
</body>

</html>

children.html内容如下:

<!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>
  <style>
    html,
    body {
      width: 100%;
      height: 100%;
      margin: 0;
      padding: 0;
    }

    #container {
      width: 100%;
      height: 100%;
      background: cadetblue;
      display: flex;
      flex-direction: column;
    }

    code {
      font-size: large;
    }
  </style>
</head>

<body>
  <div id="container">
    <h3>子窗口</h3>
    <code></code>
  </div>
  <script>

  </script>
</body>

</html>

这两网页的界面如下:

image-20230726200834842

现在,笔者准备在主窗口与iframe之间传递一个信息

3.2 访问属性

将对方访问的属性和方法挂载到window对象上,从而主窗口通过document.querySelector('iframe').contentWindow.<xxx>访问iframe,iframe通过window.parent.<xxx>访问主窗口的属性或方法

示例代码如下:

// parent.html   

const parentMessage = {
    name: '李四',
    age: 20
};
Reflect.defineProperty(window, 'parentMessage', {
    value: parentMessage
});

// 访问子窗口的全局变量
const iframe = document.querySelector('iframe');
setTimeout(() => {
    document.querySelector('code').innerHTML = JSON.stringify(iframe.contentWindow.childMessage)
}, 1000); // 等待iframe初始化完毕
// children.html

const childMessage = {
    name: '张三',
    age: 18
};
Reflect.defineProperty(window, 'childMessage', {
    value: childMessage
});

document.querySelector('code').innerHTML = JSON.stringify(parent.parentMessage)

结果如下:

image-20230727001146773

注意,这种方法只适用与同源URL,非同源URL访问对方的属性会出现类似错误:

Uncaught DOMException: Blocked a frame with origin "http://127.0.0.1:5500" from accessing a cross-origin frame

3.3 消息发送与监听

主窗口向iframe发送信息postMessage(),并监听子对象的信息,iframe监听主窗口信息并向主窗口发送信息

示例代码如下:

// parent.html

const parentMessage = {
    name: '李四',
    age: 20
};
const iframe = document.querySelector('iframe');
iframe.onload = function () {
    // 发送消息
    iframe.contentWindow.postMessage(parentMessage, '*');
};
// 接收消息
window.addEventListener('message', function (event) {
    const code = document.querySelector('code');
    code.innerHTML = JSON.stringify(event.data);
});
// children.html

const childrenMessage = {
    name: '张三',
    age: 18
};
// 接收消息
window.addEventListener('message', function (event) {
	// 发送消息
    window.parent.postMessage(childrenMessage, '*');
    const code = document.querySelector('code');
    code.innerHTML = JSON.stringify(event.data);
});

结果和上面是一样的:

image-20230727001146773

这种方法同源URL与非同源URL都适用

4. Postmate

Postmate 是一个 JavaScript 库,用于简化主窗口和 iframe 之间的跨域通,它基于 window.postMessage() 方法,并提供了一种简单的方式来在主窗口和 iframe 之间发送和接收消息

Postmate 的GitHub地址为:dollarshaveclub/postmate: 📭 A powerful, simple, promise-based postMessage library. (github.com)

Postmate的特性有:

  • 基于 promise 的 API,用于优雅和简单的通信
  • 安全的双向父 <-> 子握手,并带有消息验证
  • 子代暴露一个可检索的模型对象,父代可以访问
  • 子代发出事件,父代可以监听
  • 父代可以调用子代的函数
  • 零依赖性,如果需要的话,可以为 Promise API 提供自己的 polyfill 或抽象
  • 轻量级,大小约为1.6kb(缩小和压缩后)

Postmate 的主要用法就是:

  1. 主窗口建立握手程序,握手成功后可向iframe获取数据,监听事件以及远程调用函数
  2. iframe建立握手模型,可包含数据、函数等,成功握手后可向主窗口触发事件

大致用法就是如此,有个问题:iframe向主窗口发送数据倒是看起来很简单,直接在主窗口里获取即可,但是如何在iframe里获取主窗口的数据呢,如何才能将主窗口的数据发送给iframe呢?

方法之一是:使用iframe建立的握手模型里远程调用函数,将主窗口传给iframe的数据作为函数的参数(这一点感觉很别扭)

示例代码如下:

parent.html:

<script src="https://cdn.jsdelivr.net/npm/postmate@1.5.2/build/postmate.min.js"></script>
<div id="parent">
    <!-- <iframe src="./children.html" frameborder="0"></iframe> -->
    <div id="panel">
        <h3>主窗口</h3>
        <code></code>
    </div>
</div>

<script>
    const parentMessage = {
        name: '李四',
        age: 20
    };

    // 建立握手程序
    const handshake = new Postmate({
        container: document.querySelector('#parent'), // 注意将原iframe注释掉
        url: './children.html'
    });

    // 握手成功后
    handshake.then(child => {

        // 监听事件
        child.on('some-event', data => console.log(data)); // Logs "Hello, World!"

        // 远程调用函数,将主窗口传给iframe的数据作为函数的参数
        child.call('changeParentMessage', parentMessage);

        // 获取数据
        child.get('childMessage').then(data => {
            document.querySelector('code').innerHTML = JSON.stringify(data)
        }); 

    });
</script>

children.html:

<script src="https://cdn.jsdelivr.net/npm/postmate@1.5.2/build/postmate.min.js"></script>
<div id="container">
    <h3>子窗口</h3>
    <code></code>
</div>
<script>
    const childMessage = {
        name: '张三',
        age: 18
    };

    const handshake = new Postmate.Model({
        // 建立握手模型 Property values may be functions, promises, or regular values
        childMessage: childMessage,
        parentMessage: {},
        changeParentMessage: function (newMessage) {
            this.parentMessage = newMessage;
            document.querySelector('code').innerHTML = JSON.stringify(this.parentMessage);
        }
    });

    //成功握手后可向主窗口触发事件
    handshake.then(parent => {
        parent.emit('some-event', 'Hello, World!');
    });
</script>

结果如下:

image-20230727020851043

5. 参考资料

[1] <iframe> - HTML(超文本标记语言) | MDN (mozilla.org)

[2] window.parent - Web API 接口参考 | MDN (mozilla.org)

[3] HTMLIFrameElement.contentWindow - Web API 接口参考 | MDN (mozilla.org)

[4] iframe跨域通信(postMessage) - 掘金 (juejin.cn)

[5] window.postMessage - Web API 接口参考 | MDN (mozilla.org)

[6] dollarshaveclub/postmate: 📭 A powerful, simple, promise-based postMessage library. (github.com)

[7] 零基础学习 Postmate库 - 掘金 (juejin.cn)

与iframe与主窗口通信相似的内容:

iframe与主窗口通信

本文主要记述iframe和主窗口之间的通信方式,包含内置方法和使用Postmate库

微前端中实现沙箱环境的方案调研

前言 在微前端实践过程中有一个必然会遇到的问题:全局作用域变量的污染问题,具体来说就是window对象挂载数据会被主子应用获取和修改导致数据相互污染问题,这时候如果能在应用之间做个数据隔离,最好能实现一个沙箱环境,对解决问题很有帮助。 iframe方案 说到沙箱隔离,首先想到的是iframe,自带数

Iframe在Vue中的状态保持技术

Iframe是一个历史悠久的HTML元素,根据MDN WEB DOCS官方介绍,Iframe定义为HTML内联框架元素,表示嵌套的Browsing Context,它能够将另一个HTML页面嵌入到当前页面中。Iframe可以廉价实现跨应用级的页面共享,并且具有使用简单、高兼容性、内容隔离等优点,因此以Iframe为核心形成了前端平台架构领域第1代技术。

Nginx双层域名时 iframe嵌入/跳转页面的处理过程

# Nginx双层域名时 iframe嵌入/跳转页面的处理过程 ## 背景 ``` 两年前在上一家公司内遇到一个Nginx的问题 当时的场景是 双层nginx代理时(一层域名侧, 一层拆分微服务的网关层) 程序里面会打开一个嵌套的iframe, 便于进行缩放. 但是此时因为只能就近获取 第二层反向代

《最新出炉》系列初窥篇-Python+Playwright自动化测试-11-playwright操作iframe-上篇

1.简介 原估计宏哥这里就不对iframe这个知识点做介绍和讲解了,因为前边的窗口切换就为这种网页处理提供了思路,另一个原因就是虽然iframe很强大,但是现在很少有网站用它了。但是还是有小伙伴或者童鞋们私下问这个问题,那么宏哥就单独写一篇关于iframe网页处理的文章。iframe 是web自动化

《最新出炉》系列初窥篇-Python+Playwright自动化测试-12-playwright操作iframe-中篇

1.简介 按照计划今天就要用实际的例子进行iframe自动化测试。经过宏哥长时间的查找,终于找到了一个含有iframe的网页(QQ邮箱和163邮箱),别的邮箱宏哥就没有细看了。所以今天这一篇的主要内容就是用这两个网页的iframe结合上一篇的理论知识,宏哥给小伙伴或者童鞋们演示一下。 2.QQ邮箱

《最新出炉》系列初窥篇-Python+Playwright自动化测试-13-playwright操作iframe-下篇

1.简介 通过前边两篇的学习,想必大家已经对iframe有了一定的认识和了解,今天这一篇主要是对iframe做一个总结,主要从iframe的操作(输入框、点击等等)和定位两个方面进行总结。 2.iframe是什么? iframe 简单来说就是一个 html 嵌套了另外一个 html。在页面元素上最简

《最新出炉》系列初窥篇-Python+Playwright自动化测试-14-playwright操作iframe-番外篇

1.简介 通过前边三篇的学习,想必大家已经对iframe有了一定的认识和了解,今天这一篇主要是对iframe的一些特殊情况的介绍和讲解,主要从iframe的定位、监听事件和执行js脚本三个方面进行展开介绍。 2.iframe定位 2.1动态id属性如何定位 有时候,我们可能看到的iframe 的id

JS 实现鼠标框选(页面选择)时返回对应的代码或文本内容

当用户进行鼠标框选选择了页面上的内容时,把选择的内容进行上报。 分为以下几点: 选择文案时 选择图片、svg、iframe、video、audio 等标签时 选择 input、select、textarea 等标签时 选择input、textarea 标签内容时 选择类似   字符时 键盘全选时 鼠...

[转帖]同站 和 同源 你理解清楚了么?

同站(same-site) 和同源(same-origin) 经常在页面跳转、fetch()请求、cookie、打开弹出窗口、嵌入式资源和 iframe 等场景中被提到,但是有相当一部分同学的理解是错误的。 源(Origin) Origin 是协议(例如 HTTP 或 HTTPS )、主机名和端口的