postmessage 必须严格校验 event.origin 且用 ===,禁用模糊匹配;targetorigin 不可填 '*';需监听 iframe load 事件后再发消息;video.play() 需用户手势触发;消息结构须统一并校验。

postMessage 必须校验 event.origin,且只能用 ===
不校验 event.origin 就执行业务逻辑,等于把控制权交给任意网站。攻击者只需在自己页面里嵌一个同名 iframe,就能伪造消息触发 video.play()、location.href 或支付确认等敏感操作。
常见错误是用 includes 或 indexOf 模糊匹配:if (event.origin.includes('example.com')) ——这会被 https://evil-example.com 绕过。
- 必须写成
if (event.origin !== 'https://pay.gateway.io') return; - 多个合法源需显式白名单判断,不能放宽为子串匹配
- Safari 对
contentWindow.location.origin访问更严,部分版本返回null,子页不能缓存该值,每次都要现场读取
targetOrigin 不能填 '*',生产环境等于裸奔
postMessage 的第二个参数(targetOrigin)填 '*' 时,浏览器只在目标窗口当前 origin 与该值完全一致时才投递消息;不匹配就静默丢弃,连 Failed to execute 'postMessage' 错误都不会抛——调试时根本看不到失败痕迹。
- 本地开发可临时用
'http://localhost:3000',但上线前必须替换成真实协议+域名+端口,如'https://player.example.com:8080' - CDN 托管的子页(如
'https://d34gxw3jqlasaag.cloudfront.net/player.html')也要写死完整origin,不能靠自动推导 - 子页若重定向(如从
http跳https),contentWindow.location.origin会变,必须在load回调里立即读取,不能提前缓存
父页发消息前必须等 iframe load 事件
直接在 iframe 标签后立即调用 iframe.contentWindow.postMessage(),大概率失败——此时 contentWindow 还是 null。这不是异步延迟问题,而是 DOM 就没准备好。
- 正确做法:监听
load事件,且仅在该事件触发后才获取contentWindow - 别用
setTimeout硬等,不可靠;也别依赖iframe.onload属性写法(易被覆盖) - 如果子页加载失败(404/500),
load事件仍会触发,需配合error事件做兜底
子页 video.play() 在移动端总失败,不是 bug 是策略
iOS/Android 强制要求:video.play() 必须由用户手势(click、touchstart)触发。通过 postMessage 发来的 { type: 'play' } 属于程序调用,99% 抛 NotAllowedError。
- 子页应在
video元素上绑定一次touchstart或click,在回调里调video.play()并 resolve 一个Promise - 后续所有
postMessage指令(如seek、volume)都等这个Promisesettled 后再执行 - 父页可先发
{ type: 'check-ready' }探测,子页响应{ type: 'ready', canPlay: true },避免盲目重试
最易被忽略的是:消息结构不统一带来的隐性崩溃风险。传 { cmd: 'seek', t: 120 } 这种随意字段名,半年后维护时连自己都看不懂;更糟的是,父页传 time: '120'(字符串),子页直接赋给 video.currentTime,会静默失败或跳转错位。字段命名、类型、必选性必须在双方约定清楚,且加运行时校验。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











