返回 DeepSeek-Reasonix
frameSessionPool.ts
根目录 / desktop / electron / src / main / browser / frameSessionPool.ts
1 import type { DebuggerRelease } from "./debuggerLease.js";
2 import type { GuestDebugger } from "./guestView.js";
3
4 const key = {};
5 interface ChildSession { sessionId?: string; ready: Promise<string>; users: number; retire: boolean; detached: boolean }
6 export interface FrameSessionLease { ready: Promise<string>; release(failed: boolean): void }
7
8 // Target attachments belong to the root connection, not to one snapshot or
9 // ref lookup. Detaching between adjacent operations tears down the renderer's
10 // domain agents while the next operation may already be initializing them.
11 // Document contexts and remote object groups remain operation-scoped.
12 export class FrameSessionPool {
13 private readonly sessions = new Map<string, ChildSession>();
14 private closed = false;
15 private readonly message = (_event: unknown, method: string, raw: unknown) => {
16 if (method !== "Target.detachedFromTarget") return;
17 const { sessionId } = raw as { sessionId?: string };
18 for (const [target, child] of this.sessions) if (child.sessionId === sessionId) this.sessions.delete(target);
19 };
20 private constructor(private readonly debuggerAPI: GuestDebugger, private readonly connection: DebuggerRelease) {
21 debuggerAPI.on("message", this.message);
22 }
23 static for(debuggerAPI: GuestDebugger, connection: DebuggerRelease): FrameSessionPool {
24 return connection.shared(key, () => new FrameSessionPool(debuggerAPI, connection));
25 }
26 borrow(targetId: string): FrameSessionLease {
27 if (this.closed) throw new Error("browser frame session pool is closed");
28 const existing = this.sessions.get(targetId);
29 if (existing) return this.lease(targetId, existing);
30 const child: ChildSession = { ready: Promise.resolve(""), users: 0, retire: false, detached: false };
31 this.sessions.set(targetId, child);
32 child.ready = (async () => {
33 const attached = await this.connection.send("Target.attachToTarget", { targetId, flatten: true }) as { sessionId: string };
34 child.sessionId = attached.sessionId;
35 if (this.closed || this.sessions.get(targetId) !== child) {
36 throw new Error("browser frame session was invalidated during attachment");
37 }
38 for (const method of ["Page.enable", "Runtime.enable", "DOM.enable"]) {
39 await this.connection.send(method, {}, attached.sessionId);
40 if (this.closed || this.sessions.get(targetId) !== child) throw new Error("browser frame session was detached during initialization");
41 }
42 return attached.sessionId;
43 })().catch(error => {
44 if (this.sessions.get(targetId) === child) this.sessions.delete(targetId);
45 this.detachChild(child);
46 throw error;
47 });
48 return this.lease(targetId, child);
49 }
50 private lease(target: string, child: ChildSession): FrameSessionLease {
51 child.users++;
52 let released = false;
53 return { ready: child.ready, release: failed => {
54 if (released) return;
55 released = true;
56 child.users--; child.retire ||= failed;
57 // Cancellation never detaches a session borrowed by another operation.
58 // An abandoned attachment is removed now and its late result cleans itself.
59 if (!child.users && child.retire) {
60 if (this.sessions.get(target) === child) this.sessions.delete(target);
61 this.detachChild(child);
62 }
63 } };
64 }
65 private detachChild(child: ChildSession): void {
66 if (!child.sessionId || child.detached) return;
67 child.detached = true;
68 this.detach(child.sessionId);
69 }
70 private detach(sessionId: string): void {
71 // Cleanup owns only this opaque session ID. A late attach response cannot
72 // dispatch through, or clear resources belonging to, a replacement pool.
73 try { if (this.debuggerAPI.isAttached()) void this.debuggerAPI.sendCommand("Target.detachFromTarget", { sessionId }).catch(() => {}); } catch { /* Native page already destroyed. */ }
74 }
75 dispose(transportAvailable: boolean): void {
76 if (this.closed) return;
77 this.closed = true;
78 this.debuggerAPI.removeListener("message", this.message);
79 if (transportAvailable && !this.connection.owned) for (const child of [...this.sessions.values()].reverse()) this.detachChild(child);
80 this.sessions.clear();
81 }
82 }
83
83 lines TYPESCRIPT