不能直接发送裸字符串或忽略event.origin校验,因为这等于将控制权交给任意网站;必须全等匹配origin、白名单兜底、校验event.source和event.data结构,且iframe需onload后才通信。

为什么不能直接用 postMessage 发送裸字符串或不验 event.origin
因为这等于把门钥匙交给所有路过窗口。浏览器允许跨域发消息,但不自动过滤来源——postMessage 本身不带身份认证,只靠开发者手动校验。一旦父页用 "*" 当 targetOrigin,或子页忽略 event.origin 直接执行 eval() 或 DOM 操作,恶意页面就能伪造消息注入脚本、窃取 token、触发支付。
怎么确保 iframe 加载完成再发消息,而不是 DOMContentLoaded 就调用 contentWindow.postMessage
常见错误是把 iframe 插入 DOM 后立刻发消息,结果 contentWindow 还没初始化,报 Cannot read property 'postMessage' of null 或静默失败。
- 必须监听
iframe.onload事件,不是window.addEventListener('DOMContentLoaded') - 动态创建的 iframe(如
document.createElement('iframe'))也要等其load触发后才能访问contentWindow - 如果子页是 SPA,首次加载后可能路由切换但不刷新 iframe,此时需额外约定通信协议(如发
{ type: 'ready' }表示 JS 上下文就绪)
子页如何验证 event.origin 才算真正安全
只检查 event.origin.includes('a.com') 是危险的——evil-a.com 也能通过。必须做全匹配校验。
- 写死比对:
if (event.origin !== 'https://a.com') return,协议、域名、端口缺一不可 - 若父页有多个合法源(如
https://a.com和https://staging.a.com),可用数组白名单:const allowedOrigins = ['https://a.com', 'https://staging.a.com']; if (!allowedOrigins.includes(event.origin)) return - 顺带校验
event.source === window.parent,防非预期窗口冒充(比如弹窗劫持) -
event.data必须解析后做字段校验:比如只处理type === 'play'且id是字符串,忽略多余字段
视频类场景中,为什么 play() 命令在子页里也常静默失败
这不是通信链路问题,而是 HTML5 视频的播放策略限制:跨域 iframe 中的 <video></video> 无法被父页直接调用 play(),且子页内部执行也受制于 readyState 和用户手势。
- 子页收到
{ type: 'play', id: 'v123' }后,不能立刻调video.play(),得先等video.readyState >= 4或监听canplay事件 - iOS/Android 要求首次播放必须由用户真实点击/触摸触发,所以父页发命令前,子页得先有一个合法的手势上下文(比如用户点过“播放”按钮)
- 建议子页在初始化时主动发
{ type: 'ready', canPlay: true }给父页,让父页知道当前是否具备播放条件
真正难的不是怎么发消息,而是两边都守住边界:父页不越界操作子页 DOM,子页不信任任何未校验的 origin,连回传消息都必须用 event.origin 而不是硬编码目标地址。漏掉任意一环,隔离就形同虚设。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











