diff --git a/src/app/service/service_worker/runtime.ts b/src/app/service/service_worker/runtime.ts index 7d0ca0b..383f7a0 100644 --- a/src/app/service/service_worker/runtime.ts +++ b/src/app/service/service_worker/runtime.ts @@ -8,13 +8,22 @@ import { ScriptService } from "./script"; import { runScript, stopScript } from "../offscreen/client"; import { getRunAt } from "./utils"; import { randomString } from "@App/pkg/utils/utils"; -import { compileInjectScript, compileInjectScriptInfo, compileScriptCode } from "@App/runtime/content/utils"; +import { compileInjectScript, compileScriptCode } from "@App/runtime/content/utils"; import Cache from "@App/app/cache"; -import { dealPatternMatches } from "@App/pkg/utils/match"; +import { dealPatternMatches, UrlMatch } from "@App/pkg/utils/match"; + +// 为了优化性能,存储到缓存时删除了code与value +export interface ScriptMatchInfo extends ScriptRunResouce { + matches: string[]; + excludeMatches: string[]; +} export class RuntimeService { scriptDAO: ScriptDAO = new ScriptDAO(); + scriptMatch: UrlMatch = new UrlMatch(); + scriptMatchCache: Map | null | undefined; + constructor( private group: Group, private sender: MessageSend, @@ -99,12 +108,23 @@ export class RuntimeService { } async pageLoad(_, sender: GetSender) { - const scriptFlag = await this.messageFlag(); + const [scriptFlag, match] = await Promise.all([this.messageFlag(), this.loadScriptMatchInfo()]); const chromeSender = sender.getSender() as chrome.runtime.MessageSender; - // 匹配当前页面的脚本 - console.log("pageLoad"); - return Promise.resolve({ flag: scriptFlag }); + // 匹配当前页面的脚本 + const matchScriptUuid = match.match(chromeSender.url!); + console.log("pageLoad", match.match(chromeSender.url!)); + const scripts = await Promise.all( + matchScriptUuid.map( + (uuid) => + new Promise((resolve) => { + // 获取value + const scriptRes = Object.assign({}, this.scriptMatchCache?.get(uuid)); + resolve(scriptRes); + }) + ) + ); + return Promise.resolve({ flag: scriptFlag, scripts: scripts }); } // 停止脚本 @@ -155,6 +175,70 @@ export class RuntimeService { }); } + loadScripting: Promise | null | undefined; + + // 加载脚本匹配信息,由于service_worker的机制,如果由不活动状态恢复过来时,会优先触发事件 + // 可能当时会没有脚本匹配信息,所以使用脚本信息时,尽量使用此方法获取 + async loadScriptMatchInfo() { + if (this.scriptMatchCache) { + return this.scriptMatch; + } + if (this.loadScripting) { + await this.loadScripting; + } else { + // 如果没有缓存, 则创建一个新的缓存 + this.loadScripting = Cache.getInstance() + .get("scriptMatch") + .then((data: { [key: string]: ScriptMatchInfo }) => { + this.scriptMatchCache = new Map(); + if (data) { + Object.keys(data).forEach((key) => { + const item = data[key]; + this.scriptMatchCache!.set(item.uuid, item); + item.matches.forEach((match) => { + this.scriptMatch.add(match, item.uuid); + }); + item.excludeMatches.forEach((match) => { + this.scriptMatch.exclude(match, item.uuid); + }); + }); + } + }); + await this.loadScripting; + this.loadScripting = null; + } + return this.scriptMatch; + } + + // 保存脚本匹配信息 + async saveScriptMatchInfo() { + if (!this.scriptMatchCache) { + return; + } + const scriptMatch = {} as { [key: string]: ScriptMatchInfo }; + this.scriptMatchCache.forEach((val, key) => { + scriptMatch[key] = val; + // 优化性能,将不需要的信息去掉 + scriptMatch[key].code = ""; + scriptMatch[key].value = {}; + }); + return await Cache.getInstance().set("scriptMatch", scriptMatch); + } + + async addScriptMatch(item: ScriptMatchInfo) { + if (!this.scriptMatchCache) { + await this.loadScriptMatchInfo(); + } + this.scriptMatchCache!.set(item.uuid, item); + item.matches.forEach((match) => { + this.scriptMatch.add(match, item.uuid); + }); + item.excludeMatches.forEach((match) => { + this.scriptMatch.exclude(match, item.uuid); + }); + this.saveScriptMatchInfo(); + } + async registryPageScript(script: Script) { if (await Cache.getInstance().has("registryScript:" + script.uuid)) { return; @@ -170,6 +254,11 @@ export class RuntimeService { matches.push(...(script.metadata["include"] || [])); const patternMatches = dealPatternMatches(matches); + const scriptMatchInfo: ScriptMatchInfo = Object.assign( + { matches: patternMatches.result, excludeMatches: [] }, + scriptRes + ); + const registerScript: chrome.userScripts.RegisteredUserScript = { id: scriptRes.uuid, js: [{ code: scriptRes.code }], @@ -186,13 +275,16 @@ export class RuntimeService { const result = dealPatternMatches(excludeMatches); registerScript.excludeMatches = result.patternResult; + scriptMatchInfo.excludeMatches = result.result; } if (script.metadata["run-at"]) { registerScript.runAt = getRunAt(script.metadata["run-at"]); } - chrome.userScripts.register([registerScript], () => { + chrome.userScripts.register([registerScript], async () => { // 标记为已注册 Cache.getInstance().set("registryScript:" + script.uuid, true); + // 将脚本match信息放入缓存中 + this.addScriptMatch(scriptMatchInfo); }); } diff --git a/src/inject.ts b/src/inject.ts index c552361..d3418a1 100644 --- a/src/inject.ts +++ b/src/inject.ts @@ -15,9 +15,9 @@ const logger = new LoggerCore({ const server = new Server("inject", msg); -server.on("pageLoad", (data: ScriptRunResouce[]) => { +server.on("pageLoad", (data: { scripts: ScriptRunResouce[] }) => { logger.logger().debug("inject start"); console.log("inject", data); - const runtime = new InjectRuntime(msg, data); + const runtime = new InjectRuntime(msg, data.scripts); runtime.start(); }); diff --git a/src/pkg/utils/match.ts b/src/pkg/utils/match.ts index f7a6651..e6ecf17 100644 --- a/src/pkg/utils/match.ts +++ b/src/pkg/utils/match.ts @@ -12,6 +12,14 @@ export default class Match { protected rule = new Map(); + protected kv = new Map(); + + forEach(fn: (val: T, key: string) => void) { + this.kv.forEach((val, key) => { + fn(val, key); + }); + } + protected parseURL(url: string): Url | undefined { if (url.indexOf("*http") === 0) { url = url.substring(1); @@ -112,6 +120,7 @@ export default class Match { this.rule.set(re, rule); } rule.push(val); + this.kv.set(Match.getId(val), val); this.delCache(); } @@ -129,7 +138,6 @@ export default class Match { } }); } catch (e) { - // eslint-disable-next-line no-console console.warn("bad match rule", Logger.E(e)); // LoggerCore.getLogger({ component: "match" }).warn( // "bad match rule", @@ -141,10 +149,7 @@ export default class Match { } protected static getId(val: any): string { - if (typeof val === "object") { - return (<{ uuid: string }>(val)).uuid; - } - return (val); + return (<{ uuid: string }>(val)).uuid; } public del(val: T) { diff --git a/src/runtime/content/exec_script.ts b/src/runtime/content/exec_script.ts index 231c81e..7b9e5f7 100644 --- a/src/runtime/content/exec_script.ts +++ b/src/runtime/content/exec_script.ts @@ -33,7 +33,12 @@ export default class ExecScript { GM_info: any; - constructor(scriptRes: ScriptRunResouce, message: Message, thisContext?: { [key: string]: any }) { + constructor( + scriptRes: ScriptRunResouce, + message: Message, + code: string | ScriptFunc, + thisContext?: { [key: string]: any } + ) { this.scriptRes = scriptRes; this.logger = LoggerCore.getInstance().logger({ component: "exec", @@ -42,7 +47,11 @@ export default class ExecScript { }); this.GM_info = GMApi.GM_info(this.scriptRes); // 构建脚本资源 - this.scriptFunc = compileScript(this.scriptRes.code); + if (typeof code === "string") { + this.scriptFunc = compileScript(code); + } else { + this.scriptFunc = code; + } const grantMap: { [key: string]: boolean } = {}; scriptRes.metadata.grant?.forEach((key) => { grantMap[key] = true; diff --git a/src/runtime/content/inject.ts b/src/runtime/content/inject.ts index 0441090..3637eb4 100644 --- a/src/runtime/content/inject.ts +++ b/src/runtime/content/inject.ts @@ -1,11 +1,48 @@ import { ScriptRunResouce } from "@App/app/repo/scripts"; import { Message } from "@Packages/message/server"; +import ExecScript from "./exec_script"; +import { addStyle, ScriptFunc } from "./utils"; export class InjectRuntime { + execList: ExecScript[] = []; + constructor( private msg: Message, private scripts: ScriptRunResouce[] ) {} - start(){} + start() { + this.scripts.forEach((script) => { + // @ts-ignore + const scriptFunc = window[script.flag]; + if (scriptFunc) { + this.execScript(script, scriptFunc); + } else { + // 监听脚本加载,和屏蔽读取 + Object.defineProperty(window, script.flag, { + configurable: true, + set: (val: ScriptFunc) => { + this.execScript(script, val); + }, + }); + } + }); + } + + execScript(script: ScriptRunResouce, scriptFunc: ScriptFunc) { + // @ts-ignore + delete window[script.flag]; + const exec = new ExecScript(script, this.msg, scriptFunc); + this.execList.push(exec); + // 注入css + if (script.metadata["require-css"]) { + script.metadata["require-css"].forEach((val) => { + const res = script.resource[val]; + if (res) { + addStyle(res.content); + } + }); + } + exec.exec(); + } } diff --git a/src/runtime/content/utils.ts b/src/runtime/content/utils.ts index 8cad84e..c350ea5 100644 --- a/src/runtime/content/utils.ts +++ b/src/runtime/content/utils.ts @@ -32,18 +32,6 @@ export function compileInjectScript(script: ScriptRunResouce): string { return `window['${script.flag}']=function(context,GM_info){\n${script.code}\n}`; } -// 编译注入脚本信息 -export function compileInjectScriptInfo( - messageFlag: string, - script: ScriptRunResouce, - injectScriptInfoCode: string -): string { - return ( - `(function (MessageFlag, ScriptFlag, ScriptUuid) {\n${injectScriptInfoCode}\n})` + - `('${messageFlag}', '${script.flag}', '${script.uuid}');` - ); -} - // 设置api依赖 function setDepend(context: { [key: string]: any }, apiVal: ApiValue) { if (apiVal.param.depend) {