返回 DeepSeek-Reasonix
refResolver.ts
根目录 / desktop / electron / src / main / browser / refResolver.ts
1 import { parseRef, type DocumentBinding, type FrameBinding } from "./documents.js";
2 import { staleReference } from "./errors.js";
3 import type { GuestFrame, GuestPage } from "./guestView.js";
4 import { LOCATE_SCRIPT_SOURCE, RESOLVE_SCRIPT_SOURCE, scriptCall, type LocateOutput, type ResolvedElement, type ResolveOutput } from "./pageScripts.js";
5 import { findFrame, REGISTRY_KEY, runInFrame } from "./snapshot.js";
6 import { FrameRuntime } from "./frameRuntime.js";
7 import { RpcError } from "../rpc.js";
8
9 export interface RefFrame {
10 frame: GuestFrame;
11 binding: FrameBinding;
12 isMainFrame: boolean;
13 }
14
15 export interface ResolvedRef extends RefFrame {
16 element: ResolvedElement;
17 }
18
19 export interface LocatedRef extends RefFrame {
20 ref: string;
21 snapshotId: string;
22 tag: string;
23 type: string;
24 path: string;
25 }
26
27 export type RefOutcome<T> = { ok: true; value: T } | { ok: false; reason: string };
28
29 function hasOk(value: unknown): value is { ok: boolean; reason?: string } {
30 return typeof value === "object" && value !== null && typeof (value as { ok?: unknown }).ok === "boolean";
31 }
32
33 export function frameForRef(page: GuestPage, binding: DocumentBinding, ref: string): RefFrame {
34 const parsed = parseRef(ref);
35 if (!parsed) throw staleReference(`malformed ref ${JSON.stringify(ref)}`);
36 const frameBinding = binding.frames.find((entry) => entry.prefix === parsed.prefix);
37 if (!frameBinding) throw staleReference(`ref ${ref} names a frame the snapshot did not see`);
38 const frame = findFrame(page, frameBinding.frameTreeNodeId);
39 if (!frame) throw staleReference(`frame of ${ref} is gone`);
40 return { frame, binding: frameBinding, isMainFrame: frame.frameTreeNodeId === page.mainFrame.frameTreeNodeId };
41 }
42
43 async function runRefScript(page: GuestPage, target: RefFrame, source: string, snapshotId: string, ref: string, extra: Record<string, unknown>, runtime?: FrameRuntime): Promise<unknown> {
44 try {
45 return await runInFrame(page, target.frame, scriptCall(source, { key: REGISTRY_KEY, snapshotId, docId: target.binding.docId, ref, ...extra }), runtime);
46 } catch (error) {
47 // A protocol timeout or script failure does not prove that the ref is
48 // stale. Preserve the runtime classification for tool and export callers.
49 if (error instanceof RpcError) throw error;
50 throw staleReference(`frame of ${ref} cannot run scripts: ${String(error)}`);
51 }
52 }
53
54 // Resolves a snapshot ref to viewport geometry inside the frame that produced
55 // it. Stale refs throw -32010; a live ref that cannot be acted on reports why.
56 export async function resolveRef(page: GuestPage, binding: DocumentBinding, ref: string, scroll: boolean): Promise<RefOutcome<ResolvedRef>> {
57 const target = frameForRef(page, binding, ref);
58 const runtime = target.isMainFrame ? undefined : new FrameRuntime(page);
59 try { return await resolveTarget(page, binding, ref, scroll, target, runtime); }
60 finally { await runtime?.close(); }
61 }
62
63 async function resolveTarget(page: GuestPage, binding: DocumentBinding, ref: string, scroll: boolean, target: RefFrame, runtime?: FrameRuntime): Promise<RefOutcome<ResolvedRef>> {
64 const raw = await runRefScript(page, target, RESOLVE_SCRIPT_SOURCE, binding.snapshotId, ref, { scroll, localCoordinates: true }, runtime);
65 if (!hasOk(raw)) throw staleReference(`resolver returned nothing for ${ref}`);
66 const out = raw as ResolveOutput;
67 if (!out.ok) {
68 if (out.reason === "stale") throw staleReference(`${ref} predates the current document`);
69 return { ok: false, reason: out.reason };
70 }
71 if (!out.frameOffsetKnown) return { ok: false, reason: "element sits in a cross-origin frame; its position cannot be determined" };
72 let frame = target.frame;
73 while (frame.parent) {
74 const parent = frame.parent;
75 const siblings = parent.frames ?? parent.framesInSubtree.filter(child => child.parent?.frameTreeNodeId === parent.frameTreeNodeId);
76 const index = siblings.findIndex(child => child.frameTreeNodeId === frame.frameTreeNodeId);
77 const offset = await runInFrame(page, parent, scriptCall("pageFrameGeometry", { index, x: out.x + out.width / 2, y: out.y + out.height / 2, scroll }), runtime) as { x: number; y: number; scaleX: number; scaleY: number } | null;
78 if (!offset || index < 0) return { ok: false, reason: "frame is hidden, covered or has unsupported transform" };
79 out.width *= offset.scaleX; out.height *= offset.scaleY;
80 out.x = offset.x - out.width / 2; out.y = offset.y - out.height / 2;
81 frame = parent;
82 }
83 return { ok: true, value: { ...target, element: out } };
84 }
85
86 export async function locateRef(page: GuestPage, binding: DocumentBinding, ref: string): Promise<RefOutcome<LocatedRef>> {
87 const target = frameForRef(page, binding, ref);
88 const raw = await runRefScript(page, target, LOCATE_SCRIPT_SOURCE, binding.snapshotId, ref, {});
89 if (!hasOk(raw)) throw staleReference(`locator returned nothing for ${ref}`);
90 const out = raw as LocateOutput;
91 if (!out.ok) {
92 if (out.reason === "stale") throw staleReference(`${ref} predates the current document`);
93 return { ok: false, reason: out.reason };
94 }
95 return { ok: true, value: { ...target, ref, snapshotId: binding.snapshotId, tag: out.tag, type: out.type, path: out.path } };
96 }
97
97 lines TYPESCRIPT