扩展 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 / 文档