必须用===严格比对event.origin,因模糊匹配(如includes)会被https://evil-example.com等恶意域名绕过,导致video.play()等敏感操作被任意网站触发;targetorigin填"*"等于裸奔,消息仅在完全匹配时投递,否则静默丢弃。

为什么event.origin必须用===严格比对
不校验event.origin就执行video.play()或location.href,等于把控制权交给任意网站。攻击者只需在自己页面里嵌一个同名iframe,就能伪造消息触发敏感操作。
常见错误是用includes或indexOf模糊匹配:if (event.origin.includes('example.com'))——这会被https://evil-example.com绕过。
- 必须写成
if (event.origin !== 'https://shop.example.com') return - Safari 对跨域
iframe.contentWindow.location.origin访问更严,部分版本返回null,所以子页不能依赖缓存,每次都要现场读取 - 如果父页有多个合法来源(如
https://a.com和https://b.com),需显式白名单判断,而不是放宽匹配
targetOrigin填"*"为什么在生产环境等于裸奔
浏览器只在目标窗口当前origin与targetOrigin完全一致时才投递消息;不匹配就静默丢弃,连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,不能依赖 DOM 插入顺序
移动端video.play()为什么总失败
这不是 bug,是 iOS/Android 强制策略:play()必须由用户手势(click、touchstart)触发。通过postMessage发来的{ type: 'play' }属于程序调用,99% 抛NotAllowedError。
- 子页应在
video元素上绑定一次touchstart或click,在回调里调video.play()并 resolve 一个 Promise - 后续所有
postMessage指令(如seek、volume)都等这个 Promise settled 后再执行 - 父页可先发
{ type: 'check-ready' }探测,子页响应{ type: 'ready', canPlay: true },避免盲目重试
消息结构不统一带来的隐性崩溃风险
传{ cmd: 'seek', t: 120 }这种随意字段名,半年后维护时连自己都看不懂;更糟的是,父页传time: '120'(字符串),子页直接赋给video.currentTime,会触发静音跳转或NaN异常。
- 统一用
type作为动作标识:'play'、'pause'、'seek'、'volume' - 每个
type对应固定字段约束:例如seek只认time(number 类型),volume只认value(0–1 之间的 number) - 子页收到未知
type时,应console.warn('unknown message', event.data),不响应也不抛错
真正容易被忽略的不是怎么发,而是谁来收、收完怎么验、验完怎么回——三条铁律缺一不可:不验证origin就处理 = 开门揖盗;不指定targetOrigin就发 = 广播敏感信息;不检查data结构就执行 = 执行任意命令。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











