必须等iframe加载完成再发消息,用onload最稳妥;子页面需严格校验event.origin;移动端play需用户手势触发并用Promise链同步状态;消息结构应统一type字段及对应约束。

父页面发消息前必须等iframe加载完成
直接在iframe元素创建后立刻调用postMessage,大概率失败——因为contentWindow可能还没准备好,或子页面尚未解析完video元素。浏览器不会报错,但消息静默丢弃。
- 用
iframe.onload是最稳妥的时机,比DOMContentLoaded更可靠 - 如果子页面是CDN托管(如
https://d34gxw3jqlasaag.cloudfront.net/player.html),必须显式指定targetOrigin为完整协议+域名+端口,不能用"*" - 避免在
iframe.src赋值后立即发消息;哪怕加setTimeout也是赌运气,不是解法
子页面监听message时必须校验event.origin
不验证event.origin就执行video.play()或video.currentTime = ...,等于把控制权开放给任意网站。攻击者只需嵌入同名iframe就能触发播放、跳转甚至音量拉满。
- 校验必须严格匹配:比如父页是
https://shop.example.com,子页就要写if (e.origin !== 'https://shop.example.com') return - 不要用
indexOf或includes做模糊匹配,防止https://evil-shop.example.com绕过 -
event.source.postMessage(..., e.origin)回传时,也必须用e.origin而非硬编码,否则回信会被拦截
移动端video.play()失败几乎必然发生
iOS和Android要求play()必须由用户手势(click/tap)触发。通过postMessage发来的{ type: 'play' }属于程序调用,99%会抛NotAllowedError。
- 子页面应在
video上绑定一次touchstart或click,在回调里调用video.play()并resolve一个Promise,后续所有postMessage指令都等这个Promisesettled 后再执行 - 父页面发
play前,可先发{ type: 'check-ready' },子页面响应{ type: 'ready', canPlay: true/false },避免盲目重试 - 不要在
catch里静默吞掉错误——至少console.warn('play failed:', err),否则调试时完全无迹可寻
消息结构建议用type字段+明确payload约束
传{ cmd: 'seek', t: 120 }这种随意命名的字段,半年后维护时连自己都看不懂。更糟的是,父页传time: '120'(字符串)而子页直接赋给currentTime,会触发静音跳转或NaN异常。
立即学习“前端免费学习笔记(深入)”;
- 统一用
type作为动作标识:'play'、'pause'、'seek'、'volume' - 每个
type对应固定字段:例如seek只认time(number),volume只认value(0–1之间的number) - 子页面收到未知
type时,应console.warn('unknown message type:', cmd.type),而不是switch漏掉default分支
canplay事件可能早于用户第一次触摸,也可能晚于父页面发来的第一条play指令。这里没有银弹,只能靠Promise链 + 显式状态同步兜底。



















