Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
cd677a9
重写UrlMatch
cyfung1031 Aug 18, 2025
52a1bd8
Merge branch 'main' into pr/cyfung1031/637
CodFrm Aug 18, 2025
4658d7f
整理代码
CodFrm Aug 18, 2025
ee9109f
添加单元测试
CodFrm Aug 18, 2025
90435fc
兼容 .tld
cyfung1031 Aug 18, 2025
bd43ff5
修正 match pattern ** 问题
cyfung1031 Aug 18, 2025
499db23
代码及测试修正
cyfung1031 Aug 18, 2025
6d2f382
补充单元测试
CodFrm Aug 18, 2025
1ff927a
调整单元测试
CodFrm Aug 18, 2025
60fc52b
将单元测试url换成更实际的url
CodFrm Aug 18, 2025
24d5e2d
修改单元测试
CodFrm Aug 18, 2025
16e38d8
修改01
cyfung1031 Aug 18, 2025
12e7ecb
file应该是三个斜杠
CodFrm Aug 18, 2025
843cde4
Merge branch 'urlmatcher01a' of github.com:cyfung1031/scriptcat into …
CodFrm Aug 18, 2025
9e4223a
修正02
cyfung1031 Aug 18, 2025
588e459
修正03
cyfung1031 Aug 18, 2025
d376259
修正04
cyfung1031 Aug 18, 2025
35c1045
add -> addInclude
cyfung1031 Aug 18, 2025
6486d7f
customUrlCovering -> customMUP
cyfung1031 Aug 18, 2025
211ad86
urlCovering -> scriptMUP
cyfung1031 Aug 18, 2025
bcd407e
metaUMatchAnalyze -> extractMUP
cyfung1031 Aug 18, 2025
814b283
修正05
cyfung1031 Aug 18, 2025
4f18778
lint fix
cyfung1031 Aug 18, 2025
e01a620
注释修正
cyfung1031 Aug 18, 2025
3566c32
注释修正
cyfung1031 Aug 18, 2025
36d359f
内部测试补充
cyfung1031 Aug 18, 2025
8987f37
glob *? -> ?*
cyfung1031 Aug 18, 2025
7e31221
單元測試 glob-test-3
cyfung1031 Aug 18, 2025
146f1c2
修正06
cyfung1031 Aug 18, 2025
1acd054
MUP -> UrlPatterns
cyfung1031 Aug 19, 2025
d493142
修正07
cyfung1031 Aug 19, 2025
c04edcb
加注释:GM 的 magic tld 说明
cyfung1031 Aug 19, 2025
4eb8098
glob * 修正
cyfung1031 Aug 19, 2025
ce90f09
代码优化,注释补充
cyfung1031 Aug 19, 2025
a6c93ea
修正特殊處理,修正單元測試
cyfung1031 Aug 19, 2025
9b98598
Update match.test.ts
cyfung1031 Aug 19, 2025
19e0c92
处理lint问题
CodFrm Aug 19, 2025
d32725e
代码优化
cyfung1031 Aug 19, 2025
f3313f1
lint
cyfung1031 Aug 19, 2025
027b362
sorter 相关代码优化
cyfung1031 Aug 19, 2025
e8f2a84
变数名统一 strBlacklist
cyfung1031 Aug 19, 2025
9dee93c
修正 regex pattern 没有匹配到页面问题
cyfung1031 Aug 20, 2025
4c90a02
wildcard處理
cyfung1031 Aug 20, 2025
0cff1b6
lint
cyfung1031 Aug 20, 2025
3288b27
注釋修正
cyfung1031 Aug 20, 2025
71a90e5
把glob的指定网域取出来
cyfung1031 Aug 20, 2025
0df7270
效能修正
cyfung1031 Aug 20, 2025
9b1ad03
处理review问题
CodFrm Aug 20, 2025
210b901
单元测试优化,加强错误处理
cyfung1031 Aug 20, 2025
1c12c41
改进代码处理
cyfung1031 Aug 20, 2025
23e56e6
lint
cyfung1031 Aug 20, 2025
68019b5
extractSchemesOfGlobs, 单元测试调整
cyfung1031 Aug 20, 2025
72cfb3b
⚡ runtime.ts 代码优化 (#642)
cyfung1031 Aug 20, 2025
1aecb30
🎨 getEnableScript 优化 (#645)
cyfung1031 Aug 20, 2025
fa5ff7d
Merge branch 'main' into urlmatcher01a
cyfung1031 Aug 20, 2025
69e23e7
lint
cyfung1031 Aug 20, 2025
a9e8e17
誤字
cyfung1031 Aug 20, 2025
2f7e706
加注釋
cyfung1031 Aug 20, 2025
7bd1a72
lint
cyfung1031 Aug 20, 2025
c937880
微调 regex pattern ruleContent
cyfung1031 Aug 20, 2025
941664d
RuleType
cyfung1031 Aug 20, 2025
9b3bf7d
增加单元测试 (extractMatchPatternsFromGlobs, extractSchemesOfGlobs)
cyfung1031 Aug 20, 2025
f8afbe2
配合使用情景,增强 regexToGlob处理
cyfung1031 Aug 21, 2025
71e4f18
vitest 测试顺序
cyfung1031 Aug 21, 2025
63c2298
url_matcher.test.js -> url_matcher.test.ts
cyfung1031 Aug 21, 2025
7946e38
🐛 兼容较低的浏览器内核 #647
CodFrm Aug 21, 2025
64b6a66
Merge branch 'main' into urlmatcher01a
cyfung1031 Aug 21, 2025
730575d
Merge branch 'main' into urlmatcher01a
cyfung1031 Aug 21, 2025
188e496
移除 vitest 测试顺序
cyfung1031 Aug 21, 2025
4b29825
增强 regexToGlob处理
cyfung1031 Aug 22, 2025
02bd297
刪去不必要code
cyfung1031 Aug 22, 2025
d7792ea
单元测试調整
cyfung1031 Aug 22, 2025
c620060
单元测试調整
cyfung1031 Aug 22, 2025
f5ebb92
regexToGlob 规范化连续 修正
cyfung1031 Aug 22, 2025
c4797fe
刪去不必要code
cyfung1031 Aug 22, 2025
717f420
補充注釋
cyfung1031 Aug 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/app/service/service_worker/popup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ export class PopupService {
runNum: script.type === SCRIPT_TYPE_NORMAL ? 0 : script.runStatus === SCRIPT_RUN_STATUS_RUNNING ? 1 : 0,
runNumByIframe: 0,
menus: [],
customExclude: (script as ScriptMatchInfo).customizeExcludeMatches || [],
customUrlPatterns: (script as ScriptMatchInfo).customUrlPatterns || null,
};
}

Expand All @@ -182,7 +182,7 @@ export class PopupService {
if (run) {
// 如果脚本已经存在,则不添加,更新信息
run.enable = script.status === SCRIPT_STATUS_ENABLE;
run.customExclude = script.customizeExcludeMatches || run.customExclude;
run.customUrlPatterns = script.customUrlPatterns || run.customUrlPatterns;
run.hasUserConfig = !!script.config;
} else {
run = this.scriptToMenu(script);
Expand All @@ -198,9 +198,9 @@ export class PopupService {
}
const scriptMenu = [...scriptMenuMap.values()];
// 检查是否在黑名单中
const isBlack = this.runtime.blackMatch.match(req.url).length > 0;
const isBlacklist = this.runtime.isUrlBlacklist(req.url);
// 后台脚本只显示开启或者运行中的脚本
return { isBlacklist: isBlack, scriptList: scriptMenu, backScriptList };
return { isBlacklist, scriptList: scriptMenu, backScriptList };
}

async getScriptMenu(tabId: number): Promise<ScriptMenu[]> {
Expand Down
176 changes: 86 additions & 90 deletions src/app/service/service_worker/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { runScript, stopScript } from "../offscreen/client";
import { getRunAt } from "./utils";
import { isUserScriptsAvailable, randomMessageFlag } from "@App/pkg/utils/utils";
import { cacheInstance } from "@App/app/cache";
import { dealPatternMatches, UrlMatch } from "@App/pkg/utils/match";
import { UrlMatch } from "@App/pkg/utils/match";
import { ExtensionContentMessageSend } from "@Packages/message/extension_message";
import { sendMessage } from "@Packages/message/client";
import { compileInjectScript, compileScriptCode } from "../content/utils";
Expand All @@ -22,26 +22,23 @@ import { type SystemConfig } from "@App/pkg/config/config";
import { type ResourceService } from "./resource";
import { LocalStorageDAO } from "@App/app/repo/localStorage";
import Logger from "@App/app/logger/logger";
import { getMetadataStr, getUserConfigStr } from "@App/pkg/utils/utils";
import { getMetadataStr, getUserConfigStr, obtainBlackList } from "@App/pkg/utils/utils";
import type { GMInfoEnv } from "../content/types";
import { localePath } from "@App/locales/locales";
import { DocumentationSite } from "@App/app/const";
import { CACHE_KEY_REGISTRY_SCRIPT } from "@App/app/cache_key";

const obtainBlackList = (strBlacklist: string | null | undefined) => {
const blacklist = strBlacklist
? strBlacklist
.split("\n")
.map((item) => item.trim())
.filter((item) => item)
: [];
return blacklist;
};
import {
getApiMatchesAndGlobs,
extractUrlPatterns,
RuleType,
toUniquePatternStrings,
type URLRuleEntry,
} from "@App/pkg/utils/url_matcher";

export class RuntimeService {
scriptMatch: UrlMatch<string> = new UrlMatch<string>();
scriptCustomizeMatch: UrlMatch<string> = new UrlMatch<string>();
blackMatch: UrlMatch<boolean> = new UrlMatch<boolean>();
blackMatch: UrlMatch<string> = new UrlMatch<string>();
scriptMatchCache: Map<string, ScriptMatchInfo> | null | undefined;

logger: Logger;
Expand Down Expand Up @@ -181,7 +178,8 @@ export class RuntimeService {
// 监听脚本排序
this.mq.subscribe<TSortScript>("sortScript", async (scripts) => {
const uuidSort = Object.fromEntries(scripts.map(({ uuid, sort }) => [uuid, sort]));
this.scriptMatch.sort((a, b) => uuidSort[a] - uuidSort[b]);
this.scriptMatch.setupSorter(uuidSort);
this.scriptCustomizeMatch.setupSorter(uuidSort);
Comment thread
CodFrm marked this conversation as resolved.
// 更新缓存
const scriptMatchCache = await cacheInstance.get<{ [key: string]: ScriptMatchInfo }>("scriptMatch");
if (!scriptMatchCache) {
Expand Down Expand Up @@ -272,15 +270,14 @@ export class RuntimeService {
private loadBlacklist() {
// 设置黑名单match
const blacklist = this.blacklist; // 重用cache的blacklist阵列 (immutable)
const result = dealPatternMatches(blacklist, {
exclude: true,
});
this.blackMatch.forEach((uuid) => {
this.blackMatch.del(uuid);
});
result.result.forEach((match) => {
this.blackMatch.add(match, true);
});

const scriptUrlPatterns = extractUrlPatterns([...blacklist.map((e) => `@include ${e}`)]);
this.blackMatch.clearRules("BK");
this.blackMatch.addRules("BK", scriptUrlPatterns);
}

public isUrlBlacklist(url: string) {
return this.blackMatch.urlMatch(url)[0] === "BK";
}

// 取消脚本注册
Expand Down Expand Up @@ -412,10 +409,10 @@ export class RuntimeService {
async getPageScriptUuidByUrl(url: string, includeCustomize?: boolean) {
await this.loadScriptMatchInfo();
// 匹配当前页面的脚本
let matchScriptUuid = this.scriptMatch.match(url!);
let matchScriptUuid = this.scriptMatch.urlMatch(url!);
// 包含自定义排除的脚本
if (includeCustomize) {
const excludeScriptUuid = this.scriptCustomizeMatch.match(url!);
const excludeScriptUuid = this.scriptCustomizeMatch.urlMatch(url!);
// 自定义排除的脚本优化显示
matchScriptUuid = [...new Set<string>([...excludeScriptUuid, ...matchScriptUuid])];
}
Expand All @@ -432,12 +429,10 @@ export class RuntimeService {
if (!this.isLoadScripts) {
return { flag: "", scripts: [] };
}

const chromeSender = sender.getSender() as MessageSender;

// 判断是否黑名单(针对网址,与个别脚本设定无关)
const isBlack = this.blackMatch.match(chromeSender.url!);
if (isBlack.length > 0) {
if (this.isUrlBlacklist(chromeSender.url!)) {
// 如果在黑名单中, 则不加载脚本
return { flag: "", scripts: [] };
}
Expand Down Expand Up @@ -608,13 +603,19 @@ export class RuntimeService {
let messageFlag = await this.getMessageFlag();
if (!messageFlag) {
// 黑名单排除

const blacklist = this.blacklist;
const excludeMatches = [];
if (blacklist.length) {
const result = dealPatternMatches(blacklist, {
exclude: true,
});
excludeMatches.push(...result.patternResult);
const excludeGlobs = [];
const rules = extractUrlPatterns([...blacklist.map((e) => `@include ${e}`)]);
for (const rule of rules) {
if (rule.ruleType === RuleType.MATCH_INCLUDE) {
// matches -> excludeMatches
excludeMatches.push(rule.patternString);
} else if (rule.ruleType === RuleType.GLOB_INCLUDE) {
// includeGlobs -> excludeGlobs
excludeGlobs.push(rule.patternString);
}
}

messageFlag = await this.getAndGenMessageFlag();
Expand All @@ -634,6 +635,7 @@ export class RuntimeService {
world: "MAIN",
runAt: "document_start",
excludeMatches,
excludeGlobs,
},
// 注册content
{
Expand All @@ -644,6 +646,7 @@ export class RuntimeService {
runAt: "document_start",
world: "USER_SCRIPT",
excludeMatches,
excludeGlobs,
},
];
try {
Expand Down Expand Up @@ -722,18 +725,13 @@ export class RuntimeService {

syncAddScriptMatch(item: ScriptMatchInfo) {
// 清理一下老数据
this.scriptMatch.del(item.uuid);
this.scriptCustomizeMatch.del(item.uuid);
this.scriptMatch.clearRules(item.uuid);
this.scriptCustomizeMatch.clearRules(item.uuid);
// 添加新的数据
item.matches.forEach((match) => {
this.scriptMatch.add(match, item.uuid);
});
item.excludeMatches.forEach((match) => {
this.scriptMatch.exclude(match, item.uuid);
});
item.customizeExcludeMatches.forEach((match) => {
this.scriptCustomizeMatch.add(match, item.uuid);
});
this.scriptMatch.addRules(item.uuid, item.scriptUrlPatterns);
if (item.customUrlPatterns?.length) {
this.scriptCustomizeMatch.addRules(item.uuid, item.customUrlPatterns!);
}
}

async updateScriptStatus(uuid: string, status: SCRIPT_STATUS) {
Expand All @@ -752,76 +750,74 @@ export class RuntimeService {
await this.loadScriptMatchInfo();
}
this.scriptMatchCache!.delete(uuid);
this.scriptMatch.del(uuid);
this.scriptCustomizeMatch.del(uuid);
this.scriptMatch.clearRules(uuid);
this.scriptCustomizeMatch.clearRules(uuid);
this.saveScriptMatchInfo();
}

// 构建userScript注册信息
async getAndSetUserScriptRegister(script: Script) {
const scriptRes = await this.script.buildScriptRunResource(script);
// concat 浅拷贝是为了避免修改原数组
const matches = (scriptRes.metadata["match"] || []).concat();
matches.push(...(scriptRes.metadata["include"] || []));
if (!matches.length) {
const metaMatch = scriptRes.metadata["match"];
const metaInclude = scriptRes.metadata["include"];
const metaExclude = scriptRes.metadata["exclude"];
if ((metaMatch?.length ?? 0) + (metaInclude?.length ?? 0) === 0) {
return undefined;
}

// 黑名单排除
const strBlacklist = (await this.systemConfig.getBlacklist()) as string | undefined;
const blacklist = obtainBlackList(strBlacklist);

const scriptUrlPatterns = extractUrlPatterns([
...(metaMatch || []).map((e) => `@match ${e}`),
...(metaInclude || []).map((e) => `@include ${e}`),
...(metaExclude || []).map((e) => `@exclude ${e}`),
...(blacklist || []).map((e) => `@exclude ${e}`),
]);

let customUrlPatterns: URLRuleEntry[] | null = null;

// 自定义排除
if (script.selfMetadata && script.selfMetadata.exclude) {
customUrlPatterns = extractUrlPatterns([...(script.selfMetadata.exclude || []).map((e) => `@exclude ${e}`)]);
if (customUrlPatterns.length === 0) customUrlPatterns = null;
Comment thread
CodFrm marked this conversation as resolved.
}

scriptRes.code = compileInjectScript(scriptRes, scriptRes.code);

const patternMatches = dealPatternMatches(matches);
const scriptMatchInfo: ScriptMatchInfo = Object.assign(
{ matches: patternMatches.result, excludeMatches: [], customizeExcludeMatches: [] },
scriptRes
const { matches, includeGlobs } = getApiMatchesAndGlobs(scriptUrlPatterns);

const excludeMatches = toUniquePatternStrings(
scriptUrlPatterns.filter((e) => e.ruleType === RuleType.MATCH_EXCLUDE)
);
const excludeGlobs = toUniquePatternStrings(scriptUrlPatterns.filter((e) => e.ruleType === RuleType.GLOB_EXCLUDE));

const registerScript: chrome.userScripts.RegisteredUserScript = {
id: scriptRes.uuid,
js: [{ code: scriptRes.code }],
matches: patternMatches.patternResult,
matches: matches, // primary
includeGlobs: includeGlobs, // includeGlobs applied after matches
excludeMatches: excludeMatches,
excludeGlobs: excludeGlobs,
allFrames: !scriptRes.metadata["noframes"],
world: "MAIN",
excludeMatches: [],
};

// 排除由loadPage时决定, 不使用userScript的excludeMatches处理
if (script.metadata["exclude"]) {
// concat 浅拷贝是为了避免修改原数组
const excludeMatches = script.metadata["exclude"].concat();
const result = dealPatternMatches(excludeMatches, {
exclude: true,
});

// registerScript.excludeMatches = result.patternResult;
scriptMatchInfo.excludeMatches = result.result;
}
// 自定义排除
if (script.selfMetadata && script.selfMetadata.exclude) {
const excludeMatches = script.selfMetadata.exclude;
const result = dealPatternMatches(excludeMatches, {
exclude: true,
});

// registerScript.excludeMatches.push(...result.patternResult);
scriptMatchInfo.customizeExcludeMatches = result.result;
if (scriptRes.metadata["run-at"]) {
registerScript.runAt = getRunAt(scriptRes.metadata["run-at"]);
}

// 黑名单排除
const blacklist = this.blacklist;
if (blacklist.length) {
const result = dealPatternMatches(blacklist, {
exclude: true,
});
// scriptMatchInfo.excludeMatches.push(...result.result);
registerScript.excludeMatches!.push(...result.patternResult);
}
const scriptMatchInfo = Object.assign(
{
scriptUrlPatterns: scriptUrlPatterns,
customUrlPatterns: customUrlPatterns,
},
scriptRes
) as ScriptMatchInfo;

// 将脚本match信息放入缓存中
await this.addScriptMatch(scriptMatchInfo);

if (scriptRes.metadata["run-at"]) {
registerScript.runAt = getRunAt(scriptRes.metadata["run-at"]);
}
this.addScriptMatch(scriptMatchInfo);

return {
scriptMatchInfo,
Expand Down
8 changes: 4 additions & 4 deletions src/app/service/service_worker/types.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { Script, ScriptRunResource, SCRIPT_RUN_STATUS, SCMetadata } from "@App/app/repo/scripts";
import { type URLRuleEntry } from "@App/pkg/utils/url_matcher";
import { type GetSender } from "@Packages/message/server";

export type InstallSource = "user" | "system" | "sync" | "subscribe" | "vscode";

// 为了优化性能,存储到缓存时删除了code、value与resource
export interface ScriptMatchInfo extends ScriptRunResource {
matches: string[];
excludeMatches: string[];
customizeExcludeMatches: string[];
scriptUrlPatterns: URLRuleEntry[];
customUrlPatterns: URLRuleEntry[] | null;
}

export interface ScriptLoadInfo extends ScriptMatchInfo {
Expand Down Expand Up @@ -86,5 +86,5 @@ export type ScriptMenu = {
runNum: number; // 脚本运行次数
runNumByIframe: number; // iframe运行次数
menus: ScriptMenuItem[]; // 脚本菜单
customExclude: string[]; // 自定义排除
customUrlPatterns: URLRuleEntry[] | null; // 自定义排除
};
24 changes: 15 additions & 9 deletions src/pages/components/ScriptMenuList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,22 @@ import { popupClient, runtimeClient, scriptClient } from "@App/pages/store/featu
import { messageQueue, systemConfig } from "@App/pages/store/global";
import { i18nName } from "@App/locales/locales";
import { type TScriptRunStatus } from "@App/app/service/queue";
import { isUrlMatch, RuleTypeBit } from "@App/pkg/utils/url_matcher";

const CollapseItem = Collapse.Item;

function isExclude(script: ScriptMenu, host: string) {
if (!script.customExclude) {
function isExclude(script: ScriptMenu, url: URL) {
const rules = script.customUrlPatterns;
const href = url.href;
if (!rules) {
return false;
}
for (let i = 0; i < script.customExclude.length; i += 1) {
if (script.customExclude[i] === `*://${host}/*`) {
return true;
for (const rule of rules) {
if (!(rule.ruleType & RuleTypeBit.INCLUSION)) {
// exclude
if (!isUrlMatch(href, rule)) {
return true;
}
}
}
return false;
Expand Down Expand Up @@ -199,8 +205,8 @@ const ScriptMenuList = React.memo(
window.close();
}, []);

const handleExcludeUrl = useCallback((item: ScriptMenu, urlHost: string) => {
scriptClient.excludeUrl(item.uuid, `*://${urlHost}/*`, isExclude(item, urlHost)).finally(() => {
const handleExcludeUrl = useCallback((item: ScriptMenu, excludePattern: string, currentUrl: URL) => {
scriptClient.excludeUrl(item.uuid, excludePattern, isExclude(item, currentUrl)).finally(() => {
window.close();
});
}, []);
Expand Down Expand Up @@ -313,9 +319,9 @@ const ScriptMenuList = React.memo(
status="warning"
type="secondary"
icon={<IconMinus />}
onClick={() => handleExcludeUrl(item, url.host)}
onClick={() => handleExcludeUrl(item, `*://${url.host}/*`, url)}
>
{(isExclude(item, url.host) ? t("exclude_on") : t("exclude_off")).replace("$0", `${url.host}`)}
{(isExclude(item, url) ? t("exclude_on") : t("exclude_off")).replace("$0", `${url.host}`)}
</Button>
)}
<Popconfirm
Expand Down
Loading
Loading