Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 3 additions & 2 deletions example/tests/early_inject_content_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
// @grant GM_log
// @grant GM_info
// @grant GM_setValue
// @grant GM.setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_listValues
Expand Down Expand Up @@ -154,8 +155,8 @@
// ============ GM 存储 API 测试 ============
console.log("\n%c--- GM 存储 API 测试 ---", "color: orange; font-weight: bold;");

test("GM_setValue - 字符串", () => {
GM_setValue("test_key", "content环境测试值");
await test("GM_setValue - 字符串", async () => {
await GM.setValue("test_key", "content环境测试值");
const value = GM_getValue("test_key");
assert("content环境测试值", value, "应该正确保存和读取字符串");
});
Expand Down
5 changes: 3 additions & 2 deletions example/tests/early_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
// @grant GM_log
// @grant GM_info
// @grant GM_setValue
// @grant GM.setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_listValues
Expand Down Expand Up @@ -182,8 +183,8 @@
// ============ GM 存储 API 测试 ============
console.log("\n%c--- GM 存储 API 测试 ---", "color: orange; font-weight: bold;");

await test("GM_setValue - 字符串", () => {
GM_setValue("test_key", "早期脚本测试值");
await test("GM_setValue - 字符串", async () => {
await GM.setValue("test_key", "早期脚本测试值");
const value = GM_getValue("test_key");
assert("早期脚本测试值", value, "应该正确保存和读取字符串");
});
Expand Down
11 changes: 6 additions & 5 deletions example/tests/inject_content_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@
// @grant GM_log
// @grant GM_info
// @grant GM_setValue
// @grant GM.setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_listValues
// @run-at document-start
// ==/UserScript==

(function () {
(async function () {
"use strict";

console.log("%c=== Content环境 GM API 测试开始 ===", "color: blue; font-size: 16px; font-weight: bold;");
Expand All @@ -28,10 +29,10 @@
};

// 测试辅助函数
function test(name, fn) {
async function test(name, fn) {
testResults.total++;
try {
fn();
await fn();
testResults.passed++;
console.log(`%c✓ ${name}`, "color: green;");
return true;
Expand Down Expand Up @@ -116,8 +117,8 @@
// ============ GM 存储 API 测试 ============
console.log("\n%c--- GM 存储 API 测试 ---", "color: orange; font-weight: bold;");

test("GM_setValue - 字符串", () => {
GM_setValue("test_key", "content环境测试值");
await test("GM_setValue - 字符串", async () => {
await GM.setValue("test_key", "content环境测试值");
const value = GM_getValue("test_key");
assert("content环境测试值", value, "应该正确保存和读取字符串");
});
Expand Down
20 changes: 16 additions & 4 deletions packages/message/custom_event_message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,19 @@ export class CustomEventMessage implements Message {
// 关联dom目标
relatedTarget: Map<number, EventTarget> = new Map();

protected flags: {
injectFlag: string;
contentFlag: string;
messageFlag: string;
};

constructor(
protected flag: string,
flags: typeof CustomEventMessage.prototype.flags | string,
protected isContent: boolean
) {
window.addEventListener((isContent ? "ct" : "fd") + flag, (event) => {
flags = typeof flags === "string" ? { injectFlag: "inject", contentFlag: "content", messageFlag: flags } : flags;
this.flags = flags;
window.addEventListener((isContent ? flags.contentFlag : flags.injectFlag) + flags.messageFlag, (event) => {
if (event instanceof MouseEvent && event.movementX && event.relatedTarget) {
this.relatedTarget.set(event.movementX, event.relatedTarget!);
} else if (event instanceof CustomEvent) {
Expand Down Expand Up @@ -95,12 +103,16 @@ export class CustomEventMessage implements Message {
}
}

const ev = new CustomEvent((this.isContent ? "fd" : "ct") + this.flag, {
const ev = new CustomEvent(this.sendEventName(), {
detail,
});
window.dispatchEvent(ev);
}

sendEventName(): string {
return (this.isContent ? this.flags.injectFlag : this.flags.contentFlag) + this.flags.messageFlag;
}

sendMessage<T = any>(data: TMessage): Promise<T> {
return new Promise((resolve: ((value: T) => void) | null) => {
const messageId = uuidv4();
Expand Down Expand Up @@ -148,7 +160,7 @@ export class CustomEventMessage implements Message {
// 先将relatedTarget转换成id发送过去
const id = ++this.relateId;
// 可以使用此种方式交互element
const ev = new MouseEvent((this.isContent ? "fd" : "ct") + this.flag, {
const ev = new MouseEvent(this.sendEventName(), {
movementX: id,
relatedTarget: target,
});
Expand Down
9 changes: 7 additions & 2 deletions packages/message/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,14 @@ describe("Server", () => {
writable: true,
});

const flags = {
contentFlag: "ct",
injectFlag: "fd",
messageFlag: "test",
};
// 创建 content 和 inject 之间的消息通道
contentMessage = new CustomEventMessage("test", true); // content 端
injectMessage = new CustomEventMessage("test", false); // inject 端
contentMessage = new CustomEventMessage(flags, true); // content 端
injectMessage = new CustomEventMessage(flags, false); // inject 端

// 服务端使用 content 消息
server = new Server("api", contentMessage);
Expand Down
9 changes: 3 additions & 6 deletions src/app/service/content/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { MessageSend } from "@Packages/message/types";
import type { GMInfoEnv } from "./types";
import type { ScriptLoadInfo } from "../service_worker/types";
import type { ScriptExecutor } from "./script_executor";
import { definePropertyListener, isInjectIntoContent } from "./utils";
import { isInjectIntoContent } from "./utils";
import { RuntimeClient } from "../service_worker/client";

// content页的处理
Expand Down Expand Up @@ -126,11 +126,8 @@ export default class ContentRuntime {
);
}

pageLoad() {
// 处理content EarlyScript
definePropertyListener(window, "EarlyScriptFlag", (flag: string[]) => {
this.scriptExecutor.checkEarlyStartScript(flag);
});
pageLoad(messageFlags: MessageFlags) {
this.scriptExecutor.checkEarlyStartScript("content", messageFlags);

const client = new RuntimeClient(this.senderToExt);
// 向service_worker请求脚本列表
Expand Down
50 changes: 31 additions & 19 deletions src/app/service/content/script_executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ export type ExecScriptEntry = {

// 脚本执行器
export class ScriptExecutor {
earlyScriptFlag: Set<string> = new Set();
execMap: Map<string, ExecScript> = new Map();

envInfo: GMInfoEnv | undefined;
earlyScriptFlag: string[] = [];

constructor(private msg: Message) {}

Expand Down Expand Up @@ -51,41 +51,53 @@ export class ScriptExecutor {
envInfo: this.envInfo!,
});
};
// 监听脚本加载
scripts.forEach((script) => {
const flag = script.flag;
// 如果是EarlyScriptFlag,处理沙盒环境
if (this.earlyScriptFlag.includes(flag)) {
if (this.earlyScriptFlag.has(flag)) {
for (const val of this.execMap.values()) {
if (val.scriptRes.flag === flag) {
// 处理早期脚本的沙盒环境
val.updateEarlyScriptGMInfo(this.envInfo!);
break;
return;
}
}
return;
}

definePropertyListener(window, flag, (val: ScriptFunc) => {
loadExec(script, val);
});
});
}

checkEarlyStartScript(earlyStarFlag: string[]) {
this.earlyScriptFlag = earlyStarFlag;
const loadExec = (flag: string, scriptFunc: any) => {
this.execScriptEntry({
scriptLoadInfo: scriptFunc.scriptInfo,
scriptFunc: scriptFunc.func,
scriptFlag: flag,
envInfo: {},
});
};
this.earlyScriptFlag.forEach((flag) => {
definePropertyListener(window, flag, (val: PreScriptFunc) => {
loadExec(flag, val);
});
checkEarlyStartScript(env: "content" | "inject", messageFlags: MessageFlags) {
const eventNamePrefix = env === "content" ? messageFlags.contentFlag : messageFlags.injectFlag;
// 监听 脚本加载
// 适用于此「通知环境加载完成」代码执行后的脚本加载
window.addEventListener(`${eventNamePrefix}${messageFlags.scriptLoadComplete}`, (event) => {
if (event instanceof CustomEvent) {
if (typeof event.detail.scriptFlag === "string") {
event.preventDefault(); // dispatchEvent 会回传 false -> 分离环境也能得知环境加载代码已执行
const scriptFlag = event.detail.scriptFlag;
this.execEarlyScript(scriptFlag);
}
}
});
// 通知 环境 加载完成
// 适用于此「通知环境加载完成」代码执行前的脚本加载
const ev = new CustomEvent(eventNamePrefix + messageFlags.envLoadComplete);
window.dispatchEvent(ev);
}

execEarlyScript(flag: string) {
const scriptFunc = (window as any)[flag] as PreScriptFunc;
this.execScriptEntry({
scriptLoadInfo: scriptFunc.scriptInfo,
scriptFunc: scriptFunc.func,
scriptFlag: flag,
envInfo: {},
});
this.earlyScriptFlag.add(flag);
}

execScriptEntry(scriptEntry: ExecScriptEntry) {
Expand Down
16 changes: 15 additions & 1 deletion src/app/service/content/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,29 @@ export function compileInjectScriptByFlag(
* 将脚本函数编译为预注入脚本代码
*/
export function compilePreInjectScript(
messageFlags: MessageFlags,
script: ScriptLoadInfo,
scriptCode: string,
autoDeleteMountFunction: boolean = false
): string {
const eventNamePrefix = isInjectIntoContent(script.metadata) ? messageFlags.contentFlag : messageFlags.injectFlag;
const autoDeleteMountCode = autoDeleteMountFunction ? `try{delete window['${script.flag}']}catch(e){}` : "";
return `window['${script.flag}'] = {
scriptInfo: ${JSON.stringify(script)},
func: function(){${autoDeleteMountCode}${scriptCode}}
}`;
};
(() => {
const f = () => {
const event = new CustomEvent('${eventNamePrefix}${messageFlags.scriptLoadComplete}',
{ cancelable: true, detail: { scriptFlag: '${script.flag}' } });
return window.dispatchEvent(event);
};
const noCheckEarlyStartScript = f();
if (noCheckEarlyStartScript) {
window.addEventListener('${eventNamePrefix}${messageFlags.envLoadComplete}', f, { once: true });
}
})();
`;
}

export function addStyle(css: string): HTMLStyleElement {
Expand Down
2 changes: 1 addition & 1 deletion src/app/service/queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export type TInstallScriptParams = {

export type TInstallScript = { script: TInstallScriptParams; update: boolean; upsertBy?: InstallSource };

export type TDeleteScript = { uuid: string; storageName: string; type: SCRIPT_TYPE; isEarlyStart: boolean };
export type TDeleteScript = { uuid: string; storageName: string; type: SCRIPT_TYPE };

export type TSortedScript = { uuid: string; sort: number };

Expand Down
Loading
Loading