必须用 window.open 主动打开预览窗口并严格校验 origin 和消息结构,否则存在 xss 风险;需闭环通信、nonce 防重放、targetorigin 写死、event.origin 全等校验、接收方前置校验且不传非法类型数据。

能实现,但必须用 window.open 主动打开预览窗口,且双方都得做 origin 校验和结构校验——否则编辑器改了内容,预览窗可能渲染恶意脚本,或根本收不到更新。
必须用 window.open 打开预览页,不能靠 URL 直接跳转后反向获取引用
编辑器页面调用 window.open('preview.html', 'preview') 后,返回值才是合法的、可通信的窗口引用。如果用户手动在新标签页打开 preview.html,window.opener 在跨域时会被浏览器清空,你拿不到它的引用,postMessage 就无从谈起。
- 预览页加载完成前别急着发消息:监听
win.onload或让预览页 DOMReady 后主动发一条{ type: 'READY' }通知编辑器 - 若预览页已存在(比如用户反复点“预览”),可用
window.open('', 'preview')复用并拿到引用,避免重复开窗 - 发消息前先检查
win.closed === false,否则静默失败,编辑器状态会卡住
发送时 targetOrigin 必须写死协议+域名+端口,不能用 "*"
写成 "*" 意味着任何页面都能冒充预览页接收消息。比如用户正开着一个钓鱼页面,它监听了 message 事件,就可能截获你传的 HTML 片段并执行 XSS。
- 开发环境常用
"http://localhost:3000",生产环境必须填真实域名,如"https://editor.example.com" - 如果预览页是多租户动态域名(如
https://tenant1.preview.example.com),需在打开前调用后端接口获取白名单 origin 列表,再做includes匹配(注意:只用于判断,发送时仍要填完整 origin) -
targetOrigin和接收方event.origin必须全等(===),不能用indexOf或正则模糊匹配
接收方必须校验 event.origin + data 结构,且敏感操作需 nonce
预览页收到消息后,仅靠 event.source 不可靠——它可能被复用或伪造;event.origin 才是唯一可信来源标识。
- 必须写
if (event.origin !== 'https://editor.example.com') return,漏掉!==或写成!=都可能绕过 -
typeof event.data === 'object'且event.data.type存在,才继续处理;否则忽略,防止原型污染或非预期类型触发异常 - 如果编辑器支持“运行代码”按钮,预览页执行前应比对
event.data.nonce是否与本地缓存一致,防止重放攻击
双向闭环结构建议用 type + payload + nonce 统一约定
单向发 HTML 字符串容易丢状态:编辑器发了,预览页卡在解析中,用户又改了一次,第一次的响应还没回来,第二次就覆盖了。闭环才能追踪每次变更。
- 编辑器发:
{ type: 'UPDATE_HTML', payload: '<p>hello</p>', nonce: 'abc123' } - 预览页收到后渲染,成功后回传:
{ type: 'HTML_RENDERED', nonce: 'abc123', timestamp: Date.now() } - 编辑器用
Map缓存未确认的nonce,超时(如 3s)未收到响应就标记为失败 - 回传时必须用
event.source.postMessage(..., event.origin),确保响应只发回原请求方,不广播
最容易被忽略的是:预览页的 event.origin 校验必须在所有逻辑分支前执行,哪怕只是 console.log 也得先过这一关;否则调试时加个日志就可能绕过防护。另外,postMessage 不传函数、不传 DOM 节点、不传 undefined,这些都会被序列化成 null 或直接丢弃——别指望靠它们传递控制权。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











