返回 DeepSeek-Reasonix
ipc.browser.test.ts
根目录 / desktop / electron / src / main / ipc.browser.test.ts
1 import assert from "node:assert/strict";
2 import type { IpcMain } from "electron";
3 import { test } from "node:test";
4 import { IPC, type IpcResult } from "../shared/ipc.js";
5 import { ActionExecutor, type ActRequest } from "./browser/actions.js";
6 import { DocumentRegistry } from "./browser/documents.js";
7 import { BROWSER_ERR_STALE_REFERENCE, BROWSER_ERR_TAKEN_OVER } from "./browser/errors.js";
8 import { FakeGuestView, FakeViewFactory, silentLog } from "./browser/fakeGuestViews.js";
9 import { BrowserSurfaceManager } from "./browser/surfaceManager.js";
10 import { parseContract } from "./contract.js";
11 import { registerRendererIpc } from "./ipc.js";
12 import { RpcError } from "./rpc.js";
13
14 test("trusted toolbar takeover bypasses input suppression immediately, fences actions and permits Resume", async () => {
15 type Handler = (event: unknown, ...args: unknown[]) => Promise<IpcResult>;
16 const handlers = new Map<string, Handler>();
17 const ipcMain = { handle: (channel: string, run: Handler) => handlers.set(channel, run), on() {} } as unknown as IpcMain;
18 const trustedSender = { id: 1 }, trustedFrame = {};
19 const trusted = { sender: trustedSender, senderFrame: trustedFrame };
20 const reports: string[] = [];
21 const manager = new BrowserSurfaceManager({ views: new FakeViewFactory(), contentSize: () => null,
22 onTakeover: (_tab, reason) => reports.push(reason), onCrash() {}, log: silentLog, now: () => 1000, openWaitMs: 5 });
23 const tab = await manager.open("https://example.com", { taskId: "task", temporary: false });
24 const page = (tab.view as FakeGuestView).page;
25 const documents = new DocumentRegistry(() => "token");
26 const documentToken = documents.issue({ tabId: tab.id, epoch: tab.epoch, snapshotId: "snapshot",
27 frames: [{ prefix: "", frameTreeNodeId: page.mainFrame.frameTreeNodeId, docId: "doc" }] });
28 const actions = new ActionExecutor({ surfaces: manager, documents, fileExists: () => false, sleep: async () => {} });
29 const request: ActRequest = { operationId: "op", tabId: tab.id, documentToken, action: "click", ref: "e1",
30 text: "", keys: "", options: [], files: [], submit: false, deltaX: 0, deltaY: 0 };
31 registerRendererIpc({ ipcMain, contract: parseContract({ digest: "test", commands: ["ListTabs"] }),
32 window: { isTrustedSender: (sender, frame) => sender === trustedSender && frame === trustedFrame,
33 minimise() {}, toggleMaximise() {}, isMaximised: () => false, close() {},
34 bounds: () => ({ x: 0, y: 0, width: 1, height: 1, maximised: false }), setTheme() {}, setBackgroundColour() {},
35 getAppZoom: async () => 1, setAppZoom: async () => 1, resetAppZoom: async () => 1 },
36 invoke: async () => undefined, serviceState: () => ({ phase: "ready", generation: "g" }),
37 clipboard: { writeText() {}, readText: () => "" }, openExternal: async () => {}, log: silentLog,
38 browser: { list: () => manager.list(), open: async (url, options) => { const opened = await manager.open(url, options); return manager.list().find((entry) => entry.id === opened.id)!; },
39 close: (id) => manager.close(id), activate: (id) => manager.activate(id), navigate: async (id, target) => { await manager.navigate(id, target); },
40 setZoom: (id, factor) => manager.setZoom(id, factor), toggleDevTools: (id) => manager.toggleDevTools(id),
41 takeover: (id) => manager.takeover(id, "user takeover"), resume: (id) => manager.resume(id),
42 setLayout: (rect) => manager.setLayout(rect), setOverlay: (active) => manager.setOverlay(active) },
43 });
44 const takeover = handlers.get(IPC.browserUserTakeover)!;
45 assert.equal(handlers.has(IPC.browserTakeover), false, "guest input reports use a separate channel");
46 const epoch = tab.epoch;
47 manager.markAgentInput(tab);
48 assert.equal(manager.takeoverFromSender(page.id, "mousedown"), false, "physical detection is suppressed during the 750ms grace window");
49 assert.deepEqual(await takeover({ sender: { id: page.id }, senderFrame: {} }, tab.id), { ok: false, message: "untrusted sender" });
50 assert.deepEqual(await takeover({ sender: trustedSender, senderFrame: {} }, tab.id), { ok: false, message: "untrusted sender" });
51 assert.equal((await takeover(trusted, "")).ok, false);
52 assert.equal(tab.epoch, epoch, "untrusted and malformed calls cannot alter ownership");
53 const pending = takeover(trusted, tab.id);
54 assert.equal(tab.mode, "human", "explicit takeover is synchronous even inside the suppression window");
55 assert.equal(tab.epoch, epoch + 1);
56 assert.deepEqual(await pending, { ok: true, value: undefined });
57 assert.deepEqual(reports, ["user takeover"]);
58 await assert.rejects(actions.act(tab, request, () => {}), (error: unknown) => error instanceof RpcError && error.code === BROWSER_ERR_TAKEN_OVER);
59 assert.deepEqual(await handlers.get(IPC.browserResume)!(trusted, tab.id), { ok: true, value: undefined });
60 assert.equal(tab.mode, "agent");
61 assert.equal(tab.epoch, epoch + 2);
62 await assert.rejects(actions.act(tab, request, () => {}), (error: unknown) => error instanceof RpcError && error.code === BROWSER_ERR_STALE_REFERENCE);
63 assert.equal(page.inputs.length, 0, "takeover and Resume never replay a stale write");
64 manager.destroyAll();
65 });
66
66 lines TYPESCRIPT