Skip to content

Commit bbb083a

Browse files
committed
feat: add GM.runExclusive API for cross-context exclusive execution
1 parent be818dc commit bbb083a

File tree

2 files changed

+101
-0
lines changed

2 files changed

+101
-0
lines changed

src/app/service/content/gm_api/gm_api.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1383,6 +1383,68 @@ export default class GMApi extends GM_Base {
13831383
CAT_scriptLoaded() {
13841384
return this.loadScriptPromise;
13851385
}
1386+
1387+
@GMContext.API({ alias: "GM_runExclusive" })
1388+
["GM.runExclusive"](lockKey: string, cb: (...args: any) => any, timeout: number = -1) {
1389+
lockKey = `${lockKey}`; // 转化为字串
1390+
if (!lockKey || !this.scriptRes) {
1391+
throw new Error("Invalid Calling");
1392+
}
1393+
const key = `${getStorageName(this.scriptRes).replace(/:/g, ":_")}::${lockKey.replace(/:/g, ":_")}`;
1394+
return new Promise((resolve) => {
1395+
let killConn: (() => any) | null | undefined = undefined;
1396+
let error: any;
1397+
let result: any;
1398+
let done = false;
1399+
const onDisconnected = () => {
1400+
killConn = null; // before resolve, set disconnectFn to null
1401+
resolve({
1402+
result,
1403+
error,
1404+
done,
1405+
});
1406+
};
1407+
const onStart = async (con: MessageConnect) => {
1408+
try {
1409+
result = await cb();
1410+
} catch (e) {
1411+
error = e;
1412+
}
1413+
done = true;
1414+
con.sendMessage({
1415+
action: "done",
1416+
data: error ? false : typeof result,
1417+
});
1418+
con.disconnect();
1419+
onDisconnected(); // in case .disconnect() not working
1420+
};
1421+
this.connect("runExclusive", [key]).then((con) => {
1422+
if (killConn === null) {
1423+
// already resolved
1424+
con.disconnect();
1425+
return;
1426+
}
1427+
killConn = () => {
1428+
con.disconnect();
1429+
};
1430+
con.onDisconnect(onDisconnected);
1431+
con.onMessage((data) => {
1432+
switch (data.action) {
1433+
case "start":
1434+
onStart(con);
1435+
break;
1436+
}
1437+
});
1438+
});
1439+
if (timeout > 0) {
1440+
setTimeout(() => {
1441+
error = new Error("timeout");
1442+
killConn?.();
1443+
onDisconnected(); // in case .disconnect() not working
1444+
}, timeout);
1445+
}
1446+
});
1447+
}
13861448
}
13871449

13881450
// 从 GM_Base 对象中解构出 createGMBase 函数并导出(可供其他模块使用)

src/app/service/service_worker/gm_api/gm_api.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import PermissionVerify, { PermissionVerifyApiGet } from "../permission_verify";
1111
import { cacheInstance } from "@App/app/cache";
1212
import { type RuntimeService } from "../runtime";
1313
import { getIcon, isFirefox, getCurrentTab, openInCurrentTab, cleanFileName, makeBlobURL } from "@App/pkg/utils/utils";
14+
import { deferred, type Deferred } from "@App/pkg/utils/utils";
1415
import { type SystemConfig } from "@App/pkg/config/config";
1516
import i18next, { i18nName } from "@App/locales/locales";
1617
import FileSystemFactory from "@Packages/filesystem/factory";
@@ -44,6 +45,7 @@ import { headerModifierMap, headersReceivedMap } from "./gm_xhr";
4445
import { BgGMXhr } from "@App/pkg/utils/xhr/bg_gm_xhr";
4546
import { mightPrepareSetClipboard, setClipboard } from "../clipboard";
4647
import { nativePageWindowOpen } from "../../offscreen/gm_api";
48+
import { stackAsyncTask } from "@App/pkg/utils/async_queue";
4749

4850
let generatedUniqueMarkerIDs = "";
4951
let generatedUniqueMarkerIDWhen = "";
@@ -1305,6 +1307,43 @@ export default class GMApi {
13051307
}
13061308
}
13071309

1310+
@PermissionVerify.API({ link: ["GM.runExclusive", "GM_runExclusive"] })
1311+
runExclusive(request: GMApiRequest<[string, string, any?]>, sender: IGetSender) {
1312+
if (!request.params || request.params.length < 1) {
1313+
throw new Error("param is failed");
1314+
}
1315+
const [lockKey] = request.params as [string, string, any];
1316+
if (!sender.isType(GetSenderType.CONNECT)) {
1317+
throw new Error("GM_download ERROR: sender is not MessageConnect");
1318+
}
1319+
const msgConn = sender.getConnect();
1320+
if (!msgConn) {
1321+
throw new Error("GM_download ERROR: msgConn is undefined");
1322+
}
1323+
let isConnDisconnected = false;
1324+
const d = deferred<boolean>();
1325+
let done: boolean = false;
1326+
const onDisconnected = () => {
1327+
if (isConnDisconnected) return;
1328+
isConnDisconnected = true;
1329+
d.resolve(done);
1330+
};
1331+
msgConn.onDisconnect(onDisconnected);
1332+
msgConn.onMessage((data) => {
1333+
if (data.action === "done") {
1334+
done = true;
1335+
msgConn.disconnect();
1336+
onDisconnected(); // in case .disconnect() not working
1337+
}
1338+
});
1339+
stackAsyncTask(`${lockKey}`, async () => {
1340+
msgConn.sendMessage({
1341+
action: "start",
1342+
});
1343+
return d.promise;
1344+
});
1345+
}
1346+
13081347
handlerNotification() {
13091348
const send = async (
13101349
event: NotificationMessageOption["event"],

0 commit comments

Comments
 (0)