脚本匹配与注入
Some checks failed
build / Build (push) Failing after 5s
test / Run tests (push) Failing after 8s
Some checks failed
build / Build (push) Failing after 5s
test / Run tests (push) Failing after 8s
This commit is contained in:
parent
9ce1826a34
commit
651384f12c
@ -8,13 +8,22 @@ import { ScriptService } from "./script";
|
|||||||
import { runScript, stopScript } from "../offscreen/client";
|
import { runScript, stopScript } from "../offscreen/client";
|
||||||
import { getRunAt } from "./utils";
|
import { getRunAt } from "./utils";
|
||||||
import { randomString } from "@App/pkg/utils/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 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 {
|
export class RuntimeService {
|
||||||
scriptDAO: ScriptDAO = new ScriptDAO();
|
scriptDAO: ScriptDAO = new ScriptDAO();
|
||||||
|
|
||||||
|
scriptMatch: UrlMatch<string> = new UrlMatch<string>();
|
||||||
|
scriptMatchCache: Map<string, ScriptMatchInfo> | null | undefined;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private group: Group,
|
private group: Group,
|
||||||
private sender: MessageSend,
|
private sender: MessageSend,
|
||||||
@ -99,12 +108,23 @@ export class RuntimeService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async pageLoad(_, sender: GetSender) {
|
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;
|
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<void> | 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<string, ScriptMatchInfo>();
|
||||||
|
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) {
|
async registryPageScript(script: Script) {
|
||||||
if (await Cache.getInstance().has("registryScript:" + script.uuid)) {
|
if (await Cache.getInstance().has("registryScript:" + script.uuid)) {
|
||||||
return;
|
return;
|
||||||
@ -170,6 +254,11 @@ export class RuntimeService {
|
|||||||
|
|
||||||
matches.push(...(script.metadata["include"] || []));
|
matches.push(...(script.metadata["include"] || []));
|
||||||
const patternMatches = dealPatternMatches(matches);
|
const patternMatches = dealPatternMatches(matches);
|
||||||
|
const scriptMatchInfo: ScriptMatchInfo = Object.assign(
|
||||||
|
{ matches: patternMatches.result, excludeMatches: [] },
|
||||||
|
scriptRes
|
||||||
|
);
|
||||||
|
|
||||||
const registerScript: chrome.userScripts.RegisteredUserScript = {
|
const registerScript: chrome.userScripts.RegisteredUserScript = {
|
||||||
id: scriptRes.uuid,
|
id: scriptRes.uuid,
|
||||||
js: [{ code: scriptRes.code }],
|
js: [{ code: scriptRes.code }],
|
||||||
@ -186,13 +275,16 @@ export class RuntimeService {
|
|||||||
const result = dealPatternMatches(excludeMatches);
|
const result = dealPatternMatches(excludeMatches);
|
||||||
|
|
||||||
registerScript.excludeMatches = result.patternResult;
|
registerScript.excludeMatches = result.patternResult;
|
||||||
|
scriptMatchInfo.excludeMatches = result.result;
|
||||||
}
|
}
|
||||||
if (script.metadata["run-at"]) {
|
if (script.metadata["run-at"]) {
|
||||||
registerScript.runAt = getRunAt(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);
|
Cache.getInstance().set("registryScript:" + script.uuid, true);
|
||||||
|
// 将脚本match信息放入缓存中
|
||||||
|
this.addScriptMatch(scriptMatchInfo);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,9 +15,9 @@ const logger = new LoggerCore({
|
|||||||
|
|
||||||
const server = new Server("inject", msg);
|
const server = new Server("inject", msg);
|
||||||
|
|
||||||
server.on("pageLoad", (data: ScriptRunResouce[]) => {
|
server.on("pageLoad", (data: { scripts: ScriptRunResouce[] }) => {
|
||||||
logger.logger().debug("inject start");
|
logger.logger().debug("inject start");
|
||||||
console.log("inject", data);
|
console.log("inject", data);
|
||||||
const runtime = new InjectRuntime(msg, data);
|
const runtime = new InjectRuntime(msg, data.scripts);
|
||||||
runtime.start();
|
runtime.start();
|
||||||
});
|
});
|
||||||
|
@ -12,6 +12,14 @@ export default class Match<T> {
|
|||||||
|
|
||||||
protected rule = new Map<string, T[]>();
|
protected rule = new Map<string, T[]>();
|
||||||
|
|
||||||
|
protected kv = new Map<string, T>();
|
||||||
|
|
||||||
|
forEach(fn: (val: T, key: string) => void) {
|
||||||
|
this.kv.forEach((val, key) => {
|
||||||
|
fn(val, key);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
protected parseURL(url: string): Url | undefined {
|
protected parseURL(url: string): Url | undefined {
|
||||||
if (url.indexOf("*http") === 0) {
|
if (url.indexOf("*http") === 0) {
|
||||||
url = url.substring(1);
|
url = url.substring(1);
|
||||||
@ -112,6 +120,7 @@ export default class Match<T> {
|
|||||||
this.rule.set(re, rule);
|
this.rule.set(re, rule);
|
||||||
}
|
}
|
||||||
rule.push(val);
|
rule.push(val);
|
||||||
|
this.kv.set(Match.getId(val), val);
|
||||||
this.delCache();
|
this.delCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,7 +138,6 @@ export default class Match<T> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.warn("bad match rule", Logger.E(e));
|
console.warn("bad match rule", Logger.E(e));
|
||||||
// LoggerCore.getLogger({ component: "match" }).warn(
|
// LoggerCore.getLogger({ component: "match" }).warn(
|
||||||
// "bad match rule",
|
// "bad match rule",
|
||||||
@ -141,10 +149,7 @@ export default class Match<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected static getId(val: any): string {
|
protected static getId(val: any): string {
|
||||||
if (typeof val === "object") {
|
return (<{ uuid: string }>(<unknown>val)).uuid;
|
||||||
return (<{ uuid: string }>(<unknown>val)).uuid;
|
|
||||||
}
|
|
||||||
return <string>(<unknown>val);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public del(val: T) {
|
public del(val: T) {
|
||||||
|
@ -33,7 +33,12 @@ export default class ExecScript {
|
|||||||
|
|
||||||
GM_info: any;
|
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.scriptRes = scriptRes;
|
||||||
this.logger = LoggerCore.getInstance().logger({
|
this.logger = LoggerCore.getInstance().logger({
|
||||||
component: "exec",
|
component: "exec",
|
||||||
@ -42,7 +47,11 @@ export default class ExecScript {
|
|||||||
});
|
});
|
||||||
this.GM_info = GMApi.GM_info(this.scriptRes);
|
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 } = {};
|
const grantMap: { [key: string]: boolean } = {};
|
||||||
scriptRes.metadata.grant?.forEach((key) => {
|
scriptRes.metadata.grant?.forEach((key) => {
|
||||||
grantMap[key] = true;
|
grantMap[key] = true;
|
||||||
|
@ -1,11 +1,48 @@
|
|||||||
import { ScriptRunResouce } from "@App/app/repo/scripts";
|
import { ScriptRunResouce } from "@App/app/repo/scripts";
|
||||||
import { Message } from "@Packages/message/server";
|
import { Message } from "@Packages/message/server";
|
||||||
|
import ExecScript from "./exec_script";
|
||||||
|
import { addStyle, ScriptFunc } from "./utils";
|
||||||
|
|
||||||
export class InjectRuntime {
|
export class InjectRuntime {
|
||||||
|
execList: ExecScript[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private msg: Message,
|
private msg: Message,
|
||||||
private scripts: ScriptRunResouce[]
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,18 +32,6 @@ export function compileInjectScript(script: ScriptRunResouce): string {
|
|||||||
return `window['${script.flag}']=function(context,GM_info){\n${script.code}\n}`;
|
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依赖
|
// 设置api依赖
|
||||||
function setDepend(context: { [key: string]: any }, apiVal: ApiValue) {
|
function setDepend(context: { [key: string]: any }, apiVal: ApiValue) {
|
||||||
if (apiVal.param.depend) {
|
if (apiVal.param.depend) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user