Skip to content
This repository was archived by the owner on May 13, 2025. It is now read-only.

Commit 651384f

Browse files
committed
脚本匹配与注入
1 parent 9ce1826 commit 651384f

File tree

6 files changed

+160
-29
lines changed

6 files changed

+160
-29
lines changed

src/app/service/service_worker/runtime.ts

Lines changed: 99 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,22 @@ import { ScriptService } from "./script";
88
import { runScript, stopScript } from "../offscreen/client";
99
import { getRunAt } from "./utils";
1010
import { randomString } from "@App/pkg/utils/utils";
11-
import { compileInjectScript, compileInjectScriptInfo, compileScriptCode } from "@App/runtime/content/utils";
11+
import { compileInjectScript, compileScriptCode } from "@App/runtime/content/utils";
1212
import Cache from "@App/app/cache";
13-
import { dealPatternMatches } from "@App/pkg/utils/match";
13+
import { dealPatternMatches, UrlMatch } from "@App/pkg/utils/match";
14+
15+
// 为了优化性能,存储到缓存时删除了code与value
16+
export interface ScriptMatchInfo extends ScriptRunResouce {
17+
matches: string[];
18+
excludeMatches: string[];
19+
}
1420

1521
export class RuntimeService {
1622
scriptDAO: ScriptDAO = new ScriptDAO();
1723

24+
scriptMatch: UrlMatch<string> = new UrlMatch<string>();
25+
scriptMatchCache: Map<string, ScriptMatchInfo> | null | undefined;
26+
1827
constructor(
1928
private group: Group,
2029
private sender: MessageSend,
@@ -99,12 +108,23 @@ export class RuntimeService {
99108
}
100109

101110
async pageLoad(_, sender: GetSender) {
102-
const scriptFlag = await this.messageFlag();
111+
const [scriptFlag, match] = await Promise.all([this.messageFlag(), this.loadScriptMatchInfo()]);
103112
const chromeSender = sender.getSender() as chrome.runtime.MessageSender;
104-
// 匹配当前页面的脚本
105-
console.log("pageLoad");
106113

107-
return Promise.resolve({ flag: scriptFlag });
114+
// 匹配当前页面的脚本
115+
const matchScriptUuid = match.match(chromeSender.url!);
116+
console.log("pageLoad", match.match(chromeSender.url!));
117+
const scripts = await Promise.all(
118+
matchScriptUuid.map(
119+
(uuid) =>
120+
new Promise((resolve) => {
121+
// 获取value
122+
const scriptRes = Object.assign({}, this.scriptMatchCache?.get(uuid));
123+
resolve(scriptRes);
124+
})
125+
)
126+
);
127+
return Promise.resolve({ flag: scriptFlag, scripts: scripts });
108128
}
109129

110130
// 停止脚本
@@ -155,6 +175,70 @@ export class RuntimeService {
155175
});
156176
}
157177

178+
loadScripting: Promise<void> | null | undefined;
179+
180+
// 加载脚本匹配信息,由于service_worker的机制,如果由不活动状态恢复过来时,会优先触发事件
181+
// 可能当时会没有脚本匹配信息,所以使用脚本信息时,尽量使用此方法获取
182+
async loadScriptMatchInfo() {
183+
if (this.scriptMatchCache) {
184+
return this.scriptMatch;
185+
}
186+
if (this.loadScripting) {
187+
await this.loadScripting;
188+
} else {
189+
// 如果没有缓存, 则创建一个新的缓存
190+
this.loadScripting = Cache.getInstance()
191+
.get("scriptMatch")
192+
.then((data: { [key: string]: ScriptMatchInfo }) => {
193+
this.scriptMatchCache = new Map<string, ScriptMatchInfo>();
194+
if (data) {
195+
Object.keys(data).forEach((key) => {
196+
const item = data[key];
197+
this.scriptMatchCache!.set(item.uuid, item);
198+
item.matches.forEach((match) => {
199+
this.scriptMatch.add(match, item.uuid);
200+
});
201+
item.excludeMatches.forEach((match) => {
202+
this.scriptMatch.exclude(match, item.uuid);
203+
});
204+
});
205+
}
206+
});
207+
await this.loadScripting;
208+
this.loadScripting = null;
209+
}
210+
return this.scriptMatch;
211+
}
212+
213+
// 保存脚本匹配信息
214+
async saveScriptMatchInfo() {
215+
if (!this.scriptMatchCache) {
216+
return;
217+
}
218+
const scriptMatch = {} as { [key: string]: ScriptMatchInfo };
219+
this.scriptMatchCache.forEach((val, key) => {
220+
scriptMatch[key] = val;
221+
// 优化性能,将不需要的信息去掉
222+
scriptMatch[key].code = "";
223+
scriptMatch[key].value = {};
224+
});
225+
return await Cache.getInstance().set("scriptMatch", scriptMatch);
226+
}
227+
228+
async addScriptMatch(item: ScriptMatchInfo) {
229+
if (!this.scriptMatchCache) {
230+
await this.loadScriptMatchInfo();
231+
}
232+
this.scriptMatchCache!.set(item.uuid, item);
233+
item.matches.forEach((match) => {
234+
this.scriptMatch.add(match, item.uuid);
235+
});
236+
item.excludeMatches.forEach((match) => {
237+
this.scriptMatch.exclude(match, item.uuid);
238+
});
239+
this.saveScriptMatchInfo();
240+
}
241+
158242
async registryPageScript(script: Script) {
159243
if (await Cache.getInstance().has("registryScript:" + script.uuid)) {
160244
return;
@@ -170,6 +254,11 @@ export class RuntimeService {
170254

171255
matches.push(...(script.metadata["include"] || []));
172256
const patternMatches = dealPatternMatches(matches);
257+
const scriptMatchInfo: ScriptMatchInfo = Object.assign(
258+
{ matches: patternMatches.result, excludeMatches: [] },
259+
scriptRes
260+
);
261+
173262
const registerScript: chrome.userScripts.RegisteredUserScript = {
174263
id: scriptRes.uuid,
175264
js: [{ code: scriptRes.code }],
@@ -186,13 +275,16 @@ export class RuntimeService {
186275
const result = dealPatternMatches(excludeMatches);
187276

188277
registerScript.excludeMatches = result.patternResult;
278+
scriptMatchInfo.excludeMatches = result.result;
189279
}
190280
if (script.metadata["run-at"]) {
191281
registerScript.runAt = getRunAt(script.metadata["run-at"]);
192282
}
193-
chrome.userScripts.register([registerScript], () => {
283+
chrome.userScripts.register([registerScript], async () => {
194284
// 标记为已注册
195285
Cache.getInstance().set("registryScript:" + script.uuid, true);
286+
// 将脚本match信息放入缓存中
287+
this.addScriptMatch(scriptMatchInfo);
196288
});
197289
}
198290

src/inject.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ const logger = new LoggerCore({
1515

1616
const server = new Server("inject", msg);
1717

18-
server.on("pageLoad", (data: ScriptRunResouce[]) => {
18+
server.on("pageLoad", (data: { scripts: ScriptRunResouce[] }) => {
1919
logger.logger().debug("inject start");
2020
console.log("inject", data);
21-
const runtime = new InjectRuntime(msg, data);
21+
const runtime = new InjectRuntime(msg, data.scripts);
2222
runtime.start();
2323
});

src/pkg/utils/match.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ export default class Match<T> {
1212

1313
protected rule = new Map<string, T[]>();
1414

15+
protected kv = new Map<string, T>();
16+
17+
forEach(fn: (val: T, key: string) => void) {
18+
this.kv.forEach((val, key) => {
19+
fn(val, key);
20+
});
21+
}
22+
1523
protected parseURL(url: string): Url | undefined {
1624
if (url.indexOf("*http") === 0) {
1725
url = url.substring(1);
@@ -112,6 +120,7 @@ export default class Match<T> {
112120
this.rule.set(re, rule);
113121
}
114122
rule.push(val);
123+
this.kv.set(Match.getId(val), val);
115124
this.delCache();
116125
}
117126

@@ -129,7 +138,6 @@ export default class Match<T> {
129138
}
130139
});
131140
} catch (e) {
132-
// eslint-disable-next-line no-console
133141
console.warn("bad match rule", Logger.E(e));
134142
// LoggerCore.getLogger({ component: "match" }).warn(
135143
// "bad match rule",
@@ -141,10 +149,7 @@ export default class Match<T> {
141149
}
142150

143151
protected static getId(val: any): string {
144-
if (typeof val === "object") {
145-
return (<{ uuid: string }>(<unknown>val)).uuid;
146-
}
147-
return <string>(<unknown>val);
152+
return (<{ uuid: string }>(<unknown>val)).uuid;
148153
}
149154

150155
public del(val: T) {

src/runtime/content/exec_script.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,12 @@ export default class ExecScript {
3333

3434
GM_info: any;
3535

36-
constructor(scriptRes: ScriptRunResouce, message: Message, thisContext?: { [key: string]: any }) {
36+
constructor(
37+
scriptRes: ScriptRunResouce,
38+
message: Message,
39+
code: string | ScriptFunc,
40+
thisContext?: { [key: string]: any }
41+
) {
3742
this.scriptRes = scriptRes;
3843
this.logger = LoggerCore.getInstance().logger({
3944
component: "exec",
@@ -42,7 +47,11 @@ export default class ExecScript {
4247
});
4348
this.GM_info = GMApi.GM_info(this.scriptRes);
4449
// 构建脚本资源
45-
this.scriptFunc = compileScript(this.scriptRes.code);
50+
if (typeof code === "string") {
51+
this.scriptFunc = compileScript(code);
52+
} else {
53+
this.scriptFunc = code;
54+
}
4655
const grantMap: { [key: string]: boolean } = {};
4756
scriptRes.metadata.grant?.forEach((key) => {
4857
grantMap[key] = true;

src/runtime/content/inject.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,48 @@
11
import { ScriptRunResouce } from "@App/app/repo/scripts";
22
import { Message } from "@Packages/message/server";
3+
import ExecScript from "./exec_script";
4+
import { addStyle, ScriptFunc } from "./utils";
35

46
export class InjectRuntime {
7+
execList: ExecScript[] = [];
8+
59
constructor(
610
private msg: Message,
711
private scripts: ScriptRunResouce[]
812
) {}
913

10-
start(){}
14+
start() {
15+
this.scripts.forEach((script) => {
16+
// @ts-ignore
17+
const scriptFunc = window[script.flag];
18+
if (scriptFunc) {
19+
this.execScript(script, scriptFunc);
20+
} else {
21+
// 监听脚本加载,和屏蔽读取
22+
Object.defineProperty(window, script.flag, {
23+
configurable: true,
24+
set: (val: ScriptFunc) => {
25+
this.execScript(script, val);
26+
},
27+
});
28+
}
29+
});
30+
}
31+
32+
execScript(script: ScriptRunResouce, scriptFunc: ScriptFunc) {
33+
// @ts-ignore
34+
delete window[script.flag];
35+
const exec = new ExecScript(script, this.msg, scriptFunc);
36+
this.execList.push(exec);
37+
// 注入css
38+
if (script.metadata["require-css"]) {
39+
script.metadata["require-css"].forEach((val) => {
40+
const res = script.resource[val];
41+
if (res) {
42+
addStyle(res.content);
43+
}
44+
});
45+
}
46+
exec.exec();
47+
}
1148
}

src/runtime/content/utils.ts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,6 @@ export function compileInjectScript(script: ScriptRunResouce): string {
3232
return `window['${script.flag}']=function(context,GM_info){\n${script.code}\n}`;
3333
}
3434

35-
// 编译注入脚本信息
36-
export function compileInjectScriptInfo(
37-
messageFlag: string,
38-
script: ScriptRunResouce,
39-
injectScriptInfoCode: string
40-
): string {
41-
return (
42-
`(function (MessageFlag, ScriptFlag, ScriptUuid) {\n${injectScriptInfoCode}\n})` +
43-
`('${messageFlag}', '${script.flag}', '${script.uuid}');`
44-
);
45-
}
46-
4735
// 设置api依赖
4836
function setDepend(context: { [key: string]: any }, apiVal: ApiValue) {
4937
if (apiVal.param.depend) {

0 commit comments

Comments
 (0)