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.opener、iframeElement.contentWindow 等),然后调用该引用的 postMessage() 方法。这会在目标窗口上触发一个 MessageEvent 事件,目标窗口可以通过监听这个事件来接收并处理消息。基本语法:
targetWindow: 目标窗口的引用,即接收消息的窗口对象(例如window.opener、iframe.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)如何运行此示例:
- 将上述两段代码分别保存为
pageA.html和pageB.html。
- 你需要一个本地 Web 服务器来托管这两个文件,并且确保它们通过不同的源(端口号不同即可)访问。例如,你可以使用 Node.js 的
http-server或live-server,或者 Python 的http.server模块。 - 启动一个服务器监听
5501端口,用于pageA.html。 - 启动另一个服务器监听
5500端口,用于pageB.html。
- 在浏览器中访问
http://127.0.0.1:5501/pageA.html。
- 点击“打开页面 B 并发送问候”按钮,页面 B 应该会弹出一个新窗口或标签页。
- 现在你可以在两个页面的输入框中输入内容,并通过各自的发送/回复按钮进行通信。同时观察浏览器控制台的输出来了解消息传递过程。
安全至关重要
使用
postMessage 时,请牢记以下安全原则:- 明确指定目标源 (
targetOrigin): 发送消息时,尽可能使用具体的源地址,避免使用"*",除非你完全确定不需要关心接收方的来源。
- 验证事件来源 (
event.origin): 在接收消息的message事件监听器中,必须检查event.origin是否是你信任的、期望的发送方源地址。忽略来自未知或不可信源的消息。
总结
window.postMessage 是一个强大且必要的 Web API,它为跨源的浏览器上下文(窗口、iframe、Web Workers)提供了一种标准、安全的通信机制。通过理解其工作原理并严格遵守安全最佳实践,开发者可以构建功能丰富且安全的现代 Web 应用。A发送给B
image.png
B发送给A
image.png
这样就完成了两个页面的相互通信。
在 Web 开发中,经常需要在父页面和嵌入的
iframe 子页面之间进行数据交换。无论父子页面是否同源,window.postMessage 都提供了一种安全、可靠的通信机制。实现效果: 父页面加载
iframe 后向子页面发送消息,子页面接收并显示;子页面通过按钮点击向父页面发送消息,父页面接收并打印。假设:
- 父页面运行在
http://127.0.0.1:5501/parent.html(或其他源)
- 子页面 (
iframe的src) 运行在http://127.0.0.1:5500/child.html
代码实现
父页面 (
parent.html)子页面 (
child.html)关键点总结
- 获取目标窗口引用:
- 父 -> 子: 通过
iframeElement.contentWindow获取子页面的window对象。 - 子 -> 父: 通过
window.parent或window.top获取父级或顶级窗口的window对象。
- 发送时机 (父 -> 子): 必须在
iframe的onload事件触发后发送消息,确保子页面已加载并准备好接收。
postMessage参数:- 第一个参数
message: 要发送的数据。 - 第二个参数
targetOrigin: 必须指定目标窗口的预期源。这是核心安全机制。如果源不匹配,消息不会发送(或在某些浏览器中发送但对方无法接收),并在控制台产生错误。使用"*"会绕过此检查,应仅在完全信任或无法预知目标源的情况下使用(非常不推荐)。
- 接收消息:
- 双方都需要通过
window.addEventListener('message', callback)来监听消息。 - 在回调函数中,必须检查
event.origin是否来自预期的、可信任的源。忽略来自未知源的消息。 event.data包含接收到的数据。event.source是发送消息窗口的引用,可用于回复。
- 源 (Origin) 的精确匹配:
targetOrigin和event.origin的比较是严格的字符串匹配。这意味着协议(httpvshttps)、主机名(localhostvs127.0.0.1)和端口号都必须完全一致。如果父页面通过http://localhost:5501访问,而子页面向http://127.0.0.1:5501发送消息(或反之),就会因源不匹配而失败。
<img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/IlE1Y2rl1ub9hExz8VLXrrEeAwNiaTPcgUU9AibzspRwX2C8aMTAmibmgetGgo255HeLCYuUSlRzuwXAwkz2Xib2MQ/640?wx_fmt=other&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1" alt="Origin mismatch error example">
(示例图:源不匹配导致的 postMessage 错误)
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 长度有限制,传递复杂数据不方便。总结:
postMessage 为 iframe 和 App-H5 通信提供了基础的消息传递能力。在 iframe 场景下,它是标准且常用的方法。在 App-H5 场景下,虽然可用,但功能完备性通常不如成熟的 JSBridge 方案。无论哪种场景,严格的源校验 (targetOrigin 和 event.origin) 都是保障通信安全的关键。img_v2_2e0b2f83-7716-4f61-a258-436c849b13fg.jpg
- 作者:90_blog
- 链接:https://blog.tri7e.com/article/app_h5
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
