-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Expand file tree
/
Copy pathMcpPage.ts
More file actions
128 lines (109 loc) · 3.13 KB
/
McpPage.ts
File metadata and controls
128 lines (109 loc) · 3.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import type {
Dialog,
ElementHandle,
Page,
Viewport,
} from './third_party/index.js';
import {takeSnapshot} from './tools/snapshot.js';
import type {ContextPage} from './tools/ToolDefinition.js';
import type {
EmulationSettings,
GeolocationOptions,
TextSnapshot,
TextSnapshotNode,
} from './types.js';
/**
* Per-page state wrapper. Consolidates dialog, snapshot, emulation,
* and metadata that were previously scattered across Maps in McpContext.
*
* Internal class consumed only by McpContext. Fields are public for direct
* read/write access. The dialog field is private because it requires an
* event listener lifecycle managed by the constructor/dispose pair.
*/
export class McpPage implements ContextPage {
readonly pptrPage: Page;
readonly id: number;
// Snapshot
textSnapshot: TextSnapshot | null = null;
uniqueBackendNodeIdToMcpId = new Map<string, string>();
// Emulation
emulationSettings: EmulationSettings = {};
// Metadata
isolatedContextName?: string;
devToolsPage?: Page;
// Dialog
#dialog?: Dialog;
#dialogHandler: (dialog: Dialog) => void;
constructor(page: Page, id: number) {
this.pptrPage = page;
this.id = id;
this.#dialogHandler = (dialog: Dialog): void => {
this.#dialog = dialog;
};
page.on('dialog', this.#dialogHandler);
}
get dialog(): Dialog | undefined {
return this.#dialog;
}
clearDialog(): void {
this.#dialog = undefined;
}
get networkConditions(): string | null {
return this.emulationSettings.networkConditions ?? null;
}
get cpuThrottlingRate(): number {
return this.emulationSettings.cpuThrottlingRate ?? 1;
}
get geolocation(): GeolocationOptions | null {
return this.emulationSettings.geolocation ?? null;
}
get viewport(): Viewport | null {
return this.emulationSettings.viewport ?? null;
}
get userAgent(): string | null {
return this.emulationSettings.userAgent ?? null;
}
get colorScheme(): 'dark' | 'light' | null {
return this.emulationSettings.colorScheme ?? null;
}
dispose(): void {
this.pptrPage.off('dialog', this.#dialogHandler);
}
async getElementByUid(uid: string): Promise<ElementHandle<Element>> {
if (!this.textSnapshot) {
throw new Error(
`No snapshot found for page ${this.id ?? '?'}. Use ${takeSnapshot.name} to capture one.`,
);
}
const node = this.textSnapshot.idToNode.get(uid);
if (!node) {
throw new Error(`Element uid "${uid}" not found on page ${this.id}.`);
}
return this.#resolveElementHandle(node, uid);
}
async #resolveElementHandle(
node: TextSnapshotNode,
uid: string,
): Promise<ElementHandle<Element>> {
const message = `Element with uid ${uid} no longer exists on the page.`;
try {
const handle = await node.elementHandle();
if (!handle) {
throw new Error(message);
}
return handle;
} catch (error) {
throw new Error(message, {
cause: error,
});
}
}
getAXNodeByUid(uid: string) {
return this.textSnapshot?.idToNode.get(uid);
}
}