Skip to content
Draft
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
40 changes: 23 additions & 17 deletions src/app/service/content/create_context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ describe.concurrent("createContext", () => {
expect((context as any).loadScriptResolve).toBeUndefined();
});

it.concurrent("setInvalidContext 会释放监听器且后续 valueUpdate 不再触发", () => {
it.concurrent("setInvalidContext 会释放监听器且后续 valueStoreUpdate 不再触发", async () => {
const script = createScriptInfo();
const context = createContext(
script,
Expand All @@ -154,28 +154,34 @@ describe.concurrent("createContext", () => {
const listener = vi.fn();
context.GM_addValueChangeListener("foo", listener);

context.valueUpdate({
id: "remote-1",
uuid: script.uuid,
storageName: "",
sender: { runFlag: "other-run-flag", tabId: 7 },
entries: [["foo", encodeRValue("next"), encodeRValue("bar")]],
valueUpdated: true,
});
context.valueStoreUpdate(script.value, [
{
id: "remote-1",
uuid: script.uuid,
storageName: "",
sender: { runFlag: "other-run-flag", tabId: 7 },
valueChanges: [["foo", encodeRValue("next"), encodeRValue("bar")]],
},
]);
// 监听器回调延后到下一个 microTask 执行
expect(listener).not.toHaveBeenCalled();
await Promise.resolve();
expect(listener).toHaveBeenCalledWith("foo", "bar", "next", true, 7);

context.setInvalidContext();
context.setInvalidContext();
expect(context.isInvalidContext()).toBe(true);

context.valueUpdate({
id: "remote-2",
uuid: script.uuid,
storageName: "",
sender: { runFlag: "other-run-flag", tabId: 8 },
entries: [["foo", encodeRValue("again"), encodeRValue("next")]],
valueUpdated: true,
});
context.valueStoreUpdate(script.value, [
{
id: "remote-2",
uuid: script.uuid,
storageName: "",
sender: { runFlag: "other-run-flag", tabId: 8 },
valueChanges: [["foo", encodeRValue("again"), encodeRValue("next")]],
},
]);
await Promise.resolve();
expect(listener).toHaveBeenCalledTimes(1);
});
});
Expand Down
17 changes: 14 additions & 3 deletions src/app/service/content/exec_script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import type { Message } from "@Packages/message/types";
import type { ValueUpdateDataEncoded } from "./types";
import { evaluateGMInfo } from "./gm_api/gm_info";
import type { IGM_Base } from "./gm_api/gm_api";
import type { TScriptInfo } from "@App/app/repo/scripts";
import type { ScriptRunResource, TScriptInfo } from "@App/app/repo/scripts";
import { getStorageName } from "@App/pkg/utils/utils";

// 执行脚本,控制脚本执行与停止
export default class ExecScript {
Expand Down Expand Up @@ -74,8 +75,18 @@ export default class ExecScript {
this.sandboxContext?.emitEvent(event, eventId, data);
}

valueUpdate(data: ValueUpdateDataEncoded) {
this.sandboxContext?.valueUpdate(data);
valueUpdate(storageName: string, uuid: string, responses: ValueUpdateDataEncoded[]) {
const scriptRes = this.scriptRes;
if (scriptRes.uuid !== uuid && getStorageName(scriptRes) !== storageName) return;
const context = this.sandboxContext;
if (!context) return;
const contextScriptRes = context.scriptRes as ScriptRunResource | null | undefined;
if (!contextScriptRes) return;
// 以 extValueStoreCopy(SW 确认顺序的快照)为基准应用更新,使各页面 valueStore 的 key 顺序一致
contextScriptRes.value = context.extValueStoreCopy || contextScriptRes.value;
const valueStore = contextScriptRes.value;
context.valueStoreUpdate(valueStore, responses);
context.extValueStoreCopy = { ...valueStore };
}

execContext: any;
Expand Down
52 changes: 28 additions & 24 deletions src/app/service/content/gm_api/gm_api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { compileScript, compileScriptCode } from "../utils";
import type { Message } from "@Packages/message/types";
import { encodeRValue } from "@App/pkg/utils/message_value";
import { uuidv4 } from "@App/pkg/utils/uuid";
import { getStorageName } from "@App/pkg/utils/utils";
const nilFn: ScriptFunc = () => {};

const scriptRes = {
Expand Down Expand Up @@ -1115,14 +1116,15 @@ return { value1, value2, value3, values1,values2, allValues1, allValues2, value4
const retPromise = exec.exec();
expect(mockSendMessage).toHaveBeenCalledTimes(1);
// 模拟值变化
exec.valueUpdate({
id: "id-1",
entries: [["param1", encodeRValue(123), encodeRValue(undefined)]],
uuid: script.uuid,
storageName: script.uuid,
sender: { runFlag: exec.sandboxContext!.runFlag, tabId: -2 },
valueUpdated: true,
});
exec.valueUpdate(getStorageName(script), script.uuid, [
{
id: "id-1",
valueChanges: [["param1", encodeRValue(123), encodeRValue(undefined)]],
uuid: script.uuid,
storageName: script.uuid,
sender: { runFlag: exec.sandboxContext!.runFlag, tabId: -2 },
},
]);
const ret = await retPromise;
expect(ret).toEqual({ name: "param1", oldValue: undefined, newValue: 123, remote: false });
});
Expand Down Expand Up @@ -1155,14 +1157,15 @@ return { value1, value2, value3, values1,values2, allValues1, allValues2, value4
const retPromise = exec.exec();
expect(mockSendMessage).toHaveBeenCalledTimes(1);
// 模拟值变化
exec.valueUpdate({
id: "id-2",
entries: [["param2", encodeRValue(456), encodeRValue(undefined)]],
uuid: script.uuid,
storageName: "testStorage",
sender: { runFlag: "user", tabId: -2 },
valueUpdated: true,
});
exec.valueUpdate(getStorageName(script), script.uuid, [
{
id: "id-2",
valueChanges: [["param2", encodeRValue(456), encodeRValue(undefined)]],
uuid: script.uuid,
storageName: "testStorage",
sender: { runFlag: "user", tabId: -2 },
},
]);
const ret2 = await retPromise;
expect(ret2).toEqual({ name: "param2", oldValue: undefined, newValue: 456, remote: true });
});
Expand Down Expand Up @@ -1195,14 +1198,15 @@ return { value1, value2, value3, values1,values2, allValues1, allValues2, value4
expect(id).toBeTypeOf("string");
expect(id.length).greaterThan(0);
// 触发valueUpdate
exec.valueUpdate({
id: id,
entries: [["a", encodeRValue(123), encodeRValue(undefined)]],
uuid: script.uuid,
storageName: script.uuid,
sender: { runFlag: exec.sandboxContext!.runFlag, tabId: -2 },
valueUpdated: true,
});
exec.valueUpdate(getStorageName(script), script.uuid, [
{
id: id,
valueChanges: [["a", encodeRValue(123), encodeRValue(undefined)]],
uuid: script.uuid,
storageName: script.uuid,
sender: { runFlag: exec.sandboxContext!.runFlag, tabId: -2 },
},
]);

const ret = await retPromise;
expect(ret).toEqual(123);
Expand Down
79 changes: 42 additions & 37 deletions src/app/service/content/gm_api/gm_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@ import { base64ToBlob, randNum, randomMessageFlag, strToBase64 } from "@App/pkg/
import LoggerCore from "@App/app/logger/core";
import EventEmitter from "eventemitter3";
import GMContext from "./gm_context";
import { type ScriptRunResource } from "@App/app/repo/scripts";
import type { ScriptRunResource, ValueStore } from "@App/app/repo/scripts";
import type { ValueUpdateDataEncoded } from "../types";
import { connect, sendMessage } from "@Packages/message/client";
import { ScriptEnvTag } from "@Packages/message/consts";
import { getStorageName } from "@App/pkg/utils/utils";
import { ListenerManager } from "../listener_manager";
import { decodeRValue, encodeRValue, type REncoded } from "@App/pkg/utils/message_value";
import { type TGMKeyValue } from "@App/app/repo/value";
Expand All @@ -44,7 +43,7 @@ void CATAgentOPFSApi;
export interface IGM_Base {
sendMessage(api: string, params: any[]): Promise<any>;
connect(api: string, params: any[]): Promise<any>;
valueUpdate(data: ValueUpdateDataEncoded): void;
valueStoreUpdate(valueStore: ValueStore, responses: ValueUpdateDataEncoded[]): void;
emitEvent(event: string, eventId: string, data: any): void;
}

Expand All @@ -61,6 +60,15 @@ let valChangeRandomId = `${randNum(8e11, 2e12).toString(36)}`;

const valueChangePromiseMap = new Map<string, any>();

const generateValChangeId = () => {
if (valChangeCounterId > 1e8) {
// 防止 valChangeCounterId 过大导致无法正常工作
valChangeCounterId = 0;
valChangeRandomId = `${randNum(8e11, 2e12).toString(36)}`;
}
return `${valChangeRandomId}::${++valChangeCounterId}`;
};

const execEnvInit = (execEnv: GMApi) => {
if (!execEnv.contentEnvKey) {
execEnv.contentEnvKey = randomMessageFlag(); // 不重复识别字串。用于区分 mainframe subframe 等执行环境
Expand Down Expand Up @@ -168,12 +176,13 @@ class GM_Base implements IGM_Base {
}

@GMContext.protected()
public valueUpdate(data: ValueUpdateDataEncoded) {
if (!this.scriptRes || !this.valueChangeListener) return;
const scriptRes = this.scriptRes;
const { id, uuid, entries, storageName, sender, valueUpdated } = data;
if (uuid === scriptRes.uuid || storageName === getStorageName(scriptRes)) {
const valueStore = scriptRes.value;
public extValueStoreCopy?: ValueStore; // 以 SW 确认顺序为准的 store 快照,使各页面 valueStore 的 key 顺序一致

@GMContext.protected()
public valueStoreUpdate(valueStore: ValueStore, responses: ValueUpdateDataEncoded[]) {
// 同步更新 valueStore;监听器回调延后到下一个 microTask,避免阻塞更新流程
for (const response of responses) {
const { id, valueChanges, sender } = response;
const remote = sender.runFlag !== this.runFlag;
if (!remote && id) {
const fn = valueChangePromiseMap.get(id);
Expand All @@ -182,21 +191,18 @@ class GM_Base implements IGM_Base {
fn();
}
}
if (valueUpdated) {
const valueChanges = entries;
for (const [key, rTyped1, rTyped2] of valueChanges) {
const value = decodeRValue(rTyped1);
const oldValue = decodeRValue(rTyped2);
// 触发,并更新值
if (value === undefined) {
if (valueStore[key] !== undefined) {
delete valueStore[key];
}
} else {
valueStore[key] = value;
}
this.valueChangeListener.execute(key, oldValue, value, remote, sender.tabId);
for (const [key, rTyped1, rTyped2] of valueChanges) {
const value = decodeRValue(rTyped1);
const oldValue = decodeRValue(rTyped2);
// 触发,并更新值
if (value !== undefined) {
valueStore[key] = value;
} else if (valueStore[key] !== undefined) {
delete valueStore[key];
}
Promise.resolve().then(() => {
this.valueChangeListener?.execute(key, oldValue, value, remote, sender.tabId);
});
}
}
}
Expand Down Expand Up @@ -283,25 +289,25 @@ export default class GMApi extends GM_Base {
static _GM_setValue(a: GMApi, promise: any, key: string, value: any) {
key = `${key}`;
if (!a.scriptRes) return;
if (valChangeCounterId > 1e8) {
// 防止 valChangeCounterId 过大导致无法正常工作
valChangeCounterId = 0;
valChangeRandomId = `${randNum(8e11, 2e12).toString(36)}`;
}
const id = `${valChangeRandomId}::${++valChangeCounterId}`;
const id = generateValChangeId();
if (promise) {
valueChangePromiseMap.set(id, promise);
}
const valueStore = a.scriptRes.value;
if (!a.extValueStoreCopy) {
// 本地乐观写入前记下快照;快照只随 SW 确认的更新演进,key 顺序以 SW 为准
a.extValueStoreCopy = { ...valueStore };
}
if (value === undefined) {
delete a.scriptRes.value[key];
delete valueStore[key];
a.sendMessage("GM_setValue", [id, key]);
} else {
// 对object的value进行一次转化
if (value && typeof value === "object") {
value = customClone(value);
}
// customClone 可能返回 undefined
a.scriptRes.value[key] = value;
valueStore[key] = value;
if (value === undefined) {
a.sendMessage("GM_setValue", [id, key]);
} else {
Expand All @@ -313,16 +319,15 @@ export default class GMApi extends GM_Base {

static _GM_setValues(a: GMApi, promise: any, values: TGMKeyValue) {
if (!a.scriptRes) return;
if (valChangeCounterId > 1e8) {
// 防止 valChangeCounterId 过大导致无法正常工作
valChangeCounterId = 0;
valChangeRandomId = `${randNum(8e11, 2e12).toString(36)}`;
}
const id = `${valChangeRandomId}::${++valChangeCounterId}`;
const id = generateValChangeId();
if (promise) {
valueChangePromiseMap.set(id, promise);
}
const valueStore = a.scriptRes.value;
if (!a.extValueStoreCopy) {
// 本地乐观写入前记下快照;快照只随 SW 确认的更新演进,key 顺序以 SW 为准
a.extValueStoreCopy = { ...valueStore };
}
const keyValuePairs = [] as [string, REncoded<unknown>][];
for (const [key, value] of Object.entries(values)) {
let value_ = value;
Expand Down
13 changes: 6 additions & 7 deletions src/app/service/content/script_executor.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import type { Message } from "@Packages/message/types";
import { getStorageName } from "@App/pkg/utils/utils";
import type { EmitEventRequest } from "../service_worker/types";
import ExecScript from "./exec_script";
import type { GMInfoEnv, ScriptFunc, ValueUpdateDataEncoded } from "./types";
import type { GMInfoEnv, ScriptFunc, ValueUpdateSendData } from "./types";
import { addStyleSheet, definePropertyListener, waitBody } from "./utils";
import type { ScriptLoadInfo, TScriptInfo } from "@App/app/repo/scripts";
import { DefinedFlags } from "../service_worker/runtime.consts";
Expand Down Expand Up @@ -46,12 +45,12 @@ export class ScriptExecutor {
}
}

valueUpdate(data: ValueUpdateDataEncoded) {
valueUpdate(sendData: ValueUpdateSendData) {
// runtime/valueUpdate
const { uuid, storageName } = data;
for (const val of this.execScriptMap.values()) {
if (val.scriptRes.uuid === uuid || getStorageName(val.scriptRes) === storageName) {
val.valueUpdate(data);
const { storageName, storageChanges } = sendData;
for (const [uuid, responses] of Object.entries(storageChanges)) {
for (const execScript of this.execScriptMap.values()) {
execScript.valueUpdate(storageName, uuid, responses);
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/app/service/content/script_runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Message } from "@Packages/message/types";
import { initEnvInfo, type ScriptExecutor } from "./script_executor";
import type { TScriptInfo } from "@App/app/repo/scripts";
import type { EmitEventRequest } from "../service_worker/types";
import type { GMInfoEnv, ValueUpdateDataEncoded } from "./types";
import type { GMInfoEnv, ValueUpdateSendData } from "./types";
import type { ScriptEnvTag } from "@Packages/message/consts";
import { onInjectPageLoaded } from "./external";
import type { CustomEventMessage } from "@Packages/message/custom_event_message";
Expand Down Expand Up @@ -59,7 +59,7 @@ export class ScriptRuntime {
// 转发给脚本
this.scriptExecutor.emitEvent(data);
});
this.server.on("runtime/valueUpdate", (data: ValueUpdateDataEncoded) => {
this.server.on("runtime/valueUpdate", (data: ValueUpdateSendData) => {
this.scriptExecutor.valueUpdate(data);
});

Expand Down
4 changes: 2 additions & 2 deletions src/app/service/content/scripting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { RuntimeClient } from "../service_worker/client";
import { getStorageName, makeBlobURL } from "@App/pkg/utils/utils";
import type { Logger } from "@App/app/repo/logger";
import LoggerCore from "@App/app/logger/core";
import type { ValueUpdateDataEncoded } from "./types";
import type { ValueUpdateSendData } from "./types";

const PageOrContent = {
PAGE: 1,
Expand Down Expand Up @@ -73,7 +73,7 @@ export default class ScriptingRuntime {
deliveryStorage.onChanged.addListener((changes) => {
const record = changes["valueUpdateDelivery"];
if (record?.newValue) {
const sendData = (record.newValue as { sendData: ValueUpdateDataEncoded }).sendData;
const sendData = (record.newValue as { sendData: ValueUpdateSendData }).sendData;
const activeOn =
this.activeStorageNames === null
? PageOrContent.PAGE_AND_CONTENT
Expand Down
Loading
Loading