Lazy loaded image
postMessage 实现父子 iframe 通信 (跨源/同源)
字数 4454阅读时长 12 分钟
2024-7-16
2025-4-29
type
status
date
slug
summary
tags
category
icon
password

前言:为什么需要跨源通信?

出于安全考虑,浏览器强制执行同源策略 (Same-Origin Policy)。该策略限制了从一个源(origin)加载的文档或脚本如何与来自另一个源的资源进行交互。然而,在现代 Web 应用中,不同源之间的通信需求非常普遍,例如:
  • 页面与其嵌入的不同域的 <iframe> 之间需要交互。
  • 一个网页需要与其通过 window.open() 打开的弹出窗口通信。
  • 主页面需要与在后台运行的 Web Workers 交换数据。
为了满足这些需求,同时又不破坏浏览器的安全模型,window.postMessage() 应运而生。

什么是 window.postMessage()?

window.postMessage() 方法允许来自不同源的脚本安全地传递消息。简单来说,一个窗口(或 frame)可以获取对另一个窗口的引用(例如,通过 window.openeriframeElement.contentWindow 等),然后调用该引用的 postMessage() 方法。这会在目标窗口上触发一个 MessageEvent 事件,目标窗口可以通过监听这个事件来接收并处理消息。
基本语法:
  • targetWindow: 目标窗口的引用,即接收消息的窗口对象(例如 window.openeriframe.contentWindow)。
  • message: 要发送的数据。它可以是任何能够被“结构化克隆算法”处理的数据类型(字符串、数字、对象、数组等,但不能直接是函数或 DOM 节点)。
  • targetOrigin: 极其重要的安全参数。它指定了 targetWindow 的源(协议、主机名和端口)必须是什么,postMessage 才会发送消息。请尽可能使用具体的源 (例如 "<https://example.com>")。如果设为 "*",则表示不限制目标窗口的源,这在目标窗口位置可能意外改变时会带来安全风险。
  • transfer (可选): 一个 Transferable 对象的数组(如 ArrayBuffer, MessagePort, ImageBitmap)。这些对象的所有权将转移给接收方,而不是复制,这对于传输大数据能显著提高性能。
接收消息:
接收消息的窗口需要添加一个 'message' 事件的监听器:
message 事件回调函数中的 event 对象包含关键信息:
  • event.data: 从发送窗口传递过来的实际数据。
  • event.origin: 发送消息窗口的源(例如 "<http://127.0.0.1:5500>")。务必验证此值!
  • event.source: 发送消息的 window 对象的引用。可用于向发送方回复消息。
更多详细信息,请参阅 MDN 文档:

实践:两个本地窗口间的通信

下面我们通过一个具体例子来演示 postMessage 的用法:页面 A 打开页面 B,之后两者相互发送和接收消息。
假设:
  • 页面 A 运行在 http://127.0.0.1:5501/pageA.html
  • 页面 B 运行在 http://127.0.0.1:5500/pageB.html (注意端口不同,构成跨源)
页面 A (发送方/接收方 - pageA.html)
页面 B (接收方/发送方 - pageB.html)
如何运行此示例:
  1. 将上述两段代码分别保存为 pageA.htmlpageB.html
  1. 你需要一个本地 Web 服务器来托管这两个文件,并且确保它们通过不同的源(端口号不同即可)访问。例如,你可以使用 Node.js 的 http-serverlive-server,或者 Python 的 http.server 模块。
      • 启动一个服务器监听 5501 端口,用于 pageA.html
      • 启动另一个服务器监听 5500 端口,用于 pageB.html
  1. 在浏览器中访问 http://127.0.0.1:5501/pageA.html
  1. 点击“打开页面 B 并发送问候”按钮,页面 B 应该会弹出一个新窗口或标签页。
  1. 现在你可以在两个页面的输入框中输入内容,并通过各自的发送/回复按钮进行通信。同时观察浏览器控制台的输出来了解消息传递过程。

安全至关重要

使用 postMessage 时,请牢记以下安全原则:
  1. 明确指定目标源 (targetOrigin): 发送消息时,尽可能使用具体的源地址,避免使用 "*",除非你完全确定不需要关心接收方的来源。
  1. 验证事件来源 (event.origin): 在接收消息的 message 事件监听器中,必须检查 event.origin 是否是你信任的、期望的发送方源地址。忽略来自未知或不可信源的消息。

总结

window.postMessage 是一个强大且必要的 Web API,它为跨源的浏览器上下文(窗口、iframe、Web Workers)提供了一种标准、安全的通信机制。通过理解其工作原理并严格遵守安全最佳实践,开发者可以构建功能丰富且安全的现代 Web 应用。

A发送给B

notion image
image.png

B发送给A

notion image
image.png
这样就完成了两个页面的相互通信。
在 Web 开发中,经常需要在父页面和嵌入的 iframe 子页面之间进行数据交换。无论父子页面是否同源,window.postMessage 都提供了一种安全、可靠的通信机制。
实现效果: 父页面加载 iframe 后向子页面发送消息,子页面接收并显示;子页面通过按钮点击向父页面发送消息,父页面接收并打印。
假设:
  • 父页面运行在 http://127.0.0.1:5501/parent.html (或其他源)
  • 子页面 (iframesrc) 运行在 http://127.0.0.1:5500/child.html

代码实现

父页面 (parent.html)
子页面 (child.html)

关键点总结

  1. 获取目标窗口引用:
      • 父 -> 子: 通过 iframeElement.contentWindow 获取子页面的 window 对象。
      • 子 -> 父: 通过 window.parentwindow.top 获取父级或顶级窗口的 window 对象。
  1. 发送时机 (父 -> 子): 必须在 iframeonload 事件触发后发送消息,确保子页面已加载并准备好接收。
  1. postMessage 参数:
      • 第一个参数 message: 要发送的数据。
      • 第二个参数 targetOrigin: 必须指定目标窗口的预期源。这是核心安全机制。如果源不匹配,消息不会发送(或在某些浏览器中发送但对方无法接收),并在控制台产生错误。使用 "*" 会绕过此检查,应仅在完全信任或无法预知目标源的情况下使用(非常不推荐)。
  1. 接收消息:
      • 双方都需要通过 window.addEventListener('message', callback) 来监听消息。
      • 在回调函数中,必须检查 event.origin 是否来自预期的、可信任的源。忽略来自未知源的消息。
      • event.data 包含接收到的数据。
      • event.source 是发送消息窗口的引用,可用于回复。
  1. 源 (Origin) 的精确匹配: targetOriginevent.origin 的比较是严格的字符串匹配。这意味着协议(http vs https)、主机名(localhost vs 127.0.0.1)和端口号都必须完全一致。如果父页面通过 http://localhost:5501 访问,而子页面向 http://127.0.0.1:5501 发送消息(或反之),就会因源不匹配而失败。

postMessage 用于 App (WebView) 与 H5 通信

除了浏览器窗口间通信,postMessage 也可以用于原生 App 内嵌的 WebView (H5 页面) 与 App 原生代码之间的通信。虽然 JSBridge 是更常见且功能更全面的解决方案(提供更丰富的接口、回调机制等),但对于简单的消息传递,postMessage 也是一种可行的选择。
以下以 Android 为例,演示基于 postMessage 的基础通信思路:

1. App (原生 Android) 发送消息给 H5 (WebView)

Android 端可以通过执行 JavaScript 来调用 H5 页面中的 window.postMessage
Android (Java/Kotlin):
H5 (WebView 内):

2. H5 (WebView) 发送消息给 App (原生 Android)

这通常需要 App 向 WebView 注入一个 JavaScript 接口 (使用 addJavascriptInterface),H5 通过调用这个接口的方法来间接传递消息。
Android (Java/Kotlin):
H5 (WebView 内):
注意: 使用 addJavascriptInterface 时需要特别注意安全问题 (Android 4.2 以下存在远程代码执行漏洞),确保只暴露必要且安全的方法。
对比 URL 拦截:
文案中提到的通过 window.location.href 修改 URL 来发送消息给 App (然后 App 通过 shouldInterceptRequest 拦截) 是一种较旧的技术,通常用于不支持 addJavascriptInterface 或需要兼容旧版本的情况。它不如 addJavascriptInterface + postMessage 模式直接和高效,且对 URL 长度有限制,传递复杂数据不方便。
总结:
postMessageiframe 和 App-H5 通信提供了基础的消息传递能力。在 iframe 场景下,它是标准且常用的方法。在 App-H5 场景下,虽然可用,但功能完备性通常不如成熟的 JSBridge 方案。无论哪种场景,严格的源校验 (targetOriginevent.origin) 都是保障通信安全的关键
notion image
img_v2_2e0b2f83-7716-4f61-a258-436c849b13fg.jpg
上一篇
Vite Plugin 优化 svg,就这么简单搞定!
下一篇
现代 CSS 解决方案:全尺寸的带圆角的渐变边框