Skip to content

🐛 修复安装页回退/关闭标签逻辑:改用 chrome.tabs.goBack 并区分同标签重定向#1594

Open
cyfung1031 wants to merge 9 commits into
scriptscat:mainfrom
cyfung1031:codex/fix-1588-history-go
Open

🐛 修复安装页回退/关闭标签逻辑:改用 chrome.tabs.goBack 并区分同标签重定向#1594
cyfung1031 wants to merge 9 commits into
scriptscat:mainfrom
cyfung1031:codex/fix-1588-history-go

Conversation

@cyfung1031

@cyfung1031 cyfung1031 commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Checklist / 检查清单

背景

安装页交接结束后,ScriptCat 需要把被拦截的标签页带回安装前的页面。原实现在 Service Worker 的 finally 中用 chrome.scripting.executeScript()req.tabId 注入并执行 history.back()——issue #1588 指出这条路径的语义不对:这里要表达的只是“让指定标签页回到上一页”,不需要向页面注入任意代码,也不应该依赖目标页面是否允许脚本注入。

第一版修复(PR 原提交)把它换成了消息通信方案:Service Worker 用 chrome.tabs.sendMessagecontent/historyBack,由 content script 里新增的 historyBack 处理器执行 history.back()。这个方案本身没有走 issue 建议的优先级——它跳过了“优先使用浏览器标签页导航 API”,直接引入了一条新的常驻消息通道,而这条通道又依赖 ScriptCat 自身的 content script 已经被注入到目标标签页。经复核,这个依赖并不总是成立,会导致静默失效(见下)。

复核过程中同时发现两个与本 issue 同源、但更靠近“安装页离开方式”本身的问题,一并在本 PR 中修了。

本次改动

1. 回退逻辑改为直接使用浏览器标签页导航 API(src/app/service/service_worker/script.ts

  • 移除 chrome.tabs.sendMessage(..., "content/historyBack") 消息通道,改为直接调用 chrome.tabs.goBack(req.tabId)。该 API 只依赖已经必需的 tabs 权限(src/manifest.json),不依赖 chrome.userScripts、content script 注入状态或黑名单配置,符合 issue [提案] 将安装后的返回操作改为由标签页侧上下文执行 #1588 里“优先使用标签页历史导航 API”的验收标准。
  • 补充判断:仅当 req.tabId 仍是当前激活标签页时才执行回退,避免用户已经切走后台标签页时被意外拉回历史记录。
  • 移除第一版修复中新增的 content/historyBack 消息处理器(src/app/service/content/script_runtime.ts)及其回归测试,改动收敛为无新增消息通道。

2. 安装页离开方式区分「独立新标签」与「同标签内被重定向」(src/pages/install/useInstallData.ts

复核第一版修复时发现:install.html 有两种打开方式——通过 chrome.tabs.createopenInCurrentTab())打开的专用新标签,以及由 declarativeNetRequest 的 redirect 规则在用户当前浏览标签内就地替换出来的标签(例如点击 GitHub 上的 raw .user.js 链接)。旧代码里 install() / close() / installSkill() / cancelSkill() 全部无条件 window.close(),在后一种场景下会把用户正在使用的浏览标签整个关掉,而不只是关闭安装页。

新增 leaveInstallPage()window.history.length > 1(同标签内被重定向而来,仍有上一页可回)时改用 window.history.back();只有确认是独立新标签(history.length === 1)时才 window.close()。四处调用点统一改用该函数。

3. 安装页 iframe 防嵌入拦截的翻译缺失与拦截时机问题(src/pages/install/App.tsx

  • t("invalid_page_access") 引用的 key 在任何语言、任何命名空间下都不存在(defaultNScommoninstall/common 都没有这个 key),界面会直接显示原始 key 字符串而非译文;src/locales/i18n-usage.test.ts 的静态扫描可复现。改为正确带命名空间前缀的 install:frame_blocked_title / install:frame_blocked_desc,并补全 9 个 locale 的翻译。
  • 拦截判断原本放在 loading/invalid/error/skill 等提前 return 之后,意味着安装页在这些状态下完全不受 iframe 保护。改为紧跟在所有 Hook 调用之后、任何状态分支之前执行(不能放在 Hook 调用之前,否则违反 React Hooks 规则)。
  • 用已有的 InstallError 组件替换裸 <div>,与页面其余错误态保持一致的设计与可关闭交互。

实现考虑

  • chrome.tabs.goBack 在 Chrome 88+(MV3 基线)与 Firefox 均以 Promise 形式可用,不需要回调式 chrome.runtime.lastError 处理;错误通过 .catch/try-catch 记录,不中断安装流程。
  • leaveInstallPage()history.length 而非某个显式标志区分两种打开方式:chrome.tabs.create 新开的标签 history.length 恒为 1,declarativeNetRequest 就地重定向的标签会保留重定向前的浏览历史(length > 1),该启发式覆盖了当前所有已知的打开路径。
  • iframe 拦截必须在所有 useEffect/useState 调用之后才能提前 return,否则会在“非顶层 frame”与“顶层 frame”两种渲染路径之间产生不同的 Hook 调用序列。

已知限制

  • 未新增覆盖 listenerScriptInstallchrome.webNavigation.onBeforeNavigate 回调)本身的单元测试:仓库目前没有 chrome.webNavigation / chrome.tabs.goBack 的 mock 基础设施,补齐属于比本次修复更大的独立工作,未在本 PR 中展开。

建议审查重点

  • chrome.tabs.goBack 调用前的“仅当前激活标签页”判断(src/app/service/service_worker/script.ts)是否符合预期的用户可感知行为。
  • leaveInstallPage()history.length 启发式是否覆盖了所有已知的 install.html 打开入口(chrome.tabs.create 新标签、declarativeNetRequest 就地重定向)。
  • 9 个 locale 的 frame_blocked_title / frame_blocked_desc 译文是否符合各语言术语规范。

关联

Fixes #1588

验证

  • pnpm vitest run:304 个测试文件、3371 个断言全部通过
  • pnpm run typecheck:通过
  • pnpm run lint(Prettier + tsc + ESLint,全仓库):通过
  • pnpm run build:通过(仅有构建体积/Monaco worker 相关的既有警告,与本次改动无关)

@cyfung1031 cyfung1031 changed the title 🐛 修复 Firefox 安装脚本后的历史回退 🐛 移除安装回退对 chrome.scripting.executeScript 的依赖 Jul 17, 2026
cyfung1031 and others added 7 commits July 17, 2026 18:59
PR scriptscat#1594 把 history.back() 改由内容脚本通过消息执行,但 ScriptCat 自身的
content.js 依赖 chrome.userScripts.register() 注册(runtime.ts
registerUserscripts),仅在开启 Developer Mode 且脚本运行未被禁用/未被黑名单
排除时才会被注入。普通用户关闭 Developer Mode 时该内容脚本从不存在,
chrome.tabs.sendMessage 收不到响应但错误只是被 console.error 吞掉,
安装后标签页无法回退且没有任何异常抛出。

改为直接调用 chrome.tabs.goBack(req.tabId)——issue scriptscat#1588 本就要求优先使用
浏览器原生的标签页导航 API,该 API 只依赖已必需的 tabs 权限,不依赖任何
内容脚本注入状态。同时移除不再需要的 historyBack 消息处理器与其测试。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
install.html 既可能由 chrome.tabs.create 打开为独立新标签(history.length === 1,
安装/更新完成后关闭该标签无损),也可能由 declarativeNetRequest 的就地 redirect
规则直接在用户当前浏览标签内被替换出来(点击 raw .user.js 链接场景,history.length
> 1)。无论哪种入口,install()/close()/installSkill()/cancelSkill() 都无条件调用
window.close(),导致后者场景下用户实际浏览的标签(而非专用安装标签)被整个关闭。

改为共用的 leaveInstallPage():history.length > 1 时改用 history.back() 返回上一页,
仅在确实是独立新标签(history.length === 1)时才 window.close()。新增回归测试验证
两种 history.length 下分别调用 history.back()/window.close();已验证该测试在旧代码
上失败(history.length > 1 时 history.back 从未被调用)。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
App.tsx 里新增的 isMainFrame() 拦截存在两个问题:

1. t("invalid_page_access") 引用的 key 在任何语言的任何命名空间里都不存在
   (defaultNS 是 common,install/common 两个命名空间都没有该 key),
   i18n-usage.test.ts 静态扫描可直接复现:界面会原样显示 "invalid_page_access"
   而非译文。改为正确加了命名空间前缀的 install:frame_blocked_title /
   install:frame_blocked_desc,并补全 9 个 locale 的翻译。

2. 拦截判断被放在 loading/invalid/error/skill 等提前 return 之后、ready 视图
   组装完毕时才执行,意味着安装页在 loading 与 skill(技能安装,同样有安装按钮)
   状态下完全不受 iframe 保护,防点击劫持形同虚设。改为紧跟在所有 Hook 调用之后、
   任何状态分支之前执行(避免把 Hook 调用放进条件 return 之前,违反 Hooks 规则)。

同时把裸 <div> 替换为已有的 InstallError 组件,与其余错误态保持一致的设计风格
和可关闭交互(复用 close(),即上一提交里改好的 leaveInstallPage())。

新增回归测试验证:拦截提示的翻译能正确渲染、loading 状态下也会被拦截、拦截页
关闭按钮可用;已确认这些测试在修复前的占位实现上会失败。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@cyfung1031

Copy link
Copy Markdown
Collaborator Author

@cyfung1031 cyfung1031 changed the title 🐛 移除安装回退对 chrome.scripting.executeScript 的依赖 🐛 修复安装页回退/关闭标签逻辑:改用 chrome.tabs.goBack 并区分同标签重定向 Jul 17, 2026
对已合入改动逐处补上「为什么」注释:goBack 为何绕开 content script 通道、
为何只对当前激活标签生效;isMainFrame 里 SecurityError 的成因;
leaveInstallPage 的重入保护与 rAF 延迟目的;测试里 320ms 等待对应的
setTimeout+rAF 时序。均为注释新增,不改变任何运行时行为。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@cyfung1031
cyfung1031 marked this pull request as ready for review July 17, 2026 20:43
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.

[提案] 将安装后的返回操作改为由标签页侧上下文执行

1 participant