返回 DeepSeek-Reasonix
snapshot.ts
根目录 / desktop / electron / src / main / browser / snapshot.ts
1 import { randomToken, type DocumentRegistry, type FrameBinding } from "./documents.js";
2 import type { GuestFrame, GuestPage } from "./guestView.js";
3 import { scriptCall } from "./pageScripts.js";
4 import { SNAPSHOT_SCRIPT_SOURCE, type SnapshotOutput } from "./snapshotScript.js";
5
6 export const REGISTRY_KEY = "__reasonixBrowserRegistry";
7 export const ISOLATED_WORLD = 1;
8 export const MAX_SNAPSHOT_NODES = 4000;
9
10 export interface SnapshotResult {
11 documentToken: string;
12 url: string;
13 title: string;
14 tree: string;
15 refs: number;
16 }
17
18 // Electron 44 offers an isolated world only on the WebContents (main frame);
19 // WebFrameMain.executeJavaScript runs in the frame's own main world, so child
20 // frame registries live there and a hostile page can at most stale itself.
21 export function runInFrame(page: GuestPage, frame: GuestFrame, code: string): Promise<unknown> {
22 if (frame.frameTreeNodeId === page.mainFrame.frameTreeNodeId) return page.executeJavaScriptInIsolatedWorld(ISOLATED_WORLD, [{ code }]);
23 return frame.executeJavaScript(code);
24 }
25
26 export function findFrame(page: GuestPage, frameTreeNodeId: number): GuestFrame | null {
27 const main = page.mainFrame;
28 if (main.frameTreeNodeId === frameTreeNodeId) return main;
29 for (const frame of main.framesInSubtree) {
30 if (frame.frameTreeNodeId === frameTreeNodeId && !frame.detached) return frame;
31 }
32 return null;
33 }
34
35 function isSnapshotOutput(value: unknown): value is SnapshotOutput {
36 if (typeof value !== "object" || value === null) return false;
37 const out = value as Record<string, unknown>;
38 return typeof out.docId === "string" && typeof out.tree === "string" && typeof out.refs === "number" && typeof out.nodes === "number";
39 }
40
41 function clipURL(url: string): string {
42 return url.length > 120 ? `${url.slice(0, 119)}…` : url;
43 }
44
45 function indent(tree: string): string {
46 return tree.split("\n").map((line) => ` ${line}`).join("\n");
47 }
48
49 export async function takeSnapshot(page: GuestPage, tabId: string, epoch: number, selector: string, documents: DocumentRegistry): Promise<SnapshotResult> {
50 const snapshotId = randomToken(8);
51 const main = page.mainFrame;
52 const mainRaw = await runInFrame(page, main, scriptCall(SNAPSHOT_SCRIPT_SOURCE, { key: REGISTRY_KEY, snapshotId, prefix: "", selector, budget: MAX_SNAPSHOT_NODES }));
53 if (!isSnapshotOutput(mainRaw)) throw new Error("snapshot script returned no tree");
54 const frames: FrameBinding[] = [{ prefix: "", frameTreeNodeId: main.frameTreeNodeId, docId: mainRaw.docId }];
55 const sections = [mainRaw.tree];
56 let refs = mainRaw.refs;
57 let budget = MAX_SNAPSHOT_NODES - mainRaw.nodes;
58 let index = 0;
59 for (const frame of main.framesInSubtree) {
60 if (frame.frameTreeNodeId === main.frameTreeNodeId || frame.detached) continue;
61 if (budget <= 0) break;
62 index += 1;
63 const prefix = `f${index}`;
64 let raw: unknown;
65 try {
66 raw = await frame.executeJavaScript(scriptCall(SNAPSHOT_SCRIPT_SOURCE, { key: REGISTRY_KEY, snapshotId, prefix, selector: "", budget }));
67 } catch {
68 continue;
69 }
70 if (!isSnapshotOutput(raw)) continue;
71 frames.push({ prefix, frameTreeNodeId: frame.frameTreeNodeId, docId: raw.docId });
72 refs += raw.refs;
73 budget -= raw.nodes;
74 if (raw.tree === "") continue;
75 sections.push(`frame ${prefix} ${JSON.stringify(clipURL(frame.url))}\n${indent(raw.tree)}`);
76 }
77 const documentToken = documents.issue({ tabId, epoch, snapshotId, frames });
78 return { documentToken, url: page.getURL(), title: page.getTitle(), tree: sections.join("\n"), refs };
79 }
80
80 lines TYPESCRIPT