必须用===严格比对event.origin,否则任意网站可通过同名iframe伪造消息触发video.play()等敏感操作;模糊匹配如includes会被evil-example.com绕过;多源需显式白名单判断。

为什么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 - 若父页有多个合法来源(如
https://a.com和https://b.com),需显式白名单判断:if (!['https://a.com', 'https://b.com'].includes(event.origin)) return - Safari 对跨域
iframe.contentWindow.location.origin访问更严,部分版本返回null,所以子页不能依赖缓存,每次都要现场读取
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)都等这个Promisesettled 后再执行 - 父页可先发
{ 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),不响应也不抛错 - 所有
postMessage调用建议包裹try...catch,防止目标窗口不可达时报错中断逻辑
iframe.onload回调里对contentWindow和location.origin的双重确认——很多线上故障就卡在这一步,既没报错,又没通信,查起来像幽灵问题。



















