Skip to content

🐛 修复 Popup 全局关闭提示时有时无、且当前页脚本列表为空 - #1694

Open
CodFrm wants to merge 3 commits into
mainfrom
fix/popup-global-disabled-status
Open

🐛 修复 Popup 全局关闭提示时有时无、且当前页脚本列表为空#1694
CodFrm wants to merge 3 commits into
mainfrom
fix/popup-global-disabled-status

Conversation

@CodFrm

@CodFrm CodFrm commented Aug 28, 2026

Copy link
Copy Markdown
Member

Checklist / 检查清单

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

背景

用户报告:关掉全局「启用脚本」开关后,Popup 顶部的「脚本已全局关闭,开启上方开关并刷新页面后生效」
有时显示有时不显示;而一旦显示,「当前页运行脚本」列表就是空的。

两个现象来自 PopupService 的两段代码。

提示时有时无 —— getPageStatus() 把「本 tab 有没有 content script 报到」排在全局开关判据之前,
于是一个全局事实被标签页级、且会陈旧的证据否决:

if (await this.isTabInjected(tabId, url)) return "ok";   // ← 先看注入证据
if (!this.runtime.isLoadScripts) return "scripts-disabled";

注入证据存放在 session cache 的 tabLoaded:<tabId>,清理时机有两处:标签页关闭(clearData),
以及 webNavigation.onBeforeNavigaterunCountMap.has(tabId) && !isLoadScripts
runCountMap 是 Service Worker 里的内存 Map,SW 一被回收就空了,而 tabLoaded
chrome.storage.session 里活着。于是关开关后刷新页面:

  • SW 还活着 → 记录被清 → 下次开 Popup 提示;
  • SW 中途重启过 → 记录留存 → 判为 ok没有提示;
  • 新开的标签页从来没有记录 → 总是有提示。

同一个开关状态下,提示出不出现取决于 SW 有没有被回收过——这就是「有时候显示有时候不显示」。

列表为空 —— getPopupData() 对任何非 ok 状态直接返回 scriptList: []。这是 #1687#1689
为了「chrome:// 等触及不到的页面上不该列脚本」加的,但当时按「所有非 ok 状态」一刀切,
把「扩展被关掉」「在黑名单里」这些原因可以解除的情况也一起清空了。

本次改动

src/app/service/service_worker/popup.ts,两处:

  1. getPageStatus():把 userscripts-unavailable / scripts-disabled 移到注入证据之前。
    这两个是「扩展整体没跑起来」(registerUserscripts() 直接 return,content script 根本没注册),
    与哪个标签页无关,不该被某个 tab 的历史记录否决。
  2. getPopupData():空列表规则由 pageStatus !== "ok" 收窄为 pageStatus === "restricted"

改动后的行为:

pageStatus 顶部提示 当前页脚本列表
ok 匹配到的脚本
scripts-disabled / userscripts-unavailable / blacklist / file-access-denied / not-injected 有(说明原因) 匹配到的脚本(本次恢复
restricted(浏览器保留页、自家扩展商店) 空(#1687 行为保留)

实现考虑

  • 判定顺序是这次的核心。restricted / blacklist 依旧最先判——它们是「无论如何都不会注入」的确定结论;
    接着才是两个全局判据;注入证据退到它们之后,只用于区分 oknot-injected,以及给
    扩展商店页 / file:// 权限一个更准确的原因。file:// 权限查询与商店域名单仍然不能否定
    已注入成功的事实(Firefox 上该查询与实际可注入性并不总是一致),这部分未动。
  • 哪些状态该列脚本,界线是「抑制原因能不能解除」:开开关、开开发者模式、移出黑名单、
    给文件访问权限、刷新页面——这些解除后脚本就会在本页跑,列出来用户才知道解除后会生效,
    而顶部提示已经说明了它们现在为什么没跑。只有 restricted 是解除不了的,保持空列表。
  • 匹配数据在脚本被关停时依然可用scriptMatchEnablewaitInit() 阶段预热,
    不受 isLoadScripts / isUserScriptsAvailable 影响,因此全局关闭时仍能列出匹配脚本
    (已在真实 Chrome 中确认,见下)。
  • 一条既有测试契约被本次改动推翻:关掉开关不会杀死已注入页面上正在跑的脚本,该页仍应为 ok
    它的出发点(关开关不会杀死已在跑的脚本)没错,但 tabLoaded 只能证明「本 tab 曾经报到过」,
    刷新之后它依然为真,无法区分「脚本还在跑」和「刷新后再也不会跑」,用它压掉提示会误导用户。
    新契约是「提示不取决于标签页新旧」,并把该测试改写为对应用例。

已知限制

  • file-access-denied 列出脚本这一条只有单元测试证据,没有在浏览器里构造
    file:// 页 + 未授予文件访问 + 带 @match file:///* 的脚本」这一组合。
  • 未改动 onBeforeNavigate 里那段带条件的缓存清理。它现在不再影响提示,
    但仍然决定 tabScript:<tabId> 运行计数何时被清;那是另一件事,本次不扩大范围。

建议审查重点

  • getPageStatus() 的新顺序:确认把两个全局判据提到注入证据之前,没有让「实际能运行的页面」
    被误报(关键在于这两个条件成立时 content script 确实没注册)。
  • restricted 是否是唯一应该保持空列表的状态([BUG] popup当前页运行脚本展示的并不是实际运行的脚本 #1687 的原始诉求是 chrome://)。
  • 全局关闭时列表里各行的状态标记:runNum 来自 tabScript:<tabId> 缓存,关开关前跑过的页面
    仍会显示「运行中」——这是准确的(那些脚本确实还活着),但请确认这是期望的呈现。

参考

  • src/app/service/service_worker/popup.tsgetPageStatus() / getPopupData()
  • src/app/service/service_worker/runtime.tsisLoadScriptsenable_script 监听与 unregisterUserscripts()

关联

验证

单元测试(TDD):两轮先写 RED 再实现。

# 第一轮(scripts-disabled / userscripts-unavailable)
npx vitest run src/app/service/service_worker/popup.test.ts   → 3 failed | 50 passed
# 第二轮(blacklist / not-injected / file-access-denied)
npx vitest run src/app/service/service_worker/popup.test.ts   → 3 failed | 50 passed
# 实现后(提交态,独立 worktree)
npx vitest run src/app/service/service_worker/popup.test.ts   → 52 passed (52)
npx vitest run src/pages/popup                                → 69 passed (69)
npx tsc --noEmit                                              → 0 error
npx eslint <两个改动文件>                                       → 0 problem

真实 Chrome 手工验证pnpm run build 后用 e2e/session.mjs 驱动,本地脚本 @match http://127.0.0.1:8971/*):

场景 结果
全局关闭 + 关开关前已注入过的标签页 scripts-disabled + 列出脚本(改动前:ok,无提示)
全局关闭 + 新开的标签页(session 中确认无 tabLoaded 键) scripts-disabled + 列出脚本(改动前:有提示、空列表)
黑名单页(设置页写入 *://127.0.0.1/* blacklist + 列出脚本
无报到记录的页面 not-injected + 列出脚本
重新打开开关并刷新 恢复 ok + 列出脚本
Popup 自身扩展页(restricted 提示照常 + 列表为空(#1687 行为保留)

关开关后直接观察到 tabLoaded:<tabId> 仍然存在,这正是旧代码返回 ok、吞掉提示的那一步。

Screenshots / 截图

未附图(终端会话截图无法直接上传)。改动后 Popup 在全局关闭状态下的实际渲染文本,
取自会话中真实 Popup 页面(把被测标签页设为窗口活动标签后重载 popup.html):

脚本已全局关闭,开启上方开关并刷新页面后生效
ScriptCat
当前页运行脚本 (1/1)
Global Off Verify
开启和运行的后台脚本 (0/0)

Popup 的页面状态判定把「本 tab 有没有 content script 报到」排在全局开关之前,
于是一个全局事实被标签页级、且会陈旧的证据否决:

- 注入证据存放在 session cache 的 tabLoaded:<tabId>,只在标签页关闭时、
  或 onBeforeNavigate 且 runCountMap.has(tabId) && !isLoadScripts 时才清理。
  runCountMap 是 Service Worker 的内存 Map,SW 一被回收就空了。
- 结果:关掉全局开关后刷新页面,SW 还活着 → 记录被清 → 有提示;
  SW 中途重启过 → 记录留存 → 判为 ok → 没提示。新标签页则总是有提示。
  同一个开关状态下提示时有时无。

改为把 userscripts-unavailable / scripts-disabled 两个「扩展整体没跑起来」的
判据移到注入证据之前——它们与哪个标签页无关,不该被某个 tab 的历史否决。

同时收窄 #1687 引入的空列表规则:原本任何非 ok 状态都返回空的 scriptList,
现在只有 restricted(浏览器保留页、自家扩展商店)保持空——那里脚本猫永远触及
不到。其余状态的抑制原因都可以解除(开开关、开开发者模式、移出黑名单、给文件
访问权限、刷新页面),照常列出按网址匹配的脚本,用户才知道解除后哪些会生效;
顶部提示已经说明了它们现在为什么没跑。
@cyfung1031

Copy link
Copy Markdown
Collaborator

@CodFrm 你的AI 是實際跑瀏覽器的操作嗎?感覺不太可信
本身popup這塊寫得不好
讓AI 搞可能會亂搞一通

@CodFrm

CodFrm commented Aug 28, 2026

Copy link
Copy Markdown
Member Author

@CodFrm 你的AI 是實際跑瀏覽器的操作嗎?感覺不太可信 本身popup這塊寫得不好 讓AI 搞可能會亂搞一通

我是自己用的时候发现的这个bug,修复验证交给AI

有时候自己也会看代码和再验证

Copy link
Copy Markdown
Collaborator

重工审计结论

已基于原始 head 183c451a54d3f5dd69bd1076f45fd6485aa234c0 完成重工,并正常 fast-forward 到最终 head a07dfd432e8f864a8600997c4770edd7e4b32417

  • 保留 PR 的根因修复:getPageStatus() 先判断全局 userscripts-unavailable / scripts-disabled,不再让陈旧的 tab 注入记录否决全局状态。
  • correction commit a07dfd43:恢复 Popup 契约——所有非 ok 状态的当前页脚本列表均为空,避免把匹配候选误显示为正在运行;同步补齐 blacklist、not-injected、file-access-denied、旧/新 tab 全局关闭、UserScripts 不可用的回归断言。
  • 本地验证:Popup service 52/52、Popup UI 69/69、typecheck、针对性 ESLint、完整 lint、build 均通过。
  • Chromium throwaway probe:脚本先在旧 tab 实际运行;关闭全局开关后,旧 tab 与新 tab 都返回 scripts-disabledscriptList: []backScriptList: []
  • 未覆盖:Firefox/Edge;file:// 未授权组合仍只有单元测试证据;未改动也未重新判定 onBeforeNavigate 的缓存清理策略。

PR 标题、正文和 API 契约未修改。

@CodFrm

CodFrm commented Aug 28, 2026

Copy link
Copy Markdown
Member Author

@cyfung1031 改错了吧,这样就是个空列表,只是因为当时的情况不能运行脚本,展示还是要展示吧

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