diff --git a/src/pages/install/context_fallback.test.ts b/src/pages/install/context_fallback.test.ts new file mode 100644 index 000000000..4d43cb9ef --- /dev/null +++ b/src/pages/install/context_fallback.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, it } from "vitest"; +import { DocumentationSite } from "@App/app/const"; +import { getIncompleteContextFallbackUrl } from "./context_fallback"; + +describe("getIncompleteContextFallbackUrl", () => { + it("扩展 API 不完整的 ?url= 安装页转到文档站安装中间页,脚本地址原样带上(含其自身的查询串)", () => { + const scriptUrl = "https://example.com/a.user.js?v=1&lang=zh"; + expect(getIncompleteContextFallbackUrl(`?url=${scriptUrl}`, false)).toBe( + `${DocumentationSite}/docs/script_installation/#url=${scriptUrl}` + ); + }); + + it("扩展 API 完整时不转跳,正常浏览器照常在本页安装", () => { + expect(getIncompleteContextFallbackUrl("?url=https://example.com/a.user.js", true)).toBeUndefined(); + }); + + it("没有 ?url= 的入口(如 SW 新建的 ?uuid=)不转跳", () => { + expect(getIncompleteContextFallbackUrl("?uuid=8f3c2a10-0000-4000-8000-000000000000", false)).toBeUndefined(); + }); +}); diff --git a/src/pages/install/context_fallback.ts b/src/pages/install/context_fallback.ts new file mode 100644 index 000000000..544482eda --- /dev/null +++ b/src/pages/install/context_fallback.ts @@ -0,0 +1,18 @@ +import { DocumentationSite } from "@App/app/const"; + +/** + * 浏览器兼容处理(#1816):360 X 中,网页以 noopener 新开的标签被 DNR 重定向到安装页后,页面只拿到 + * 不完整的扩展 API(chrome.tabs / chrome.permissions 缺失,读 chrome.storage 报 + * "Invalid context type provided.")。浏览器为何这样分类尚未确认,在本页刷新或原标签重新导航都恢复不了, + * 由 SW 用 tabs.create 新开的安装页才正常。 + * + * 因此这种上下文改为转到文档站的安装中间页:SW 的 webNavigation 监听会接住这次导航,备好脚本后新开 + * `?uuid=` 安装页。只有 DNR 产生的 `?url=` 入口会转跳,SW 新建的入口不会再落回这里,不会循环。 + * `url=` 之后的整段原样转交,与安装页读取 `?url=` 的方式一致,脚本地址自带的查询串不会被截断。 + */ +export function getIncompleteContextFallbackUrl(search: string, hasTabsApi: boolean): string | undefined { + if (hasTabsApi) return undefined; + const urlIdx = search.indexOf("url="); + if (urlIdx === -1) return undefined; + return `${DocumentationSite}/docs/script_installation/#url=${search.slice(urlIdx + 4)}`; +} diff --git a/src/pages/install/main.tsx b/src/pages/install/main.tsx index 38bc39401..acc25275f 100644 --- a/src/pages/install/main.tsx +++ b/src/pages/install/main.tsx @@ -6,6 +6,7 @@ import { message } from "../store/global.ts"; import MessageWriter from "@App/app/logger/message_writer.ts"; import { ThemeProvider } from "../components/theme-provider.tsx"; import { Toaster } from "../components/ui/sonner.tsx"; +import { getIncompleteContextFallbackUrl } from "./context_fallback.ts"; import "@App/index.css"; // 初始化日志组件 @@ -23,6 +24,11 @@ const Root = ( ); -ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - process.env.NODE_ENV === "development" ? {Root} : Root -); +const fallbackUrl = getIncompleteContextFallbackUrl(location.search, typeof chrome.tabs !== "undefined"); +if (fallbackUrl) { + location.replace(fallbackUrl); +} else { + ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( + process.env.NODE_ENV === "development" ? {Root} : Root + ); +}