跳转至

扩展 reload 生命周期(MV3)

当 Chrome 扩展被 reload / update 时,会发生什么?为什么 v0.10.29 和 v0.10.34 都要处理这件事?

触发"扩展 reload"的 5 种途径

# 触发 onInstalled.reason
1 首次安装 / 重装 install
2 Chrome Web Store 自动更新到新版本 update
3 开发者在 chrome://extensions 点🔄 reload 按钮(unpacked 模式) update
4 wxt dev 模式代码改动自动 reload update
5 Chrome 浏览器自身升级 chrome_update

reload 瞬间发生什么

T=0 ms     用户点 reload(或自动触发)
T+10 ms    Service Worker 进程被强制 terminate
           - 所有 setTimeout / setInterval 丢失
           - 内存 state(如 globalStatus)丢失,依赖 storage 持久化
           - alarms 不丢(由 Chrome 持久化)
T+50 ms    Chrome 重启 SW
           - background script 重新执行
           - 重建监听器、调度器
T+100 ms   onInstalled 触发 with details.reason
T+? ms     所有 chrome-extension://<id>/*.html tab 的状态:
           - URL 保持 chrome-extension://<id>/... 不变
           - 但 JS context 已死:
              · chrome.runtime.id → undefined(或抛错)
              · chrome.runtime.sendMessage → 抛 "Extension context invalidated"
              · onMessage listener 全部静默失效
           - 页面 DOM 还在显示(React 树还在),但所有 chrome API 调用都炸

旧版(v0.10.28 之前)的痛点

  • 用户在 main.html / settings.html 进行操作 → 全部失败 → 报错堆积
  • 用户按浏览器 F5/Cmd+R 刷新 → Chrome 此时拒绝 fetch 处于"invalidated"状态的 chrome-extension:// 资源 → 替换 tab 为 newtab
  • 用户看到自己工作台变成空白新标签页,一脸懵

v0.10.29 + v0.10.34 双轨保险

              ┌─────────────────────────────────────┐
              │ background SW 重启                    │
              │   onInstalled(reason='update')      │
              │       ↓                             │
              │   restoreExtensionTabs()  ←─ v0.10.34│
              │   遍历所有 chrome-extension://<id>/  │
              │   逐个 tabs.reload()                 │
              └─────────────────────────────────────┘
              ┌─────────────────────────────────────┐
              │ 页面侧 ext-context-guard.ts ← v0.10.29│
              │  - window.error 监听                 │
              │  - unhandledrejection 监听          │
              │  - 5s 周期 isContextValid() 检查    │
              │  失效 → 全屏 overlay → reload tab    │
              └─────────────────────────────────────┘
触发源 时机 角色 防什么 case
bg.onInstalled('update') SW ready 那一刻(毫秒级) 主动 用户没立刻按 F5,SW 主动救场
ext-context-guard.ts 5s 周期 + window.error 即时 被动兜底 onInstalled 偶发不触发 / 主动机制失效

主动机制先到(几乎零空窗),被动机制兜底(防主动机制失效)。

排查表(当用户报"刷新后变 newtab")

检查项 命令 期望
1. extension id 是否变了 对比 reload 前后 chrome://extensions/?id=... 不应变(变了 → manifest.key 未固定 → 走商店草稿拿 key)
2. SW 是否报错 DevTools → background service worker → Console 不应有红色异常
3. installListener 是否触发 console.log('reason=', reason) 复现 reload 应看到 reason=update
4. tab 是否被自动 reload onInstalled 后看 main tab url 应仍是 main.html,内容刷新
5. guard 是否生效 DevTools → main tab → Console 失效时应看到 Extension context invalidated

边角 case + 注意点

  • wxt dev 模式下:代码改动触发自动 reload,频率很高(每次保存)。主动机制会让所有打开的 tab 都刷新 — 如果用户在编辑 settings 表单时被刷掉,体验差。未来可加 dirty-form 检测,刷新前提示
  • reason='install' 不走 restoreExtensionTabs(也没必要 — 首次安装不会有已打开的 tab)。
  • reason='chrome_update' 走,因为浏览器升级也会让 chrome-extension context 失效。
  • SW 第一次启动 vs reload 启动 都触发 onInstalled,但 reason 不同。onStartup 仅在浏览器启动时触发,不能替代。

相关代码

文件 角色
src/entrypoints/background/index.ts:installListener 主动 — onInstalled('update') → restoreExtensionTabs
src/utils/ext-context-guard.ts 被动 — 3 重检测(error / rejection / 5s 周期)
所有 entry main.tsx installContextGuard() 注册

相关 issue / 文档