🐛 修复安装页回退/关闭标签逻辑:改用 chrome.tabs.goBack 并区分同标签重定向#1594
Open
cyfung1031 wants to merge 9 commits into
Open
Conversation
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>
Collaborator
Author
已通過人工檢查&測試
|
对已合入改动逐处补上「为什么」注释:goBack 为何绕开 content script 通道、 为何只对当前激活标签生效;isMainFrame 里 SecurityError 的成因; leaveInstallPage 的重入保护与 rAF 延迟目的;测试里 320ms 等待对应的 setTimeout+rAF 时序。均为注释新增,不改变任何运行时行为。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
cyfung1031
marked this pull request as ready for review
July 17, 2026 20:43
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Checklist / 检查清单
背景
安装页交接结束后,ScriptCat 需要把被拦截的标签页带回安装前的页面。原实现在 Service Worker 的
finally中用chrome.scripting.executeScript()向req.tabId注入并执行history.back()——issue #1588 指出这条路径的语义不对:这里要表达的只是“让指定标签页回到上一页”,不需要向页面注入任意代码,也不应该依赖目标页面是否允许脚本注入。第一版修复(PR 原提交)把它换成了消息通信方案:Service Worker 用
chrome.tabs.sendMessage发content/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.create(openInCurrentTab())打开的专用新标签,以及由 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 在任何语言、任何命名空间下都不存在(defaultNS是common,install/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),该启发式覆盖了当前所有已知的打开路径。useEffect/useState调用之后才能提前return,否则会在“非顶层 frame”与“顶层 frame”两种渲染路径之间产生不同的 Hook 调用序列。已知限制
listenerScriptInstall(chrome.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 就地重定向)。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 相关的既有警告,与本次改动无关)