返回 DeepSeek-Reasonix
mouseInput.ts
根目录 / desktop / electron / src / main / browser / mouseInput.ts
1 import type { MouseInputEvent } from "electron";
2 import type { GuestFrame, GuestPage } from "./guestView.js";
3 import { abortable } from "./captureQueue.js";
4 import { browserFailure } from "./errors.js";
5 import { FrameRuntime, frameOperationSignal } from "./frameRuntime.js";
6 import { scriptCall } from "./pageScripts.js";
7 import { runInFrame } from "./snapshot.js";
8
9 // Chromium's root CDP hit-test graph can still describe the preceding surface
10 // after native reparenting or emulation changes. Resolve the current frame in
11 // its isolated DOM world, then send to that frame's owning renderer. In-process
12 // targets use Electron's direct root-widget input path; OOPIFs use their own
13 // CDP session, never the root's asynchronous cross-process hit-test routing.
14 export async function dispatchMouseInput(page: GuestPage, event: MouseInputEvent, inputScale: () => number, verify: () => void = () => {}): Promise<void> {
15 const signal = frameOperationSignal();
16 const main = page.mainFrame, zoom = page.getZoomFactor(), scale = inputScale();
17 const path: Array<{ parent: GuestFrame; frame: GuestFrame; index: number; url: string }> = [];
18 let runtime: FrameRuntime | undefined;
19 const children = (frame: GuestFrame) => frame.frames ?? frame.framesInSubtree.filter(child => child.parent?.frameTreeNodeId === frame.frameTreeNodeId);
20 const checkpoint = () => {
21 signal?.throwIfAborted(); verify();
22 if (page.isDestroyed() || page.mainFrame !== main || main.detached || page.getZoomFactor() !== zoom || inputScale() !== scale || path.some(item => item.frame.detached || item.frame.url !== item.url || children(item.parent)[item.index] !== item.frame)) throw browserFailure("stale_document", "pointer frame or viewport changed during input preparation");
23 };
24 const evaluate = async (frame: GuestFrame, method: string, input: unknown) => {
25 checkpoint();
26 if (frame !== main) runtime ??= new FrameRuntime(page);
27 const pending = runInFrame(page, frame, scriptCall(method, input), runtime);
28 const result = await (signal ? abortable(pending, signal) : pending);
29 checkpoint();
30 return result;
31 };
32 try {
33 let frame = main, point = { x: event.x / scale, y: event.y / scale };
34 const points = new Map<GuestFrame, { x: number; y: number }>();
35 for (;;) {
36 points.set(frame, point);
37 if (event.type === "mouseMove" && !event.button && await evaluate(frame, "pageInputReady", point) !== true) throw browserFailure("page_not_ready", "input target has not reached a stable rendered frame; observe the page again");
38 const child = await evaluate(frame, "pageInputFrame", point) as { index: number; x: number; y: number; unsupported?: boolean } | null;
39 if (!child) break;
40 if (child.unsupported) throw browserFailure("capability_unsupported", "pointer frame has an unsupported transform");
41 if (path.length >= 16) throw browserFailure("capability_unsupported", "pointer frame nesting exceeds the supported depth");
42 const next = children(frame)[child.index];
43 if (!next || next.detached) throw browserFailure("stale_document", "pointer frame disappeared");
44 path.push({ parent: frame, frame: next, index: child.index, url: next.url });
45 frame = next; point = child;
46 }
47 checkpoint();
48 if (frame === main) { page.sendInputEvent(event); return; }
49 await runtime!.run(frame, "", async (_context, sessionId, send, sessionFrame) => {
50 checkpoint();
51 if (!sessionId) { page.sendInputEvent(event); return; }
52 const ownerPoint = sessionFrame ? points.get(sessionFrame) : undefined;
53 if (!ownerPoint) throw browserFailure("stale_document", "pointer no longer belongs to the resolved renderer");
54 const buttons = event.type === "mouseUp" ? 0 : event.button === "left" ? 1 : event.button === "right" ? 2 : event.button === "middle" ? 4 : 0;
55 // A target session takes CSS pixels local to its renderer. The outer
56 // Electron display scale has already been removed during frame traversal;
57 // applying it again can hit the same large button at the wrong point.
58 await send("Input.dispatchMouseEvent", { type: event.type === "mouseDown" ? "mousePressed" : event.type === "mouseUp" ? "mouseReleased" : "mouseMoved", x: ownerPoint.x, y: ownerPoint.y, button: event.button ?? "none", buttons, clickCount: event.clickCount ?? 0 }, sessionId);
59 });
60 } catch (error) { await runtime?.close(true); throw error; }
61 finally { await runtime?.close(); }
62 }
63
63 lines TYPESCRIPT