From 2f4d5edb5e8ce309abb7efe9e223e4c596e8e6de Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Tue, 22 Sep 2026 23:49:24 +0900 Subject: [PATCH 01/10] =?UTF-8?q?=E2=9C=85=20=E6=B7=BB=E5=8A=A0=E6=97=A0?= =?UTF-8?q?=E6=95=88=20@match=20scheme=20=E5=9B=9E=E5=BD=92=E6=B5=8B?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/service/service_worker/utils.test.ts | 25 ++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/app/service/service_worker/utils.test.ts b/src/app/service/service_worker/utils.test.ts index 94c31b627..60babedae 100644 --- a/src/app/service/service_worker/utils.test.ts +++ b/src/app/service/service_worker/utils.test.ts @@ -308,6 +308,31 @@ describe.concurrent("getUserScriptRegister", () => { expect(result.registerScript.world).toBe("MAIN"); expect(result.registerScript.runAt).toBe("document_end"); }); + + it.concurrent("不应把 Chromium 不支持的 @match scheme 交给 userScripts 注册", () => { + const mockScriptMatchInfo: ScriptMatchInfo = { + uuid: "test-invalid-match-scheme", + name: "Invalid Match Scheme", + metadata: {}, + scriptUrlPatterns: extractUrlPatterns([ + "@match https://*/*", + "@match http://*/*", + "@match notsupported://*/*", + ]), + originalUrlPatterns: [], + namespace: "test", + type: 1, + status: 1, + sort: 0, + runStatus: "running", + createtime: Date.now(), + checktime: Date.now(), + }; + + const { registerScript } = getUserScriptRegister(mockScriptMatchInfo); + + expect(registerScript.matches).toEqual(["https://*/*", "http://*/*"]); + }); }); describe.concurrent("compileInjectionCode", () => { From 1c8c28a86dd12488ca1c19cfd2fd90bb774c61be Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Wed, 23 Sep 2026 04:30:48 +0900 Subject: [PATCH 02/10] =?UTF-8?q?=F0=9F=90=9B=20=E8=BF=87=E6=BB=A4=20Chrom?= =?UTF-8?q?ium=20=E6=97=A0=E6=95=88=20userScripts=20match=20pattern?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/service/service_worker/utils.ts | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/app/service/service_worker/utils.ts b/src/app/service/service_worker/utils.ts index 456013438..ef54e8077 100644 --- a/src/app/service/service_worker/utils.ts +++ b/src/app/service/service_worker/utils.ts @@ -1,7 +1,7 @@ export const BrowserNoSupport = new Error("browserNoSupport"); import type { SCMetadata, Script, ScriptLoadInfo, ScriptRunResource } from "@App/app/repo/scripts"; import { SELF_METADATA_ONLY_RUN_ON_URL } from "@App/app/repo/metadata"; -import { getMetadataStr, getUserConfigStr } from "@App/pkg/utils/utils"; +import { getMetadataStr, getUserConfigStr, isFirefox } from "@App/pkg/utils/utils"; import type { ScriptMatchInfo } from "./types"; import { compileInjectScript, @@ -24,6 +24,18 @@ import { cacheInstance } from "@App/app/cache"; export type RegisteredUserScriptWithJsCode = RequireField; +const CHROMIUM_USER_SCRIPT_MATCH_SCHEMES = new Set(["*", "http", "https", "file"]); + +export function filterUserScriptApiMatchPatterns(patterns: readonly string[]): string[] { + // Firefox 的 match-pattern scheme 集合与 Chromium 不同;这里只在已验证的 Chromium API 边界收紧。 + if (isFirefox()) return [...patterns]; + return patterns.filter((pattern) => { + if (pattern === "") return true; + const schemeEnd = pattern.indexOf("://"); + return schemeEnd > 0 && CHROMIUM_USER_SCRIPT_MATCH_SCHEMES.has(pattern.substring(0, schemeEnd)); + }); +} + export function getRunAt(runAts: string[]): chrome.extensionTypes.RunAt { // 没有 run-at 时为 undefined. Fallback 至 document_idle const runAt = runAts[0] as string | undefined; @@ -212,9 +224,10 @@ export function compileInjectionCode( // 构建userScript注册信息(忽略代码部份) export function getUserScriptRegister(scriptMatchInfo: ScriptMatchInfo) { const { matches, includeGlobs } = getApiMatchesAndGlobs(scriptMatchInfo.scriptUrlPatterns); + const apiMatches = filterUserScriptApiMatchPatterns(matches); - const excludeMatches = toUniquePatternStrings( - scriptMatchInfo.scriptUrlPatterns.filter((e) => e.ruleType === RuleType.MATCH_EXCLUDE) + const excludeMatches = filterUserScriptApiMatchPatterns( + toUniquePatternStrings(scriptMatchInfo.scriptUrlPatterns.filter((e) => e.ruleType === RuleType.MATCH_EXCLUDE)) ); const excludeGlobs = toUniquePatternStrings( scriptMatchInfo.scriptUrlPatterns.filter((e) => e.ruleType === RuleType.GLOB_EXCLUDE) @@ -223,7 +236,7 @@ export function getUserScriptRegister(scriptMatchInfo: ScriptMatchInfo) { const registerScript: chrome.userScripts.RegisteredUserScript = { id: scriptMatchInfo.uuid, js: [{ code: "" }], - matches: matches, // primary + matches: apiMatches, // primary includeGlobs: includeGlobs, // includeGlobs applied after matches excludeMatches: excludeMatches, excludeGlobs: excludeGlobs, From ed60a8aa7cfa1ebc2494622bc08c9e1075a81873 Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Wed, 23 Sep 2026 04:30:51 +0900 Subject: [PATCH 03/10] =?UTF-8?q?=F0=9F=90=9B=20=E6=B3=A8=E5=86=8C?= =?UTF-8?q?=E7=BC=93=E5=AD=98=E8=84=9A=E6=9C=AC=E6=97=B6=E8=BF=87=E6=BB=A4?= =?UTF-8?q?=E6=97=A0=E6=95=88=20match=20pattern?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/service/service_worker/runtime.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/app/service/service_worker/runtime.ts b/src/app/service/service_worker/runtime.ts index d44ef0215..d05036f28 100644 --- a/src/app/service/service_worker/runtime.ts +++ b/src/app/service/service_worker/runtime.ts @@ -13,6 +13,7 @@ import { runScript, stopScript } from "../offscreen/client"; import { buildScriptRunResourceBasic, compileInjectionCode, + filterUserScriptApiMatchPatterns, getCombinedMeta, getUserScriptRegister, parseUrlSRI, @@ -834,7 +835,7 @@ export class RuntimeService { excludeGlobs.push(rule.patternString); } } - this.blacklistExcludeMatches = excludeMatches; + this.blacklistExcludeMatches = filterUserScriptApiMatchPatterns(excludeMatches); this.blacklistExcludeGlobs = excludeGlobs; } @@ -977,9 +978,9 @@ export class RuntimeService { const registerScript = { id: result.uuid, js: [{ code: resultCode }], - matches: result.matches, + matches: filterUserScriptApiMatchPatterns(result.matches), includeGlobs: result.includeGlobs, - excludeMatches: [...result.excludeMatches, ...excludeMatches], + excludeMatches: filterUserScriptApiMatchPatterns([...result.excludeMatches, ...excludeMatches]), excludeGlobs: [...result.excludeGlobs, ...excludeGlobs], allFrames: result.allFrames, world: result.world, From 519f5531a642e3712a25894f84c36045987ddec5 Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Wed, 23 Sep 2026 04:30:53 +0900 Subject: [PATCH 04/10] =?UTF-8?q?=E2=9C=85=20=E8=A6=86=E7=9B=96=E6=97=A0?= =?UTF-8?q?=E6=95=88=20exclude=20match=20scheme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/service/service_worker/utils.test.ts | 27 +++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/app/service/service_worker/utils.test.ts b/src/app/service/service_worker/utils.test.ts index 60babedae..6bc7f43da 100644 --- a/src/app/service/service_worker/utils.test.ts +++ b/src/app/service/service_worker/utils.test.ts @@ -314,10 +314,31 @@ describe.concurrent("getUserScriptRegister", () => { uuid: "test-invalid-match-scheme", name: "Invalid Match Scheme", metadata: {}, + scriptUrlPatterns: extractUrlPatterns(["@match https://*/*", "@match http://*/*", "@match notsupported://*/*"]), + originalUrlPatterns: [], + namespace: "test", + type: 1, + status: 1, + sort: 0, + runStatus: "running", + createtime: Date.now(), + checktime: Date.now(), + }; + + const { registerScript } = getUserScriptRegister(mockScriptMatchInfo); + + expect(registerScript.matches).toEqual(["https://*/*", "http://*/*"]); + }); + + it.concurrent("应同时过滤 Chromium 不支持的 exclude match scheme", () => { + const mockScriptMatchInfo: ScriptMatchInfo = { + uuid: "test-invalid-exclude-scheme", + name: "Invalid Exclude Scheme", + metadata: {}, scriptUrlPatterns: extractUrlPatterns([ "@match https://*/*", - "@match http://*/*", - "@match notsupported://*/*", + "@exclude https://example.com/private/*", + "@exclude notsupported://*/*", ]), originalUrlPatterns: [], namespace: "test", @@ -331,7 +352,7 @@ describe.concurrent("getUserScriptRegister", () => { const { registerScript } = getUserScriptRegister(mockScriptMatchInfo); - expect(registerScript.matches).toEqual(["https://*/*", "http://*/*"]); + expect(registerScript.excludeMatches).toEqual(["https://example.com/private/*"]); }); }); From 4dcc54cbf947d8700027019f9dd2596ce8e0de22 Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Wed, 23 Sep 2026 04:32:59 +0900 Subject: [PATCH 05/10] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20=E6=98=8E=E7=A1=AE?= =?UTF-8?q?=20userScripts=20scheme=20=E8=BF=87=E6=BB=A4=E8=81=8C=E8=B4=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/service/service_worker/utils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/service/service_worker/utils.ts b/src/app/service/service_worker/utils.ts index ef54e8077..c8ea7540e 100644 --- a/src/app/service/service_worker/utils.ts +++ b/src/app/service/service_worker/utils.ts @@ -26,7 +26,7 @@ export type RegisteredUserScriptWithJsCode = RequireField { @@ -224,9 +224,9 @@ export function compileInjectionCode( // 构建userScript注册信息(忽略代码部份) export function getUserScriptRegister(scriptMatchInfo: ScriptMatchInfo) { const { matches, includeGlobs } = getApiMatchesAndGlobs(scriptMatchInfo.scriptUrlPatterns); - const apiMatches = filterUserScriptApiMatchPatterns(matches); + const apiMatches = filterUserScriptApiPatternsByScheme(matches); - const excludeMatches = filterUserScriptApiMatchPatterns( + const excludeMatches = filterUserScriptApiPatternsByScheme( toUniquePatternStrings(scriptMatchInfo.scriptUrlPatterns.filter((e) => e.ruleType === RuleType.MATCH_EXCLUDE)) ); const excludeGlobs = toUniquePatternStrings( From 64a44a2d1b46be67578f575cc17f9bf0be6adefc Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Wed, 23 Sep 2026 04:33:02 +0900 Subject: [PATCH 06/10] =?UTF-8?q?=F0=9F=90=9B=20=E8=B7=B3=E8=BF=87?= =?UTF-8?q?=E6=97=A0=E5=8F=AF=E6=B3=A8=E5=86=8C=20match=20=E7=9A=84?= =?UTF-8?q?=E6=97=A7=E7=BC=93=E5=AD=98=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/service/service_worker/runtime.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/app/service/service_worker/runtime.ts b/src/app/service/service_worker/runtime.ts index d05036f28..2eaecf6c3 100644 --- a/src/app/service/service_worker/runtime.ts +++ b/src/app/service/service_worker/runtime.ts @@ -13,7 +13,7 @@ import { runScript, stopScript } from "../offscreen/client"; import { buildScriptRunResourceBasic, compileInjectionCode, - filterUserScriptApiMatchPatterns, + filterUserScriptApiPatternsByScheme, getCombinedMeta, getUserScriptRegister, parseUrlSRI, @@ -835,7 +835,7 @@ export class RuntimeService { excludeGlobs.push(rule.patternString); } } - this.blacklistExcludeMatches = filterUserScriptApiMatchPatterns(excludeMatches); + this.blacklistExcludeMatches = filterUserScriptApiPatternsByScheme(excludeMatches); this.blacklistExcludeGlobs = excludeGlobs; } @@ -975,12 +975,14 @@ export class RuntimeService { resultCode = await this.restoreJSCodeFromCompiledResource(script, result); } if (!resultCode) return undefined; + const matches = filterUserScriptApiPatternsByScheme(result.matches); + if (matches.length === 0) return undefined; const registerScript = { id: result.uuid, js: [{ code: resultCode }], - matches: filterUserScriptApiMatchPatterns(result.matches), + matches, includeGlobs: result.includeGlobs, - excludeMatches: filterUserScriptApiMatchPatterns([...result.excludeMatches, ...excludeMatches]), + excludeMatches: filterUserScriptApiPatternsByScheme([...result.excludeMatches, ...excludeMatches]), excludeGlobs: [...result.excludeGlobs, ...excludeGlobs], allFrames: result.allFrames, world: result.world, From 8e4b3a368a39349ea16016cd61addfc16299eb61 Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Wed, 23 Sep 2026 04:33:05 +0900 Subject: [PATCH 07/10] =?UTF-8?q?=E2=9C=85=20=E8=A6=86=E7=9B=96=E9=BB=91?= =?UTF-8?q?=E5=90=8D=E5=8D=95=E4=B8=8E=E6=97=A7=E7=BC=93=E5=AD=98=20match?= =?UTF-8?q?=20scheme=20=E8=BF=87=E6=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/service_worker/runtime.test.ts | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/app/service/service_worker/runtime.test.ts b/src/app/service/service_worker/runtime.test.ts index fc272def4..6f0fd094d 100644 --- a/src/app/service/service_worker/runtime.test.ts +++ b/src/app/service/service_worker/runtime.test.ts @@ -650,6 +650,18 @@ describe.concurrent("RuntimeService - getPageScriptMatchingResultByUrl 脚本匹 expect(blacklistResult2).toBe(true); expect(normalResult2).toBe(false); }); + + it.concurrent("Chromium 原生黑名单应过滤不支持的 scheme,但内部匹配规则保持不变", async () => { + const blacklistString = "*://www.blacklisted.com/*\nnotsupported://*/*"; + mockSystemConfig.getBlacklist.mockReturnValue(blacklistString); + runtime.blacklist = obtainBlackList(blacklistString); + + runtime.loadBlacklist(); + + expect(runtime.blackMatch?.rulesMap.get("BK")?.length || 0).toBe(2); + expect(runtime.blacklistExcludeMatches).toEqual(["*://www.blacklisted.com/*"]); + expect(runtime.isUrlBlacklist("notsupported://example/path")).toBe(true); + }); }); }); @@ -1321,6 +1333,59 @@ describe("pushValueUpdate 判断是否需要为 early-start 脚本重新编译", }); }); +describe("getParticularScriptList 原生 match scheme 过滤", () => { + const createCompiledResource = (script: Script, matches: string[]): CompiledResource => { + const patterns = scriptURLPatternResults(_createScriptRunResource(script))!; + return { + name: script.name, + flag: "", + uuid: script.uuid, + require: [], + matches, + includeGlobs: [], + excludeMatches: ["notsupported://*/*"], + excludeGlobs: [], + allFrames: false, + world: "MAIN", + runAt: "", + scriptUrlPatterns: patterns.scriptUrlPatterns, + originalUrlPatterns: null, + }; + }; + + it("旧缓存混有不支持 scheme 时只恢复有效的原生 match pattern", async () => { + const { runtime, mockScriptDAO } = _createRuntimeContext(); + const script = _createMockScript(); + const compiledResource = createCompiledResource(script, ["https://*/*", "notsupported://*/*"]); + mockScriptDAO.all.mockResolvedValue([script]); + runtime.compiledResourceDAO = { + get: vi.fn().mockResolvedValue(compiledResource), + } as any; + vi.spyOn(runtime, "restoreJSCodeFromCompiledResource").mockResolvedValue("console.log(1);"); + + const list = await runtime.getParticularScriptList({ excludeMatches: [], excludeGlobs: [] }); + + expect(list).toHaveLength(1); + expect(list[0].matches).toEqual(["https://*/*"]); + expect(list[0].excludeMatches).toEqual([]); + }); + + it("旧缓存没有任何 Chromium 可注册 scheme 时不创建原生注册项", async () => { + const { runtime, mockScriptDAO } = _createRuntimeContext(); + const script = _createMockScript(); + const compiledResource = createCompiledResource(script, ["notsupported://*/*"]); + mockScriptDAO.all.mockResolvedValue([script]); + runtime.compiledResourceDAO = { + get: vi.fn().mockResolvedValue(compiledResource), + } as any; + vi.spyOn(runtime, "restoreJSCodeFromCompiledResource").mockResolvedValue("console.log(1);"); + + const list = await runtime.getParticularScriptList({ excludeMatches: [], excludeGlobs: [] }); + + expect(list).toEqual([]); + }); +}); + describe("registerUserscripts 注册健康检查", () => { let runtime: RuntimeService; let originalScripting: unknown; From 049576d1bdc2e61101382f529646060678c3a6de Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Wed, 23 Sep 2026 04:33:31 +0900 Subject: [PATCH 08/10] =?UTF-8?q?=F0=9F=8E=A8=20=E9=81=BF=E5=85=8D?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=B5=8B=E8=AF=95=E4=BD=BF=E7=94=A8=20any=20?= =?UTF-8?q?=E5=BC=BA=E8=BD=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/service/service_worker/runtime.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/service/service_worker/runtime.test.ts b/src/app/service/service_worker/runtime.test.ts index 6f0fd094d..7d8ef7f3f 100644 --- a/src/app/service/service_worker/runtime.test.ts +++ b/src/app/service/service_worker/runtime.test.ts @@ -1360,7 +1360,7 @@ describe("getParticularScriptList 原生 match scheme 过滤", () => { mockScriptDAO.all.mockResolvedValue([script]); runtime.compiledResourceDAO = { get: vi.fn().mockResolvedValue(compiledResource), - } as any; + } as unknown as RuntimeService["compiledResourceDAO"]; vi.spyOn(runtime, "restoreJSCodeFromCompiledResource").mockResolvedValue("console.log(1);"); const list = await runtime.getParticularScriptList({ excludeMatches: [], excludeGlobs: [] }); @@ -1377,7 +1377,7 @@ describe("getParticularScriptList 原生 match scheme 过滤", () => { mockScriptDAO.all.mockResolvedValue([script]); runtime.compiledResourceDAO = { get: vi.fn().mockResolvedValue(compiledResource), - } as any; + } as unknown as RuntimeService["compiledResourceDAO"]; vi.spyOn(runtime, "restoreJSCodeFromCompiledResource").mockResolvedValue("console.log(1);"); const list = await runtime.getParticularScriptList({ excludeMatches: [], excludeGlobs: [] }); From 007533fffac2256cd1d850f58a162ead748847e1 Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Wed, 23 Sep 2026 04:35:25 +0900 Subject: [PATCH 09/10] =?UTF-8?q?=E2=9C=85=20=E9=9A=94=E7=A6=BB=E9=BB=91?= =?UTF-8?q?=E5=90=8D=E5=8D=95=20scheme=20=E5=9B=9E=E5=BD=92=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E4=B8=8A=E4=B8=8B=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/service_worker/runtime.test.ts | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/app/service/service_worker/runtime.test.ts b/src/app/service/service_worker/runtime.test.ts index 7d8ef7f3f..1d23e2544 100644 --- a/src/app/service/service_worker/runtime.test.ts +++ b/src/app/service/service_worker/runtime.test.ts @@ -650,18 +650,6 @@ describe.concurrent("RuntimeService - getPageScriptMatchingResultByUrl 脚本匹 expect(blacklistResult2).toBe(true); expect(normalResult2).toBe(false); }); - - it.concurrent("Chromium 原生黑名单应过滤不支持的 scheme,但内部匹配规则保持不变", async () => { - const blacklistString = "*://www.blacklisted.com/*\nnotsupported://*/*"; - mockSystemConfig.getBlacklist.mockReturnValue(blacklistString); - runtime.blacklist = obtainBlackList(blacklistString); - - runtime.loadBlacklist(); - - expect(runtime.blackMatch?.rulesMap.get("BK")?.length || 0).toBe(2); - expect(runtime.blacklistExcludeMatches).toEqual(["*://www.blacklisted.com/*"]); - expect(runtime.isUrlBlacklist("notsupported://example/path")).toBe(true); - }); }); }); @@ -731,6 +719,19 @@ const _createRuntimeContext = () => { return { runtime, mockSystemConfig, mockScriptService, mockScriptDAO, mockGroup }; }; +describe("loadBlacklist 原生 match scheme 过滤", () => { + it("Chromium 原生黑名单应过滤不支持的 scheme,但内部匹配规则保持不变", () => { + const { runtime } = _createRuntimeContext(); + runtime.blacklist = obtainBlackList("*://www.blacklisted.com/*\nnotsupported://*/*"); + + runtime.loadBlacklist(); + + expect(runtime.blackMatch?.rulesMap.get("BK")?.length || 0).toBe(2); + expect(runtime.blacklistExcludeMatches).toEqual(["*://www.blacklisted.com/*"]); + expect(runtime.isUrlBlacklist("notsupported://example/path")).toBe(true); + }); +}); + // ───────────────────────────────────────────────────────────────────────────── describe("shouldSkipPageLoadScript 页面脚本加载过滤规则", () => { From a0be9f1c3d5372757dd1204733f1ea846cbdabf2 Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Wed, 23 Sep 2026 18:04:02 +0900 Subject: [PATCH 10/10] =?UTF-8?q?=F0=9F=90=9B=20=E8=B7=A8=E7=80=8F?= =?UTF-8?q?=E8=A6=BD=E5=99=A8=E9=81=8E=E6=BF=BE=20userScripts=20=E4=B8=8D?= =?UTF-8?q?=E6=94=AF=E6=8F=B4=E7=9A=84=20@match=20scheme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/service_worker/runtime.test.ts | 4 +- .../service_worker/utils.match-scheme.test.ts | 38 +++++++++++++++++++ src/app/service/service_worker/utils.test.ts | 4 +- src/app/service/service_worker/utils.ts | 6 +-- 4 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 src/app/service/service_worker/utils.match-scheme.test.ts diff --git a/src/app/service/service_worker/runtime.test.ts b/src/app/service/service_worker/runtime.test.ts index 1d23e2544..00c6dc67c 100644 --- a/src/app/service/service_worker/runtime.test.ts +++ b/src/app/service/service_worker/runtime.test.ts @@ -720,7 +720,7 @@ const _createRuntimeContext = () => { }; describe("loadBlacklist 原生 match scheme 过滤", () => { - it("Chromium 原生黑名单应过滤不支持的 scheme,但内部匹配规则保持不变", () => { + it("原生 userScripts 黑名单应过滤不支持的 scheme,但内部匹配规则保持不变", () => { const { runtime } = _createRuntimeContext(); runtime.blacklist = obtainBlackList("*://www.blacklisted.com/*\nnotsupported://*/*"); @@ -1371,7 +1371,7 @@ describe("getParticularScriptList 原生 match scheme 过滤", () => { expect(list[0].excludeMatches).toEqual([]); }); - it("旧缓存没有任何 Chromium 可注册 scheme 时不创建原生注册项", async () => { + it("旧缓存没有任何 userScripts 可注册 scheme 时不创建原生注册项", async () => { const { runtime, mockScriptDAO } = _createRuntimeContext(); const script = _createMockScript(); const compiledResource = createCompiledResource(script, ["notsupported://*/*"]); diff --git a/src/app/service/service_worker/utils.match-scheme.test.ts b/src/app/service/service_worker/utils.match-scheme.test.ts new file mode 100644 index 000000000..5e2d720f2 --- /dev/null +++ b/src/app/service/service_worker/utils.match-scheme.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, it, vi } from "vitest"; +import type * as Utils from "@App/pkg/utils/utils"; + +const { isFirefox } = vi.hoisted(() => ({ + isFirefox: vi.fn(() => false), +})); + +vi.mock("@App/pkg/utils/utils", async (importOriginal) => ({ + ...(await importOriginal()), + isFirefox, +})); + +import { filterUserScriptApiPatternsByScheme } from "./utils"; + +const patterns = [ + "https://example.com/*", + "ws://example.com/*", + "wss://example.com/*", + "ftp://example.com/*", + "notsupported://example.com/*", +]; + +describe("filterUserScriptApiPatternsByScheme", () => { + it("保留 Chromium userScripts 支持的 scheme,并过滤其他 scheme", () => { + expect(filterUserScriptApiPatternsByScheme(patterns)).toEqual(["https://example.com/*"]); + }); + + it("保留 Firefox userScripts 额外支持的 scheme,并过滤未知 scheme", () => { + isFirefox.mockReturnValue(true); + + expect(filterUserScriptApiPatternsByScheme(patterns)).toEqual([ + "https://example.com/*", + "ws://example.com/*", + "wss://example.com/*", + "ftp://example.com/*", + ]); + }); +}); diff --git a/src/app/service/service_worker/utils.test.ts b/src/app/service/service_worker/utils.test.ts index 6bc7f43da..c6bc5247c 100644 --- a/src/app/service/service_worker/utils.test.ts +++ b/src/app/service/service_worker/utils.test.ts @@ -309,7 +309,7 @@ describe.concurrent("getUserScriptRegister", () => { expect(result.registerScript.runAt).toBe("document_end"); }); - it.concurrent("不应把 Chromium 不支持的 @match scheme 交给 userScripts 注册", () => { + it.concurrent("不应把 userScripts 不支持的 @match scheme 交给注册 API", () => { const mockScriptMatchInfo: ScriptMatchInfo = { uuid: "test-invalid-match-scheme", name: "Invalid Match Scheme", @@ -330,7 +330,7 @@ describe.concurrent("getUserScriptRegister", () => { expect(registerScript.matches).toEqual(["https://*/*", "http://*/*"]); }); - it.concurrent("应同时过滤 Chromium 不支持的 exclude match scheme", () => { + it.concurrent("应同时过滤 userScripts 不支持的 exclude match scheme", () => { const mockScriptMatchInfo: ScriptMatchInfo = { uuid: "test-invalid-exclude-scheme", name: "Invalid Exclude Scheme", diff --git a/src/app/service/service_worker/utils.ts b/src/app/service/service_worker/utils.ts index c8ea7540e..93e4b943b 100644 --- a/src/app/service/service_worker/utils.ts +++ b/src/app/service/service_worker/utils.ts @@ -25,14 +25,14 @@ import { cacheInstance } from "@App/app/cache"; export type RegisteredUserScriptWithJsCode = RequireField; const CHROMIUM_USER_SCRIPT_MATCH_SCHEMES = new Set(["*", "http", "https", "file"]); +const FIREFOX_USER_SCRIPT_MATCH_SCHEMES = new Set(["*", "http", "https", "file", "ws", "wss", "ftp"]); export function filterUserScriptApiPatternsByScheme(patterns: readonly string[]): string[] { - // Firefox 的 match-pattern scheme 集合与 Chromium 不同;这里只在已验证的 Chromium API 边界收紧。 - if (isFirefox()) return [...patterns]; + const supportedSchemes = isFirefox() ? FIREFOX_USER_SCRIPT_MATCH_SCHEMES : CHROMIUM_USER_SCRIPT_MATCH_SCHEMES; return patterns.filter((pattern) => { if (pattern === "") return true; const schemeEnd = pattern.indexOf("://"); - return schemeEnd > 0 && CHROMIUM_USER_SCRIPT_MATCH_SCHEMES.has(pattern.substring(0, schemeEnd)); + return schemeEnd > 0 && supportedSchemes.has(pattern.substring(0, schemeEnd)); }); }