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
2 changes: 2 additions & 0 deletions src/app/service/content/create_context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ describe.concurrent("createContext", () => {
sender: { runFlag: "other-run-flag", tabId: 7 },
entries: [["foo", encodeRValue("next"), encodeRValue("bar")]],
valueUpdated: true,
updatetime: Date.now(),
});
expect(listener).toHaveBeenCalledWith("foo", "bar", "next", true, 7);

Expand All @@ -175,6 +176,7 @@ describe.concurrent("createContext", () => {
sender: { runFlag: "other-run-flag", tabId: 8 },
entries: [["foo", encodeRValue("again"), encodeRValue("next")]],
valueUpdated: true,
updatetime: Date.now(),
});
expect(listener).toHaveBeenCalledTimes(1);
});
Expand Down
7 changes: 7 additions & 0 deletions src/app/service/content/create_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ export const createContext = (
this.scriptRes = null;
this.valueChangeListener = null;
this.EE = null;
const readFreshes = this.readFreshes;
if (readFreshes) {
for (const [updatetime, d] of readFreshes) {
readFreshes.delete(updatetime);
d.resolve(this.valueDaoUpdatetime ?? 0);
}
}
},
isInvalidContext() {
return invalid;
Expand Down
239 changes: 229 additions & 10 deletions src/app/service/content/gm_api/gm_api.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { describe, expect, it, vi } from "vitest";
import { describe, expect, it, type Mock, vi } from "vitest";
import ExecScript from "../exec_script";
import type { ScriptLoadInfo } from "@App/app/service/service_worker/types";
import type { GMInfoEnv, ScriptFunc } from "../types";
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 @@ -176,6 +177,31 @@ describe.concurrent("window.*", () => {
});

describe.concurrent("GM Api", () => {
// 异步 GM.* 取值会先发出 internalApiWaitForFreshValueState,等 valueUpdate 回推最新
// updatetime 后才读取本地缓存;这里模拟 service_worker 的该次回推
const respondFreshValueState = (
mockSendMessage: Mock<(...args: any[]) => any>,
exec: ExecScript,
script: ScriptLoadInfo
) => {
const idx = mockSendMessage.mock.calls.findIndex((entry) => {
return entry?.[0]?.data?.api === "internalApiWaitForFreshValueState";
});
expect(idx).toBeGreaterThanOrEqual(0);
const actualCall = mockSendMessage.mock.calls[idx][0];
const [id] = actualCall.data.params;
expect(id).toBeTypeOf("string");
expect(id.length).greaterThan(0);
exec.valueUpdate({
id,
entries: [],
uuid: script.uuid,
storageName: getStorageName(script),
sender: { runFlag: exec.sandboxContext!.runFlag, tabId: -2 },
valueUpdated: false,
updatetime: Date.now(),
});
};
it.concurrent("GM_getValue", async () => {
const script = Object.assign({}, scriptRes) as ScriptLoadInfo;
script.value = { test: "ok" };
Expand All @@ -192,7 +218,91 @@ describe.concurrent("GM Api", () => {
const ret = await exec.exec();
expect(ret).toEqual("ok");
});
it.concurrent("GM.getValue", async () => {
it.concurrent("GM.getValue 应等待最新值同步后返回", async () => {
const script = Object.assign({}, scriptRes) as ScriptLoadInfo;
script.value = { test: "ok" };
script.metadata.grant = ["GM.getValue"];
script.code = `return GM.getValue("test").then(v=>v+"!");`;
const mockSendMessage = vi.fn().mockResolvedValue({ code: 0 });
const mockMessage = {
sendMessage: mockSendMessage,
} as unknown as Message;
const exec = new ExecScript(script, {
envPrefix: "scripting",
message: mockMessage,
contentMsg: undefined as any,
code: nilFn,
envInfo,
});
exec.scriptFunc = compileScript(compileScriptCode(script));
const retPromise = exec.exec();
respondFreshValueState(mockSendMessage, exec, script);
const ret = await retPromise;
expect(ret).toEqual("ok!");
});

it.concurrent("GM.getValue 遇到更新的 updatetime 时应等待对应 valueUpdate 才读取", async () => {
const script = Object.assign({ uuid: uuidv4() }, scriptRes) as ScriptLoadInfo;
script.value = { k: "old" };
script.metadata.grant = ["GM.getValue"];
script.code = `return GM.getValue("k").then(v1=>GM.getValue("k").then(v2=>[v1, v2]));`;
const t0 = Date.now();
const t1 = t0 + 1000;
const internalCalls: any[] = [];
const mockSendMessage = vi.fn().mockImplementation((msg: any) => {
if (msg?.data?.api === "internalApiWaitForFreshValueState") {
internalCalls.push(msg);
// 第一次返回当前 updatetime t0;第二次返回更新的 t1,模拟其他环境已写入新值
return Promise.resolve({ code: 0, data: internalCalls.length === 1 ? t0 : t1 });
}
return Promise.resolve({ code: 0 });
});
const mockMessage = {
sendMessage: mockSendMessage,
} as unknown as Message;
const exec = new ExecScript(script, {
envPrefix: "scripting",
message: mockMessage,
contentMsg: undefined as any,
code: nilFn,
envInfo,
});
exec.scriptFunc = compileScript(compileScriptCode(script));
const retPromise = exec.exec();
// 第一次读取:以 id 对应的 valueUpdate 回推 t0
expect(internalCalls.length).toBe(1);
exec.valueUpdate({
id: internalCalls[0].data.params[0],
entries: [],
uuid: script.uuid,
storageName: getStorageName(script),
sender: { runFlag: exec.sandboxContext!.runFlag, tabId: -2 },
valueUpdated: false,
updatetime: t0,
});
await vi.waitFor(() => expect(internalCalls.length).toBe(2));
// 第二次读取:service_worker 返回 t1 > t0,在 valueUpdate 到达前不得完成
let settled = false;
retPromise.then(() => {
settled = true;
});
await new Promise((r) => setTimeout(r, 10));
expect(settled).toBe(false);
// t1 的 valueUpdate 到达后放行,且读取到同步后的新值
exec.valueUpdate({
id: "",
entries: [["k", encodeRValue("new"), encodeRValue("old")]],
uuid: script.uuid,
storageName: getStorageName(script),
sender: { runFlag: "user", tabId: -2 },
valueUpdated: true,
updatetime: t1,
});
const ret = await retPromise;
expect(ret).toEqual(["old", "new"]);
});

it.concurrent("GM.getValue 在 message 无效时直接返回本地缓存", async () => {
const script = Object.assign({}, scriptRes) as ScriptLoadInfo;
script.value = { test: "ok" };
script.metadata.grant = ["GM.getValue"];
Expand All @@ -209,6 +319,94 @@ describe.concurrent("GM Api", () => {
expect(ret).toEqual("ok!");
});

it("收到较旧的 valueUpdate 时不应回滚本地缓存", () => {
const script = Object.assign({}, scriptRes) as ScriptLoadInfo;
script.value = { key: "initial" };
script.metadata.grant = ["GM.getValue"];
const exec = new ExecScript(script, {
envPrefix: "scripting",
message: undefined as any,
contentMsg: undefined as any,
code: nilFn,
envInfo,
});

exec.valueUpdate({
id: "",
entries: [["key", encodeRValue("new"), encodeRValue("initial")]],
uuid: script.uuid,
storageName: getStorageName(script),
sender: { runFlag: "other-run-flag", tabId: 7 },
valueUpdated: true,
updatetime: 200,
});
exec.valueUpdate({
id: "",
entries: [["key", encodeRValue("old"), encodeRValue("new")]],
uuid: script.uuid,
storageName: getStorageName(script),
sender: { runFlag: "other-run-flag", tabId: 7 },
valueUpdated: true,
updatetime: 100,
});

expect(script.value.key).toBe("new");
});

it("较新的 valueUpdate 即使缺少初始 id 也应放行读取", async () => {
const script = Object.assign({}, scriptRes) as ScriptLoadInfo;
script.value = { key: "old" };
script.metadata.grant = ["GM.getValue"];
script.code = `return GM.getValue("key");`;
const mockSendMessage = vi.fn().mockResolvedValue({ code: 0, data: 200 });
const exec = new ExecScript(script, {
envPrefix: "scripting",
message: { sendMessage: mockSendMessage } as unknown as Message,
contentMsg: undefined as any,
code: nilFn,
envInfo,
});
exec.scriptFunc = compileScript(compileScriptCode(script));
const retPromise = exec.exec();
await vi.waitFor(() => expect(mockSendMessage).toHaveBeenCalled());

exec.valueUpdate({
id: "",
entries: [["key", encodeRValue("new"), encodeRValue("old")]],
uuid: script.uuid,
storageName: getStorageName(script),
sender: { runFlag: "other-run-flag", tabId: 7 },
valueUpdated: true,
updatetime: 200,
});

const ret = await Promise.race([retPromise, new Promise((resolve) => setTimeout(() => resolve("timeout"), 50))]);
expect(ret).toBe("new");
});

it("context invalidated 时不应让异步读取永久等待", async () => {
const script = Object.assign({}, scriptRes) as ScriptLoadInfo;
script.value = { key: "old" };
script.metadata.grant = ["GM.getValue"];
script.code = `return GM.getValue("key");`;
const mockSendMessage = vi.fn().mockResolvedValue({ code: 0, data: 200 });
const exec = new ExecScript(script, {
envPrefix: "scripting",
message: { sendMessage: mockSendMessage } as unknown as Message,
contentMsg: undefined as any,
code: nilFn,
envInfo,
});
exec.scriptFunc = compileScript(compileScriptCode(script));
const retPromise = exec.exec().then(() => "settled");
await vi.waitFor(() => expect(mockSendMessage).toHaveBeenCalled());
await new Promise((resolve) => setTimeout(resolve, 0));
exec.sandboxContext!.setInvalidContext();

const ret = await Promise.race([retPromise, new Promise((resolve) => setTimeout(() => resolve("timeout"), 50))]);
expect(ret).toBe("settled");
});

it.concurrent("GM_listValues", async () => {
const script = Object.assign({}, scriptRes) as ScriptLoadInfo;
script.value = { test1: "23", test2: "45", test3: "67" };
Expand Down Expand Up @@ -247,20 +445,26 @@ describe.concurrent("GM Api", () => {
expect(ret).toEqual("test5-test2-test3-test1"); // TM也没有sort
});

it.concurrent("GM.listValues", async () => {
it.concurrent("GM.listValues 应等待最新值同步后返回", async () => {
const script = Object.assign({}, scriptRes) as ScriptLoadInfo;
script.value = { test1: "23", test2: "45", test3: "67" };
script.metadata.grant = ["GM.listValues"];
script.code = `return GM.listValues().then(v=>v.join("-"));`;
const mockSendMessage = vi.fn().mockResolvedValue({ code: 0 });
const mockMessage = {
sendMessage: mockSendMessage,
} as unknown as Message;
const exec = new ExecScript(script, {
envPrefix: "scripting",
message: undefined as any,
message: mockMessage,
contentMsg: undefined as any,
code: nilFn,
envInfo,
});
exec.scriptFunc = compileScript(compileScriptCode(script));
const ret = await exec.exec();
const retPromise = exec.exec();
respondFreshValueState(mockSendMessage, exec, script);
const ret = await retPromise;
expect(ret).toEqual("test1-test2-test3");
});

Expand All @@ -273,15 +477,21 @@ describe.concurrent("GM Api", () => {
script.value.test1 = "40";
script.metadata.grant = ["GM.listValues"];
script.code = `return GM.listValues().then(v=>v.join("-"));`;
const mockSendMessage = vi.fn().mockResolvedValue({ code: 0 });
const mockMessage = {
sendMessage: mockSendMessage,
} as unknown as Message;
const exec = new ExecScript(script, {
envPrefix: "scripting",
message: undefined as any,
message: mockMessage,
contentMsg: undefined as any,
code: nilFn,
envInfo,
});
exec.scriptFunc = compileScript(compileScriptCode(script));
const ret = await exec.exec();
const retPromise = exec.exec();
respondFreshValueState(mockSendMessage, exec, script);
const ret = await retPromise;
expect(ret).toEqual("test5-test2-test3-test1"); // TM也没有sort
});

Expand Down Expand Up @@ -311,20 +521,26 @@ describe.concurrent("GM Api", () => {
expect(ret2.test4).toEqual("default");
});

it.concurrent("GM.getValues", async () => {
it.concurrent("GM.getValues 应等待最新值同步后返回", async () => {
const script = Object.assign({}, scriptRes) as ScriptLoadInfo;
script.value = { test1: "23", test2: 45, test3: "67" };
script.metadata.grant = ["GM.getValues"];
script.code = `return GM.getValues(["test2", "test3", "test1"]).then(v=>v);`;
const mockSendMessage = vi.fn().mockResolvedValue({ code: 0 });
const mockMessage = {
sendMessage: mockSendMessage,
} as unknown as Message;
const exec = new ExecScript(script, {
envPrefix: "scripting",
message: undefined as any,
message: mockMessage,
contentMsg: undefined as any,
code: nilFn,
envInfo,
});
exec.scriptFunc = compileScript(compileScriptCode(script));
const ret = await exec.exec();
const retPromise = exec.exec();
respondFreshValueState(mockSendMessage, exec, script);
const ret = await retPromise;
expect(ret.test1).toEqual("23");
expect(ret.test2).toEqual(45);
expect(ret.test3).toEqual("67");
Expand Down Expand Up @@ -1122,6 +1338,7 @@ return { value1, value2, value3, values1,values2, allValues1, allValues2, value4
storageName: script.uuid,
sender: { runFlag: exec.sandboxContext!.runFlag, tabId: -2 },
valueUpdated: true,
updatetime: Date.now(),
});
const ret = await retPromise;
expect(ret).toEqual({ name: "param1", oldValue: undefined, newValue: 123, remote: false });
Expand Down Expand Up @@ -1162,6 +1379,7 @@ return { value1, value2, value3, values1,values2, allValues1, allValues2, value4
storageName: "testStorage",
sender: { runFlag: "user", tabId: -2 },
valueUpdated: true,
updatetime: Date.now(),
});
const ret2 = await retPromise;
expect(ret2).toEqual({ name: "param2", oldValue: undefined, newValue: 456, remote: true });
Expand Down Expand Up @@ -1202,6 +1420,7 @@ return { value1, value2, value3, values1,values2, allValues1, allValues2, value4
storageName: script.uuid,
sender: { runFlag: exec.sandboxContext!.runFlag, tabId: -2 },
valueUpdated: true,
updatetime: Date.now(),
});

const ret = await retPromise;
Expand Down
Loading
Loading