Vue Iframe

这是一个非常典型且令人抓狂的 "iframe 通信信噪比" 问题。

根据你提供的代码、调用栈以及"火狐浏览器特有"这一特征,结论如下:

核心结论:问题的根源

问题的根源不在于子系统(iframe内部)的代码,也不在于你父系统的业务逻辑,而在于中间的 smb/iframe-package (ParentIframe) 库的 init 方法实现过于简陋,缺失了关键的安全性校验。

这个库在监听 message 事件时,没有校验消息的来源(Origin)和发送者(Source) ,导致它把火狐浏览器中浏览器插件(Extensions)开发者工具 或者父窗口自身 产生的非业务 postMessage 消息,错误地当做了子系统发送的 mask-toggle 消息进行处理。


一、 为什么控制台 [原生监测] 没有打印?

你提到:"native 的 addEventListener 没打印,但库的监听器却触发了业务逻辑"。这看似矛盾,其实是因为 执行时机(Timing) 的问题。

请看你的本地调用栈:

  1. Index.vue:407 setup -> 调用 initializeIframe

  2. index.js:204 ParentIframe -> 实例化库。

  3. index.js:217 init -> 库内部 同步 执行了 window.addEventListener

解释:

在 Vue 组件的 setup 阶段,库已经完成了初始化并开始监听。而在火狐中,某些插件或环境噪声(比如 React/Vue DevTools,或者某些密码管理器)会在页面加载瞬间广播 postMessage

  • 库的监听器 :在 setup 时刻已经挂载,刚好捕捉到了这个瞬间产生的"噪音"消息。

  • 你的原生监听器 :如果你的 window.addEventListener 是写在 onMounted 里,或者在 setup 中位于 initializeIframe 之后执行,那么在它挂载成功之前,那个"鬼影消息"已经发生并结束了。

这就解释了为什么库捕获到了,而你的原生监听器错过了。


二、 为什么只有火狐有这个问题?

Chrome 和 Edge 对 postMessage 的处理相对"干净",或者它们的插件生态在广播消息时比较克制。

但在火狐(Firefox)中,浏览器自身的某些机制(如无障碍服务、UI 渲染计算通知)或安装的第三方插件,经常会使用 postMessage 在 window 内部通信。

漏洞复现逻辑:

  1. 火狐某插件发送了一条消息:{ type: 'something-else', payload: ... } (或者仅仅是一个对象)。

  2. ParentIframe 库的代码缺陷

    JavaScript

    复制代码
    // 库的源码
    window.addEventListener("message", (event) => {
      if (event.data) { // 只要有 data 就进来了!
        try {
           const data = event.data;
           if (data.type === "micro_app_heartbeat") {
              // ...
           } else {
              this.emit(data); // 🚨 致命错误:这里无差别地 emit 了所有消息!
           }
        }
      }
    });
  3. 如果这条噪音消息恰好包含 type: 'mask-toggle'(概率极低),或者更可能的是:你的 ParentIframe 库在 emit 没有任何 type 的数据时,后续逻辑产生了默认行为,或者之前的某些缓存数据导致了误判。

    • 更正猜测 :根据你的描述,事件名确凿是 mask-toggle。这极有可能是 Vue DevtoolsVite HMR (热更新) 甚至是你项目中引入的其他第三方库在火狐下不仅发了消息,而且被这个库错误转发了。

三、 解决方案

必须修改 ParentIframe 库的源码。由于这是一个 npm 包,你不能直接改 node_modules(CI/CD 会覆盖),建议使用 patch-package 来打补丁,或者如果这个库是你们公司内部维护的,请联系维护者紧急修复。

修复方案:增加 Origin 和 Source 校验

我们需要修改 ParentIframe 类的 init 方法,让它只接受来自它所管理的 iframe 的消息

修改前的代码(存在漏洞):

JavaScript

复制代码
init() {
  window.addEventListener("message", (event) => {
    if (event.data) {
      try {
        const data = event.data;
        // ... 逻辑 ...
      } catch (err) { console.log(err); }
    }
  });
}

修改后的代码(安全):

JavaScript

复制代码
init() {
  window.addEventListener("message", (event) => {
    // 1. 安全校验:如果消息里没有 data,直接忽略
    if (!event.data) return;

    // 2. 🚨 关键修复:校验消息来源是否在我们的白名单 iframe 列表中
    // 我们需要遍历所有注册的 iframe,看 event.source 是否等于其中某一个的 contentWindow
    let isTrustedSource = false;
    
    // 遍历 this.iframeNode (Map结构)
    for (const [name, iframeEl] of this.iframeNode.entries()) {
      if (iframeEl && event.source === iframeEl.contentWindow) {
        isTrustedSource = true;
        // 可选:你还可以校验 event.origin === this.childOrigin.get(name)
        // 但 event.source 校验通常已经足够强壮
        break; 
      }
    }

    // 如果发送消息的不是我们管理的 iframe(比如是插件、父窗口自己),直接丢弃
    if (!isTrustedSource) {
        // console.warn('收到非受信任来源的消息,已拦截', event);
        return; 
    }

    try {
      const data = event.data;
      if (data.type === "micro_app_heartbeat") {
         // ... 原有心跳逻辑 ...
      } else {
         this.emit(data);
      }
    } catch (err) {
      console.log(err);
    }
  });
}

运行

四、 如果无法修改库代码,如何在业务层规避?

如果你暂时无法修改库代码,你必须在业务层(你的 handleMaskToggle)做"二次防御"。但是,因为库已经剥离了 event 对象,只透传了 data,你在业务层无法校验 event.source。这是一个死局。

唯一可行的临时业务层规避方案(脏办法):

在子系统发送 mask-toggle 时,携带一个时间戳签名

  1. 修改子系统 (Iframe)

    发送消息时:
    JavaScript

    复制代码
    window.parent.postMessage({
      type: 'mask-toggle',
      data: { visible: true },
      _timestamp: Date.now(),
      _sender: 'my-subsystem-v1' // 标识身份
    }, '*');
  2. 修改父系统 (Handle Logic)

    JavaScript

    复制代码
    iframeCtrl.on('mask-toggle', (msg) => {
       // 过滤掉没有签名的"鬼影消息"
       if (msg._sender !== 'my-subsystem-v1') {
          console.warn('收到不明来源的 mask-toggle,忽略');
          return;
       }
       // 过滤掉陈旧消息(比如超过2秒前的)
       if (Date.now() - msg._timestamp > 2000) {
          return;
       }
    
       handleMaskToggle(msg.data?.visible)
    })

总结

  1. 现象原因ParentIframe 库没有校验 event.source,导致火狐浏览器环境下的插件或内部通信噪音被误认为是 iframe 消息。

  2. 监控失效原因:噪音消息发生在页面初始化瞬间,早于你的原生监控器挂载。

  3. 最佳解决 :修改 ParentIframe 库,在 initmessage 监听中增加 if (event.source !== iframe.contentWindow) return 的校验。

相关推荐
阿部多瑞 ABU2 小时前
`tredomb`:一个面向「思想临界质量」初始化的 Python 工具
前端·python·ai写作
比特森林探险记2 小时前
React API集成与路由
前端·react.js·前端框架
爱上妖精的尾巴2 小时前
8-1 WPS JS宏 String.raw等关于字符串的3种引用方式
前端·javascript·vue.js·wps·js宏·jsa
hvang19882 小时前
某花顺隐藏了重仓涨幅,通过chrome插件计算基金的重仓涨幅
前端·javascript·chrome
Async Cipher2 小时前
TypeScript 的用法
前端·typescript
web打印社区3 小时前
vue页面打印:printjs实现与进阶方案推荐
前端·javascript·vue.js·electron·html
We་ct3 小时前
LeetCode 30. 串联所有单词的子串:从暴力到高效,滑动窗口优化详解
前端·算法·leetcode·typescript
木卫二号Coding3 小时前
Docker-构建自己的Web-Linux系统-Ubuntu:22.04
linux·前端·docker
daols883 小时前
vue2 甘特图 vxe-gantt 一行渲染多个子任务的配置
vue.js·甘特图·vxe-table