Skip to content

Commit 6832ca8

Browse files
committed
Merge branch 'release/v1.4' into pr/1167
2 parents 1aa4f66 + f42baa4 commit 6832ca8

69 files changed

Lines changed: 3102 additions & 1288 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ yarn.lock
3636
.claude
3737

3838
CLAUDE.md
39+
.omc
3940

4041
test-results
4142
playwright-report

example/run-in/run-in_bg.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// ==UserScript==
2+
// @name Test @run-in background
3+
// @namespace https://bbs.tampermonkey.net.cn/
4+
// @version 0.1.0
5+
// @description 后台脚本支持 @run-in 区分正常窗口与隐身窗口;同时可透过 GM_info.isIncognito 与 GM_info.userAgentData 取得运行环境
6+
// @author You
7+
// @background
8+
// @run-in incognito-tabs
9+
// @grant GM_log
10+
// ==/UserScript==
11+
12+
return new Promise((resolve) => {
13+
// 后台脚本指定 @run-in incognito-tabs 后,仅在隐身窗口对应的扩展环境中执行
14+
// 若改为 normal-tabs 则仅在正常窗口环境执行;不写或写 @run-in normal-tabs 与 @run-in incognito-tabs 时两者皆执行
15+
GM_log(`run-in: ${GM_info.script["run-in"]}`);
16+
GM_log(`isIncognito: ${GM_info.isIncognito}`);
17+
GM_log(`userAgentData: ${JSON.stringify(GM_info.userAgentData)}`);
18+
resolve();
19+
});

example/tests/gm_menu_test.js

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
// ==UserScript==
2+
// @name GM_registerMenuCommand Example
3+
// @namespace http://tampermonkey.net/
4+
// @version 1.0
5+
// @description Simple demo for GM_registerMenuCommand
6+
// @match *://*/*
7+
// @grant GM_registerMenuCommand
8+
// @grant GM_unregisterMenuCommand
9+
// ==/UserScript==
10+
11+
(async function () {
12+
'use strict';
13+
14+
const checkSubFrameIdSequence = false;
15+
16+
const intervalChanging = false;
17+
18+
const skipClickCheck = false;
19+
20+
let myResolve = () => { };
21+
const waitNext = async () => {
22+
await new Promise((resolve) => {
23+
myResolve = () => { setTimeout(resolve, 50) };
24+
});
25+
};
26+
27+
const waitActions = async (...messages) => {
28+
if (skipClickCheck) return;
29+
messages = messages.flat();
30+
for (const message of messages) {
31+
console.log(message);
32+
await waitNext();
33+
}
34+
};
35+
36+
const isInSubFrame = () => {
37+
38+
try {
39+
return window.top !== window;
40+
} catch {
41+
return true;
42+
}
43+
}
44+
45+
if (intervalChanging) {
46+
// TM: 在打开菜单时,显示会不断改变
47+
let i = 1000;
48+
let p = 0;
49+
setInterval(() => {
50+
if (p) GM_unregisterMenuCommand(p);
51+
i++;
52+
GM_registerMenuCommand(`interval-m-${i}`, () => { console.log(`${i}`); }, {id: "m"});
53+
p = GM_registerMenuCommand(`interval-n-${i}`, () => { console.log(`${i}`); });
54+
}, 1000);
55+
// return;
56+
}
57+
if (checkSubFrameIdSequence) {
58+
const key = Math.floor(Math.random() * 99999 + 99999).toString();
59+
60+
let arr = [];
61+
arr.push(
62+
GM_registerMenuCommand("test", () => { console.log(`${key}-1`); }),
63+
GM_registerMenuCommand("test", () => { console.log(`${key}-2`); }),
64+
GM_registerMenuCommand("test", () => { console.log(`${key}-3`); })
65+
);
66+
if (isInSubFrame()) {
67+
arr.push(GM_registerMenuCommand("test-sub", () => { console.log(`${key}-sub`); }));
68+
} else {
69+
arr.push(GM_registerMenuCommand("test-main", () => { console.log(`${key}-main`); }));
70+
}
71+
arr.push(GM_registerMenuCommand(`test-${location.origin}`, () => { console.log(`${key}-origin`); }));
72+
console.log(`checkSubFrameIdSequence (key=${key}, frame=${isInSubFrame()})`, arr.join("..."));
73+
// return;
74+
}
75+
76+
let obj1 = { id: "abc" };
77+
78+
const r01 = GM_registerMenuCommand("MenuReg abc-1", () => {
79+
80+
console.log("abc-1");
81+
myResolve();
82+
}, obj1);
83+
84+
const r02 = GM_registerMenuCommand("MenuReg abc-2", () => {
85+
86+
console.log("abc-2");
87+
myResolve();
88+
}, obj1);
89+
90+
console.log("abc-1 id === abc", r01 === "abc");
91+
console.log("abc-2 id === abc", r02 === "abc");
92+
93+
// there shall be only "MenuReg abc-2" in the menu.
94+
await waitActions("There shall be only 'MenuReg abc-2'. Click it to continue.");
95+
96+
GM_registerMenuCommand("MenuReg abc-1", () => {
97+
98+
console.log("abc-1.abd");
99+
myResolve();
100+
}, { id: "abd" });
101+
102+
GM_registerMenuCommand("MenuReg abc-2", () => {
103+
104+
console.log("abc-2.abe");
105+
myResolve();
106+
}, { id: "abe" });
107+
108+
109+
// there shall be only "MenuReg abc-1" and "MenuReg abc-2" in the menu.
110+
await waitActions("There shall be 'MenuReg abc-2' and 'MenuReg abc-1'. Click either them to continue.");
111+
112+
113+
GM_registerMenuCommand("MenuReg abc-2", () => {
114+
115+
console.log("abc-2.abf");
116+
myResolve();
117+
}, { id: "abf", accessKey: "h" });
118+
119+
// there shall be only "MenuReg abc-1" and "MenuReg abc-2" in the menu.
120+
await waitActions("There shall be 'MenuReg abc-2', 'MenuReg abc-1' and 'MenuReg abc-2 (H)'. Click either them to continue.");
121+
122+
GM_unregisterMenuCommand("abc");
123+
GM_unregisterMenuCommand("abd");
124+
GM_unregisterMenuCommand("abe");
125+
GM_unregisterMenuCommand("abf");
126+
127+
128+
129+
const p10 = GM_registerMenuCommand("MenuReg D-23", () => {
130+
131+
console.log(110);
132+
myResolve();
133+
}, "b");
134+
135+
136+
const p20 = GM_registerMenuCommand("MenuReg D-23", () => {
137+
138+
console.log(120);
139+
myResolve();
140+
}, "b");
141+
142+
console.log("p10 === 1", p10 === 1);
143+
console.log("p20 === 2", p20 === 2);
144+
145+
// MenuReg D-23 clicking shall give both 110 and 120
146+
await waitActions("Click [MenuReg D-23] -> 110, 120");
147+
148+
149+
const p30 = GM_registerMenuCommand("MenuReg D-26", () => {
150+
151+
console.log(130);
152+
myResolve();
153+
}, { id: "2" });
154+
console.log("p30 === '2'", p30 === "2");
155+
156+
// MenuReg D-23 clicking shall give 110
157+
// MenuReg D-26 clicking shall give 130
158+
159+
await waitActions("Click [MenuReg D-23] -> 110", "Click [MenuReg D-26] -> 130");
160+
161+
162+
const p32 = GM_registerMenuCommand("MenuReg D-26", () => {
163+
164+
console.log(210);
165+
myResolve();
166+
}, { id: 2 });
167+
console.log("p32 === 2", p32 === 2);
168+
169+
// MenuReg D-23 clicking shall give 110
170+
// MenuReg D-26 clicking shall give 210
171+
172+
await waitActions("Click [MenuReg D-23] -> 110", "Click [MenuReg D-26] -> 210");
173+
174+
175+
const p33 = GM_registerMenuCommand("MenuReg D-26", () => {
176+
177+
console.log(220);
178+
myResolve();
179+
}, { id: 3 });
180+
console.log("p33 === 3", p33 === 3);
181+
182+
// MenuReg D-23 clicking shall give 110
183+
// MenuReg D-26 clicking shall give 210 220
184+
185+
await waitActions("Click [MenuReg D-23] -> 110", "Click [MenuReg D-26] -> 210, 220");
186+
187+
188+
189+
const p34 = GM_registerMenuCommand("MenuReg D-26", () => {
190+
191+
console.log(230);
192+
myResolve();
193+
}, { id: "4" });
194+
console.log("p34 === '4'", p34 === "4");
195+
196+
// MenuReg D-23 clicking shall give 110
197+
// MenuReg D-26 clicking shall give 210 220 230
198+
await waitActions("Click [MenuReg D-23] -> 110", "Click [MenuReg D-26] -> 210, 220, 230");
199+
200+
GM_unregisterMenuCommand("4");
201+
202+
203+
// MenuReg D-23 clicking shall give 110
204+
// MenuReg D-26 clicking shall give 210 220
205+
await waitActions("Click [MenuReg D-23] -> 110", "Click [MenuReg D-26] -> 210, 220");
206+
207+
208+
209+
const p40 = GM_registerMenuCommand("MenuReg D-40", () => {
210+
211+
console.log(601);
212+
});
213+
214+
const p50 = GM_registerMenuCommand("MenuReg D-50", () => {
215+
216+
console.log(602);
217+
});
218+
console.log("p40, p50", [p40, p50]); // TM gives 3&4
219+
220+
221+
222+
})().finally(() => {
223+
console.log("finish");
224+
});
225+

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@
6161
"@eslint/compat": "^1.4.1",
6262
"@eslint/js": "9.39.2",
6363
"@playwright/test": "^1.58.2",
64-
"@rspack/cli": "^1.7.6",
65-
"@rspack/core": "^1.6.8",
64+
"@rspack/cli": "^1.7.11",
65+
"@rspack/core": "^1.7.11",
6666
"@swc/helpers": "^0.5.17",
6767
"@testing-library/jest-dom": "^6.9.1",
6868
"@testing-library/react": "^16.3.0",
@@ -103,7 +103,7 @@
103103
"unocss": "66.5.4",
104104
"vitest": "^4.0.18"
105105
},
106-
"packageManager": "pnpm@10.12.4",
106+
"packageManager": "pnpm@10.33.0",
107107
"sideEffects": [
108108
"**/*.css",
109109
"**/*.scss",

packages/chrome-extension-mock/declarativ_net_request.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
export default class DeclarativeNetRequest {
2+
MAX_NUMBER_OF_SESSION_RULES = 5000;
3+
4+
private _sessionRules: chrome.declarativeNetRequest.Rule[] = [];
5+
26
HeaderOperation = {
37
APPEND: "append",
48
SET: "set",
@@ -30,9 +34,44 @@ export default class DeclarativeNetRequest {
3034
OTHER: "other",
3135
};
3236

33-
updateSessionRules() {
37+
updateSessionRules(arg1: any, arg2: any): Promise<void> {
38+
let options: {
39+
addRules?: chrome.declarativeNetRequest.Rule[];
40+
removeRuleIds?: number[];
41+
} = {};
42+
let callback: undefined | ((...args: any) => any) = undefined;
43+
44+
if (typeof arg1 === "function") {
45+
callback = arg1;
46+
} else if (typeof arg2 === "function") {
47+
callback = arg2;
48+
}
49+
if (typeof arg1 === "object" && arg1) options = arg1;
50+
3451
return new Promise<void>((resolve) => {
52+
const { addRules = [], removeRuleIds = [] } = options;
53+
54+
// Remove rules by ID
55+
if (removeRuleIds.length > 0) {
56+
this._sessionRules = this._sessionRules.filter((rule) => !removeRuleIds.includes(rule.id));
57+
}
58+
59+
// Add or update rules (upsert by ID)
60+
for (const newRule of addRules) {
61+
const existingIndex = this._sessionRules.findIndex((rule) => rule.id === newRule.id);
62+
if (existingIndex !== -1) {
63+
this._sessionRules[existingIndex] = newRule; // update
64+
} else {
65+
this._sessionRules.push(newRule); // add
66+
}
67+
}
68+
3569
resolve();
70+
callback?.();
3671
});
3772
}
73+
74+
getSessionRules(): Promise<chrome.declarativeNetRequest.Rule[]> {
75+
return Promise.resolve([...this._sessionRules]);
76+
}
3877
}

packages/chrome-extension-mock/web_reqeuest.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import EventEmitter from "eventemitter3";
22

33
export default class WebRequest {
44
sendHeader?: (details: chrome.webRequest.OnSendHeadersDetails) => chrome.webRequest.BlockingResponse | void;
5+
responseStarted?: (details: chrome.webRequest.OnResponseStartedDetails) => void;
56

67
onBeforeSendHeaders = {
78
addListener: (callback: any) => {
@@ -15,6 +16,12 @@ export default class WebRequest {
1516
},
1617
};
1718

19+
onResponseStarted = {
20+
addListener: (callback: any) => {
21+
this.responseStarted = callback;
22+
},
23+
};
24+
1825
onCompleted = {
1926
addListener: () => {
2027
// TODO

packages/filesystem/auth.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2+
import { AuthVerify } from "./auth";
3+
import { LocalStorageDAO } from "@App/app/repo/localStorage";
4+
5+
describe("AuthVerify", () => {
6+
const localStorageDAO = new LocalStorageDAO();
7+
const key = "netdisk:token:onedrive";
8+
let originalFetch: typeof fetch;
9+
10+
beforeEach(async () => {
11+
vi.clearAllMocks();
12+
await chrome.storage.local.clear();
13+
originalFetch = globalThis.fetch;
14+
});
15+
16+
afterEach(() => {
17+
vi.stubGlobal("fetch", originalFetch);
18+
});
19+
20+
it("expired token refresh network failure should reject, not fallback old token", async () => {
21+
await localStorageDAO.saveValue(key, {
22+
accessToken: "old-access",
23+
refreshToken: "old-refresh",
24+
createtime: Date.now() - 3600000 - 1000,
25+
});
26+
27+
vi.stubGlobal("fetch", vi.fn().mockRejectedValueOnce(new Error("refresh network failed")));
28+
29+
await expect(AuthVerify("onedrive")).rejects.toThrow("refresh network failed");
30+
});
31+
32+
it("non-expired token should return cached token without refresh", async () => {
33+
await localStorageDAO.saveValue(key, {
34+
accessToken: "cached-access",
35+
refreshToken: "cached-refresh",
36+
createtime: Date.now(),
37+
});
38+
39+
const fetchMock = vi.fn();
40+
vi.stubGlobal("fetch", fetchMock);
41+
42+
await expect(AuthVerify("onedrive")).resolves.toBe("cached-access");
43+
expect(fetchMock).not.toHaveBeenCalled();
44+
});
45+
});

0 commit comments

Comments
 (0)