Skip to content

🐛 修复消息层三个隐性运行时问题:广播 lastError 未检查、SW→offscreen 未就绪竞态、offscreen 创建失败永久卡死#1581

Open
cyfung1031 wants to merge 3 commits into
scriptscat:mainfrom
cyfung1031:claude/extension-runtime-issues-160573
Open

🐛 修复消息层三个隐性运行时问题:广播 lastError 未检查、SW→offscreen 未就绪竞态、offscreen 创建失败永久卡死#1581
cyfung1031 wants to merge 3 commits into
scriptscat:mainfrom
cyfung1031:claude/extension-runtime-issues-160573

Conversation

@cyfung1031

Copy link
Copy Markdown
Collaborator

Checklist / 检查清单

  • Fixes mentioned issues / 修复已提及的问题
  • Code reviewed by human / 代码通过人工检查
  • Changes tested / 已完成测试

Description / 描述

本 PR 修复消息层(SW ↔ offscreen / 跨上下文广播)中三个现有 vitest / e2e 均无法暴露的隐性运行时问题。每个问题均先补充失败测试再修复(TDD),共新增 12 个单元测试。

问题 1:MessageQueue.publish 未检查 chrome.runtime.lastError

现象:广播消息(enableScriptsvalueUpdate 等)在无其他上下文存活时产生 Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.;MV3 下不传回调时 chrome.runtime.sendMessage 返回 Promise,直接变成 unhandled rejection。Firefox MV3(事件页、无 offscreen 文档)下后台每次广播都会触发;Chrome 在 SW 启动早于 offscreen 创建的窗口期同样会触发。

根因packages/message/message_queue.tspublish() 以 fire-and-forget 方式调用 chrome.runtime.sendMessage 且不传回调,lastError 永远无人读取。项目自有 ESLint 规则 require-last-error-check 只能约束"有回调但未检查"的形态,无回调的形态是盲区。

修复:传入回调并读取 chrome.runtime.lastError;无接收方属于广播的正常情况,仅记录 trace 日志,不再污染控制台/产生 unhandled rejection。

问题 2:SW→offscreen 消息在 offscreen 客户端未就绪时崩溃(消息静默丢失)

现象:SW 启动后、offscreen 文档创建完成前,任何经 ServiceWorkerMessageSend 发往 offscreen 的消息(如 runtime/valueUpdaterunScript)抛出 TypeError: Cannot read properties of undefined (reading 'postMessage'),消息静默丢失且无可诊断信息。

根因packages/message/window_message.tsinit() 只做一次 clients.matchAll() 查找,查不到时 this.targetundefinedconnect() / sendMessage() 用非空断言 this.target! 直接发送。另外 target 一旦缓存永不刷新,offscreen 重建后旧 Client 引用已失效。

修复

  • 发送前轮询等待 offscreen 客户端出现(200ms 间隔、上限 30s,多个并发发送共享同一次等待);超时以可诊断的错误拒绝,调用方(sendMessage 的 Promise 链)能正常捕获。
  • 收到来自 offscreen 的消息时用其 source(即当前有效客户端)刷新 target,并校验 source.url 必须等于 offscreen 文档 URL,防止其他页面污染 target。

问题 3:offscreen 文档创建一次失败后永久卡死,直至 SW 重启

现象chrome.offscreen.createDocument 一旦拒绝(启动资源压力、与关闭中的文档竞态出现 "Only a single offscreen document may be created" 等暂时性失败),本次 SW 生命周期内 offscreen 环境永远无法建立——后台脚本、定时脚本、沙箱全部失效,且伴随 unhandled rejection。

根因src/service_worker.tscreating latch 在 Promise 拒绝后停留在 rejected 状态且永不重置;main()setupOffscreenDocument() 也没有任何错误处理。

修复:抽出为可独立测试的 src/app/service/service_worker/offscreen_setup.ts 模块:

  • 创建失败按退避(500ms / 1s / 2s)重试;
  • 失败后复查文档存在性,与检查之间的 TOCTOU 竞态(文档实际已存在)视为成功;
  • 持续失败时返回 false 并重置 latch,后续调用可重新尝试;不再产生 unhandled rejection;
  • 并发调用仍共享同一次创建(保持原语义);Firefox(无 chrome.offscreen)行为不变。

测试 / Test results

  • 新增 12 个单元测试(先写失败测试确认能复现,再修复转绿):
    • packages/message/message_queue.test.ts:publish 必须传回调并在回调中读取 lastError;
    • packages/message/window_message.test.ts:客户端未出现时等待后发送、始终不可用时描述性拒绝、来源刷新 target、非 offscreen 来源不污染 target;
    • src/app/service/service_worker/offscreen_setup.test.ts:已存在跳过创建、并发共享、TOCTOU 视为成功、退避重试、持续失败后 latch 重置、Firefox 降级。
  • 全量单测:vitest run 294 文件 / 3142 用例全部通过。
  • pnpm run typecheck、ESLint、pnpm run build(Rspack 生产构建)通过。
  • e2e 冒烟:background-script.spec.ts(offscreen 路径)、gm-api.spec.tspopup.spec.ts 全部通过。

Screenshots / 截图

无 UI 变更 / No UI changes.

🤖 Generated with Claude Code

cyfung1031 and others added 3 commits July 15, 2026 08:15
广播为 fire-and-forget,其他上下文(offscreen/popup/options)可能不存在或不回应;
不传回调时 MV3 的 chrome.runtime.sendMessage 返回的 Promise 会产生 unhandled rejection。
现传入回调并读取 chrome.runtime.lastError,无接收方等正常情况仅记录 trace。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ServiceWorkerMessageSend 此前用 clients.matchAll() 单次查找并以 this.target! 直接发送:
SW 启动早于 offscreen 文档创建完成时 target 为 undefined,抛出
TypeError: Cannot read properties of undefined (reading 'postMessage'),消息静默丢失。

现改为轮询等待 offscreen 客户端出现(上限 30s),超时以可诊断错误拒绝;
并在收到来自 offscreen 的消息时刷新 target 引用,避免持有已失效的旧 Client。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
setupOffscreenDocument 的 creating latch 在 createDocument 拒绝后停留在 rejected 状态:
后台/定时脚本沙箱在本次 SW 生命周期内永远无法建立,且产生 unhandled rejection。

抽出为可测试的 offscreen_setup 模块:创建失败按退避重试(500ms/1s/2s),
与存在性检查间的 TOCTOU 竞态(文档实际已存在)视为成功,
持续失败时重置 latch 允许后续调用重新尝试。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@CodFrm

CodFrm commented Jul 15, 2026

Copy link
Copy Markdown
Member

这是AI检查出来的吗?虽然看起来有道理,但是没有确定明确的问题,感觉不做改动最好,AI很喜欢做无意义的兜底

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants