@olimsaidov/icdp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +104 -0
- package/dist/frame/ax-tree.mjs +2186 -0
- package/dist/frame/index.d.mts +17 -0
- package/dist/frame/index.mjs +782 -0
- package/dist/host/index.d.mts +99 -0
- package/dist/host/index.mjs +328 -0
- package/dist/protocol.d.mts +102 -0
- package/dist/protocol.mjs +16 -0
- package/dist/relay/core.d.mts +54 -0
- package/dist/relay/core.mjs +411 -0
- package/dist/relay/node.d.mts +24 -0
- package/dist/relay/node.mjs +99 -0
- package/package.json +77 -0
- package/src/frame/ax-tree.ts +2393 -0
- package/src/frame/index.ts +1048 -0
- package/src/host/index.ts +422 -0
- package/src/protocol.ts +125 -0
- package/src/relay/core.ts +499 -0
- package/src/relay/node.ts +135 -0
|
@@ -0,0 +1,782 @@
|
|
|
1
|
+
import { CDP_SERVER_ERROR, isHandshakeMessage } from "../protocol.mjs";
|
|
2
|
+
import { createDomRegistry, getAXNodeAndAncestors, getChildAXNodes, getFullAXTree, getPartialAXTree, getRootAXNode, queryAXTree } from "./ax-tree.mjs";
|
|
3
|
+
import chobitsu from "chobitsu";
|
|
4
|
+
//#region src/frame/index.ts
|
|
5
|
+
const cdp = chobitsu;
|
|
6
|
+
const frameId = "icdp-frame";
|
|
7
|
+
const registry = createDomRegistry();
|
|
8
|
+
const noop = () => ({});
|
|
9
|
+
let port = null;
|
|
10
|
+
let nextScriptIdentifier = 1;
|
|
11
|
+
let nextSearchId = 1;
|
|
12
|
+
const outboundMethods = /* @__PURE__ */ new Map();
|
|
13
|
+
const searchResults = /* @__PURE__ */ new Map();
|
|
14
|
+
let runtimeEnabled = false;
|
|
15
|
+
const queuedRuntimeEvents = [];
|
|
16
|
+
const consoleWrapped = Symbol("icdp-console-wrapped");
|
|
17
|
+
let pressedElement = null;
|
|
18
|
+
let hoveredElement = null;
|
|
19
|
+
let lastClickElement = null;
|
|
20
|
+
let lastClickTime = 0;
|
|
21
|
+
function sendToHost(message) {
|
|
22
|
+
port?.postMessage(JSON.stringify(message));
|
|
23
|
+
}
|
|
24
|
+
function sendRuntimeEvent(message) {
|
|
25
|
+
if (runtimeEnabled) sendToHost(message);
|
|
26
|
+
else {
|
|
27
|
+
queuedRuntimeEvents.push(message);
|
|
28
|
+
if (queuedRuntimeEvents.length > 200) queuedRuntimeEvents.shift();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
chobitsu.setOnMessage((raw) => {
|
|
32
|
+
let message;
|
|
33
|
+
try {
|
|
34
|
+
message = JSON.parse(raw);
|
|
35
|
+
} catch {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (message.id != null) {
|
|
39
|
+
const method = outboundMethods.get(message.id);
|
|
40
|
+
outboundMethods.delete(message.id);
|
|
41
|
+
if (method && message.error?.message === `${method} unimplemented`) message.error = {
|
|
42
|
+
code: CDP_SERVER_ERROR,
|
|
43
|
+
message: `Method not found: ${method}`
|
|
44
|
+
};
|
|
45
|
+
else if (message.error && message.error.code == null) message.error = {
|
|
46
|
+
...message.error,
|
|
47
|
+
code: CDP_SERVER_ERROR
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
sendToHost(message);
|
|
51
|
+
});
|
|
52
|
+
function elementForBackendId(id) {
|
|
53
|
+
const node = registry.nodeForBackendId(id);
|
|
54
|
+
const element = node instanceof Element ? node : node?.parentElement;
|
|
55
|
+
if (!element) throw new Error(`No element for backendDOMNodeId=${id}`);
|
|
56
|
+
return element;
|
|
57
|
+
}
|
|
58
|
+
function boxModel(id) {
|
|
59
|
+
const rect = elementForBackendId(id).getBoundingClientRect();
|
|
60
|
+
const quad = [
|
|
61
|
+
rect.left,
|
|
62
|
+
rect.top,
|
|
63
|
+
rect.right,
|
|
64
|
+
rect.top,
|
|
65
|
+
rect.right,
|
|
66
|
+
rect.bottom,
|
|
67
|
+
rect.left,
|
|
68
|
+
rect.bottom
|
|
69
|
+
].map(Math.round);
|
|
70
|
+
return { model: {
|
|
71
|
+
content: quad,
|
|
72
|
+
padding: quad,
|
|
73
|
+
border: quad,
|
|
74
|
+
margin: quad,
|
|
75
|
+
width: Math.round(rect.width),
|
|
76
|
+
height: Math.round(rect.height)
|
|
77
|
+
} };
|
|
78
|
+
}
|
|
79
|
+
function contentQuads(id) {
|
|
80
|
+
return { quads: [boxModel(id).model.content] };
|
|
81
|
+
}
|
|
82
|
+
function describeNode(id) {
|
|
83
|
+
const node = registry.nodeForBackendId(id);
|
|
84
|
+
if (!node) throw new Error(`No element for backendDOMNodeId=${id}`);
|
|
85
|
+
return { node: domNode(node, 0) };
|
|
86
|
+
}
|
|
87
|
+
function attributesFor(el) {
|
|
88
|
+
const attributes = [];
|
|
89
|
+
for (const attr of el.attributes) attributes.push(attr.name, attr.value);
|
|
90
|
+
return attributes;
|
|
91
|
+
}
|
|
92
|
+
function childNodesFor(node, depth) {
|
|
93
|
+
if (depth === 0) return [];
|
|
94
|
+
const nextDepth = depth < 0 ? -1 : depth - 1;
|
|
95
|
+
return Array.from(node.childNodes).map((child) => domNode(child, nextDepth));
|
|
96
|
+
}
|
|
97
|
+
function domNode(node, depth) {
|
|
98
|
+
const nodeId = registry.backendIdFor(node);
|
|
99
|
+
const children = childNodesFor(node, depth);
|
|
100
|
+
const nodeName = node instanceof Element && node.namespaceURI === "http://www.w3.org/1999/xhtml" ? node.nodeName.toUpperCase() : node.nodeName;
|
|
101
|
+
const result = {
|
|
102
|
+
backendNodeId: nodeId,
|
|
103
|
+
localName: node instanceof Element ? node.localName : "",
|
|
104
|
+
nodeId,
|
|
105
|
+
nodeName,
|
|
106
|
+
nodeType: node.nodeType,
|
|
107
|
+
nodeValue: node.nodeValue ?? ""
|
|
108
|
+
};
|
|
109
|
+
if (node.hasChildNodes()) {
|
|
110
|
+
result.childNodeCount = node.childNodes.length;
|
|
111
|
+
if (depth !== 0) result.children = children;
|
|
112
|
+
}
|
|
113
|
+
if (node instanceof Document) {
|
|
114
|
+
result.documentURL = location.href;
|
|
115
|
+
result.baseURL = document.baseURI;
|
|
116
|
+
result.xmlVersion = "";
|
|
117
|
+
} else if (node instanceof DocumentType) {
|
|
118
|
+
result.publicId = node.publicId;
|
|
119
|
+
result.systemId = node.systemId;
|
|
120
|
+
result.internalSubset = node.internalSubset;
|
|
121
|
+
} else if (node instanceof Element) {
|
|
122
|
+
result.attributes = attributesFor(node);
|
|
123
|
+
if (node instanceof HTMLIFrameElement && node.contentDocument) {
|
|
124
|
+
result.frameId = `${frameId}:${nodeId}`;
|
|
125
|
+
result.contentDocument = domNode(node.contentDocument, depth);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
function runtimeValue(value) {
|
|
131
|
+
if (value === void 0) return { type: "undefined" };
|
|
132
|
+
if (value === null) return {
|
|
133
|
+
type: "object",
|
|
134
|
+
subtype: "null",
|
|
135
|
+
value: null
|
|
136
|
+
};
|
|
137
|
+
if (value instanceof Node) return nodeRuntimeValue(value);
|
|
138
|
+
if (typeof value === "bigint") return {
|
|
139
|
+
type: "bigint",
|
|
140
|
+
unserializableValue: `${value}n`,
|
|
141
|
+
description: `${value}n`
|
|
142
|
+
};
|
|
143
|
+
if (typeof value === "symbol") return {
|
|
144
|
+
type: "symbol",
|
|
145
|
+
description: String(value)
|
|
146
|
+
};
|
|
147
|
+
if (typeof value === "function") return {
|
|
148
|
+
type: "function",
|
|
149
|
+
description: String(value)
|
|
150
|
+
};
|
|
151
|
+
if (typeof value === "number" && !Number.isFinite(value)) return {
|
|
152
|
+
type: "number",
|
|
153
|
+
unserializableValue: String(value),
|
|
154
|
+
description: String(value)
|
|
155
|
+
};
|
|
156
|
+
if (typeof value === "object") return {
|
|
157
|
+
type: "object",
|
|
158
|
+
value: JSON.parse(JSON.stringify(value)),
|
|
159
|
+
description: Object.prototype.toString.call(value)
|
|
160
|
+
};
|
|
161
|
+
return {
|
|
162
|
+
type: typeof value,
|
|
163
|
+
value
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function nodeRuntimeValue(node) {
|
|
167
|
+
const element = node instanceof Element ? node : node.parentElement;
|
|
168
|
+
const objectId = `backend:${registry.backendIdFor(element || node)}`;
|
|
169
|
+
return {
|
|
170
|
+
type: "object",
|
|
171
|
+
subtype: "node",
|
|
172
|
+
className: node instanceof Element ? node.constructor.name : "Node",
|
|
173
|
+
description: node instanceof Element ? node.outerHTML : node.nodeName,
|
|
174
|
+
objectId
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function cloneConsoleObject(value, seen = /* @__PURE__ */ new WeakSet(), depth = 0) {
|
|
178
|
+
if (value === null || typeof value !== "object") return value;
|
|
179
|
+
if (seen.has(value)) return "[Circular]";
|
|
180
|
+
if (depth >= 4) return Object.prototype.toString.call(value);
|
|
181
|
+
seen.add(value);
|
|
182
|
+
if (value instanceof Error) return {
|
|
183
|
+
name: value.name,
|
|
184
|
+
message: value.message,
|
|
185
|
+
stack: value.stack
|
|
186
|
+
};
|
|
187
|
+
if (value instanceof Element) return {
|
|
188
|
+
tagName: value.tagName.toLowerCase(),
|
|
189
|
+
id: value.id || void 0,
|
|
190
|
+
className: typeof value.className === "string" && value.className ? value.className : void 0,
|
|
191
|
+
textContent: (value.textContent || "").trim().slice(0, 120) || void 0
|
|
192
|
+
};
|
|
193
|
+
if (Array.isArray(value)) return value.slice(0, 100).map((item) => cloneConsoleObject(item, seen, depth + 1));
|
|
194
|
+
if (value instanceof Map) return { entries: Array.from(value.entries()).slice(0, 100).map(([key, entryValue]) => [cloneConsoleObject(key, seen, depth + 1), cloneConsoleObject(entryValue, seen, depth + 1)]) };
|
|
195
|
+
if (value instanceof Set) return { values: Array.from(value.values()).slice(0, 100).map((item) => cloneConsoleObject(item, seen, depth + 1)) };
|
|
196
|
+
if (value instanceof Date) return value.toISOString();
|
|
197
|
+
if (value instanceof RegExp) return String(value);
|
|
198
|
+
return Object.fromEntries(Object.entries(value).slice(0, 100).map(([key, entryValue]) => [key, cloneConsoleObject(entryValue, seen, depth + 1)]));
|
|
199
|
+
}
|
|
200
|
+
function consoleRuntimeValue(value) {
|
|
201
|
+
if (value === void 0 || value === null || typeof value !== "object") return runtimeValue(value);
|
|
202
|
+
const description = value instanceof Error ? value.stack || `${value.name}: ${value.message}` : value instanceof Element ? value.outerHTML : Object.prototype.toString.call(value);
|
|
203
|
+
return {
|
|
204
|
+
type: "object",
|
|
205
|
+
subtype: value instanceof Error ? "error" : Array.isArray(value) ? "array" : value instanceof Element ? "node" : void 0,
|
|
206
|
+
className: value.constructor?.name,
|
|
207
|
+
description,
|
|
208
|
+
value: cloneConsoleObject(value)
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function flushQueuedRuntimeEvents() {
|
|
212
|
+
while (queuedRuntimeEvents.length) {
|
|
213
|
+
const event = queuedRuntimeEvents.shift();
|
|
214
|
+
if (event) sendToHost(event);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function enableRuntime() {
|
|
218
|
+
runtimeEnabled = true;
|
|
219
|
+
sendToHost({
|
|
220
|
+
method: "Runtime.executionContextCreated",
|
|
221
|
+
params: { context: {
|
|
222
|
+
id: 1,
|
|
223
|
+
name: "top",
|
|
224
|
+
origin: location.origin
|
|
225
|
+
} }
|
|
226
|
+
});
|
|
227
|
+
flushQueuedRuntimeEvents();
|
|
228
|
+
return {};
|
|
229
|
+
}
|
|
230
|
+
function emitConsole(type, args) {
|
|
231
|
+
sendRuntimeEvent({
|
|
232
|
+
method: "Runtime.consoleAPICalled",
|
|
233
|
+
params: {
|
|
234
|
+
type,
|
|
235
|
+
args: args.map(consoleRuntimeValue),
|
|
236
|
+
executionContextId: 1,
|
|
237
|
+
timestamp: Date.now(),
|
|
238
|
+
stackTrace: { callFrames: [] }
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
function installConsoleBridge() {
|
|
243
|
+
for (const [name, type] of Object.entries({
|
|
244
|
+
clear: "clear",
|
|
245
|
+
debug: "debug",
|
|
246
|
+
dir: "dir",
|
|
247
|
+
error: "error",
|
|
248
|
+
group: "startGroup",
|
|
249
|
+
groupCollapsed: "startGroupCollapsed",
|
|
250
|
+
groupEnd: "endGroup",
|
|
251
|
+
info: "info",
|
|
252
|
+
log: "log",
|
|
253
|
+
table: "table",
|
|
254
|
+
warn: "warning"
|
|
255
|
+
})) {
|
|
256
|
+
const original = console[name];
|
|
257
|
+
if (typeof original !== "function") continue;
|
|
258
|
+
let current = wrapConsoleMethod(original, type);
|
|
259
|
+
Object.defineProperty(console, name, {
|
|
260
|
+
configurable: true,
|
|
261
|
+
get: () => current,
|
|
262
|
+
set: (next) => {
|
|
263
|
+
current = typeof next === "function" ? wrapConsoleMethod(next, type) : next;
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function wrapConsoleMethod(fn, type) {
|
|
269
|
+
if (fn[consoleWrapped]) return fn;
|
|
270
|
+
const wrapped = function(...args) {
|
|
271
|
+
fn.apply(console, args);
|
|
272
|
+
emitConsole(type, args);
|
|
273
|
+
};
|
|
274
|
+
Object.defineProperty(wrapped, consoleWrapped, { value: true });
|
|
275
|
+
return wrapped;
|
|
276
|
+
}
|
|
277
|
+
function highlight(el) {
|
|
278
|
+
const rect = el.getBoundingClientRect();
|
|
279
|
+
const marker = document.createElement("div");
|
|
280
|
+
marker.style.cssText = [
|
|
281
|
+
"position:fixed",
|
|
282
|
+
`left:${rect.left}px`,
|
|
283
|
+
`top:${rect.top}px`,
|
|
284
|
+
`width:${rect.width}px`,
|
|
285
|
+
`height:${rect.height}px`,
|
|
286
|
+
"z-index:2147483647",
|
|
287
|
+
"pointer-events:none",
|
|
288
|
+
"outline:2px solid #005fb8",
|
|
289
|
+
"background:rgba(0,95,184,.08)"
|
|
290
|
+
].join(";");
|
|
291
|
+
document.documentElement.appendChild(marker);
|
|
292
|
+
setTimeout(() => marker.remove(), 500);
|
|
293
|
+
}
|
|
294
|
+
function setNativeValue(el, value) {
|
|
295
|
+
const proto = el instanceof HTMLInputElement ? HTMLInputElement.prototype : HTMLTextAreaElement.prototype;
|
|
296
|
+
(Object.getOwnPropertyDescriptor(proto, "value")?.set)?.call(el, value);
|
|
297
|
+
}
|
|
298
|
+
function canSelectText(el) {
|
|
299
|
+
return [
|
|
300
|
+
"",
|
|
301
|
+
"text",
|
|
302
|
+
"search",
|
|
303
|
+
"tel",
|
|
304
|
+
"url",
|
|
305
|
+
"password"
|
|
306
|
+
].includes(el.type);
|
|
307
|
+
}
|
|
308
|
+
function insertText(text) {
|
|
309
|
+
const el = document.activeElement;
|
|
310
|
+
if (!el) return;
|
|
311
|
+
highlight(el);
|
|
312
|
+
if (el instanceof HTMLInputElement && !canSelectText(el)) {
|
|
313
|
+
setNativeValue(el, text);
|
|
314
|
+
el.dispatchEvent(new InputEvent("input", {
|
|
315
|
+
bubbles: true,
|
|
316
|
+
data: text,
|
|
317
|
+
inputType: "insertText"
|
|
318
|
+
}));
|
|
319
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
320
|
+
} else if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
|
|
321
|
+
const start = el.selectionStart ?? el.value.length;
|
|
322
|
+
const end = el.selectionEnd ?? el.value.length;
|
|
323
|
+
setNativeValue(el, `${el.value.slice(0, start)}${text}${el.value.slice(end)}`);
|
|
324
|
+
try {
|
|
325
|
+
el.setSelectionRange(start + text.length, start + text.length);
|
|
326
|
+
} catch {}
|
|
327
|
+
el.dispatchEvent(new InputEvent("input", {
|
|
328
|
+
bubbles: true,
|
|
329
|
+
data: text,
|
|
330
|
+
inputType: "insertText"
|
|
331
|
+
}));
|
|
332
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
333
|
+
} else if (el.isContentEditable) document.execCommand("insertText", false, text);
|
|
334
|
+
}
|
|
335
|
+
function deleteBackward() {
|
|
336
|
+
const el = document.activeElement;
|
|
337
|
+
if (!el) return;
|
|
338
|
+
if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
|
|
339
|
+
const start = el.selectionStart ?? el.value.length;
|
|
340
|
+
const end = el.selectionEnd ?? el.value.length;
|
|
341
|
+
if (start === 0 && end === 0) return;
|
|
342
|
+
const nextStart = start === end ? start - 1 : start;
|
|
343
|
+
setNativeValue(el, `${el.value.slice(0, nextStart)}${el.value.slice(end)}`);
|
|
344
|
+
try {
|
|
345
|
+
el.setSelectionRange(nextStart, nextStart);
|
|
346
|
+
} catch {}
|
|
347
|
+
el.dispatchEvent(new InputEvent("input", {
|
|
348
|
+
bubbles: true,
|
|
349
|
+
inputType: "deleteContentBackward"
|
|
350
|
+
}));
|
|
351
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
352
|
+
} else if (el.isContentEditable) document.execCommand("delete", false);
|
|
353
|
+
}
|
|
354
|
+
function pageFrame() {
|
|
355
|
+
return {
|
|
356
|
+
id: frameId,
|
|
357
|
+
loaderId: "icdp-loader",
|
|
358
|
+
domainAndRegistry: "",
|
|
359
|
+
mimeType: document.contentType || "text/html",
|
|
360
|
+
securityOrigin: location.origin,
|
|
361
|
+
secureContextType: window.isSecureContext ? "Secure" : "InsecureScheme",
|
|
362
|
+
crossOriginIsolatedContextType: "NotIsolated",
|
|
363
|
+
gatedAPIFeatures: [],
|
|
364
|
+
url: location.href
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
function emulateNetworkConditionsByRule(params = {}) {
|
|
368
|
+
return { ruleIds: (params.matchedNetworkConditions ?? []).map((_, index) => `icdp-rule-${index}`) };
|
|
369
|
+
}
|
|
370
|
+
function addScriptToEvaluateOnNewDocument() {
|
|
371
|
+
return { identifier: `icdp-script-${nextScriptIdentifier++}` };
|
|
372
|
+
}
|
|
373
|
+
function getFrameTree() {
|
|
374
|
+
return { frameTree: { frame: pageFrame() } };
|
|
375
|
+
}
|
|
376
|
+
function getResourceTree() {
|
|
377
|
+
return { frameTree: {
|
|
378
|
+
frame: pageFrame(),
|
|
379
|
+
resources: []
|
|
380
|
+
} };
|
|
381
|
+
}
|
|
382
|
+
function getStorageKey() {
|
|
383
|
+
return { storageKey: location.origin };
|
|
384
|
+
}
|
|
385
|
+
function axOptions() {
|
|
386
|
+
return {
|
|
387
|
+
document,
|
|
388
|
+
frameId,
|
|
389
|
+
registry
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
/** Our DOM domain unifies nodeId with backendNodeId, so either resolves here. */
|
|
393
|
+
function axTargetBackendId(params) {
|
|
394
|
+
const id = Number(params.backendNodeId ?? params.nodeId);
|
|
395
|
+
return Number.isFinite(id) && id > 0 ? id : void 0;
|
|
396
|
+
}
|
|
397
|
+
/** Strict target resolution with Chromium's AssertNode error messages. */
|
|
398
|
+
function assertAXTarget(params) {
|
|
399
|
+
if (params.nodeId == null && params.backendNodeId == null && params.objectId == null) throw new Error("Either nodeId, backendNodeId or objectId must be specified");
|
|
400
|
+
if (params.nodeId != null) {
|
|
401
|
+
const id = Number(params.nodeId);
|
|
402
|
+
if (registry.nodeForBackendId(id)) return id;
|
|
403
|
+
throw new Error("Could not find node with given id");
|
|
404
|
+
}
|
|
405
|
+
if (params.backendNodeId != null) {
|
|
406
|
+
const id = Number(params.backendNodeId);
|
|
407
|
+
if (registry.nodeForBackendId(id)) return id;
|
|
408
|
+
throw new Error("No node found for given backend id");
|
|
409
|
+
}
|
|
410
|
+
const raw = String(params.objectId);
|
|
411
|
+
if (raw.startsWith("backend:")) {
|
|
412
|
+
const id = Number(raw.slice(8));
|
|
413
|
+
if (registry.nodeForBackendId(id)) return id;
|
|
414
|
+
}
|
|
415
|
+
throw new Error("Invalid remote object id");
|
|
416
|
+
}
|
|
417
|
+
function getFullAccessibilityTree(params = {}) {
|
|
418
|
+
return getFullAXTree(axOptions(), params.depth);
|
|
419
|
+
}
|
|
420
|
+
function getPartialAccessibilityTree(params = {}) {
|
|
421
|
+
return getPartialAXTree(axOptions(), axTargetBackendId(params), params.fetchRelatives ?? true);
|
|
422
|
+
}
|
|
423
|
+
function getRootAccessibilityNode() {
|
|
424
|
+
return getRootAXNode(axOptions());
|
|
425
|
+
}
|
|
426
|
+
function getChildAccessibilityNodes(params) {
|
|
427
|
+
return getChildAXNodes(axOptions(), String(params.id));
|
|
428
|
+
}
|
|
429
|
+
function getAccessibilityNodeAndAncestors(params = {}) {
|
|
430
|
+
const target = axTargetBackendId(params);
|
|
431
|
+
if (target == null) throw new Error("getAXNodeAndAncestors requires a nodeId or backendNodeId");
|
|
432
|
+
return getAXNodeAndAncestors(axOptions(), target);
|
|
433
|
+
}
|
|
434
|
+
function queryAccessibilityTree(params = {}) {
|
|
435
|
+
return queryAXTree(axOptions(), {
|
|
436
|
+
target: assertAXTarget(params),
|
|
437
|
+
accessibleName: params.accessibleName,
|
|
438
|
+
role: params.role
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
function getDocument(params = {}) {
|
|
442
|
+
return { root: domNode(document, Number(params.depth ?? 1)) };
|
|
443
|
+
}
|
|
444
|
+
function queryRoot(nodeId) {
|
|
445
|
+
if (nodeId == null || Number(nodeId) === 0) return document;
|
|
446
|
+
const node = registry.nodeForBackendId(Number(nodeId));
|
|
447
|
+
if (node instanceof Document) return node;
|
|
448
|
+
if (node instanceof Element) return node;
|
|
449
|
+
return node?.parentElement ?? document;
|
|
450
|
+
}
|
|
451
|
+
function querySelector(params) {
|
|
452
|
+
const element = queryRoot(params.nodeId).querySelector(params.selector);
|
|
453
|
+
return { nodeId: element ? registry.backendIdFor(element) : 0 };
|
|
454
|
+
}
|
|
455
|
+
function querySelectorAll(params) {
|
|
456
|
+
const root = queryRoot(params.nodeId);
|
|
457
|
+
return { nodeIds: Array.from(root.querySelectorAll(params.selector)).map((element) => registry.backendIdFor(element)) };
|
|
458
|
+
}
|
|
459
|
+
function matchingSearchNodes(query) {
|
|
460
|
+
try {
|
|
461
|
+
return Array.from(document.querySelectorAll(query)).map((element) => registry.backendIdFor(element));
|
|
462
|
+
} catch {}
|
|
463
|
+
const text = query.toLowerCase();
|
|
464
|
+
return Array.from(document.querySelectorAll("*")).filter((element) => (element.textContent || "").toLowerCase().includes(text)).map((element) => registry.backendIdFor(element));
|
|
465
|
+
}
|
|
466
|
+
function performSearch(params) {
|
|
467
|
+
const searchId = `icdp-search-${nextSearchId++}`;
|
|
468
|
+
const nodes = matchingSearchNodes(String(params.query || ""));
|
|
469
|
+
searchResults.set(searchId, nodes);
|
|
470
|
+
return {
|
|
471
|
+
searchId,
|
|
472
|
+
resultCount: nodes.length
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
function getSearchResults(params) {
|
|
476
|
+
return { nodeIds: (searchResults.get(params.searchId) ?? []).slice(params.fromIndex, params.toIndex) };
|
|
477
|
+
}
|
|
478
|
+
function discardSearchResults(params) {
|
|
479
|
+
searchResults.delete(params.searchId);
|
|
480
|
+
return {};
|
|
481
|
+
}
|
|
482
|
+
function getAttributes(params) {
|
|
483
|
+
const node = registry.nodeForBackendId(Number(params.nodeId));
|
|
484
|
+
return { attributes: node instanceof Element ? attributesFor(node) : [] };
|
|
485
|
+
}
|
|
486
|
+
function getOuterHTML(params) {
|
|
487
|
+
const node = registry.nodeForBackendId(Number(params.backendNodeId ?? params.nodeId));
|
|
488
|
+
if (node instanceof Element) return { outerHTML: node.outerHTML };
|
|
489
|
+
if (node instanceof Document) return { outerHTML: node.documentElement.outerHTML };
|
|
490
|
+
return { outerHTML: "" };
|
|
491
|
+
}
|
|
492
|
+
function focusNode(params) {
|
|
493
|
+
const element = elementForBackendId(Number(params.backendNodeId ?? params.nodeId));
|
|
494
|
+
if (element instanceof HTMLElement) element.focus();
|
|
495
|
+
return {};
|
|
496
|
+
}
|
|
497
|
+
function scrollIntoViewIfNeeded(params) {
|
|
498
|
+
elementForBackendId(Number(params.backendNodeId ?? params.nodeId)).scrollIntoView({
|
|
499
|
+
block: "center",
|
|
500
|
+
inline: "center"
|
|
501
|
+
});
|
|
502
|
+
return {};
|
|
503
|
+
}
|
|
504
|
+
function requestChildNodes(params) {
|
|
505
|
+
const node = registry.nodeForBackendId(Number(params.nodeId));
|
|
506
|
+
if (!node) throw new Error(`No element for backendDOMNodeId=${params.nodeId}`);
|
|
507
|
+
sendToHost({
|
|
508
|
+
method: "DOM.setChildNodes",
|
|
509
|
+
params: {
|
|
510
|
+
parentId: Number(params.nodeId),
|
|
511
|
+
nodes: childNodesFor(node, Number(params.depth ?? 1))
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
return {};
|
|
515
|
+
}
|
|
516
|
+
function describeDomNode(params = {}) {
|
|
517
|
+
return describeNode(Number(params.backendNodeId ?? params.nodeId ?? 1));
|
|
518
|
+
}
|
|
519
|
+
function resolveNode(params = {}) {
|
|
520
|
+
return { object: {
|
|
521
|
+
objectId: `backend:${params.backendNodeId ?? params.nodeId}`,
|
|
522
|
+
type: "object",
|
|
523
|
+
className: "Element"
|
|
524
|
+
} };
|
|
525
|
+
}
|
|
526
|
+
function pushNodesByBackendIdsToFrontend(params = {}) {
|
|
527
|
+
return { nodeIds: (params.backendNodeIds ?? []).map((id) => id) };
|
|
528
|
+
}
|
|
529
|
+
function getBoxModel(params) {
|
|
530
|
+
return boxModel(Number(params.backendNodeId ?? params.nodeId));
|
|
531
|
+
}
|
|
532
|
+
function getContentQuads(params) {
|
|
533
|
+
return contentQuads(Number(params.backendNodeId ?? params.nodeId));
|
|
534
|
+
}
|
|
535
|
+
function getComputedStyleForNode(params) {
|
|
536
|
+
const style = getComputedStyle(elementForBackendId(Number(params.nodeId)));
|
|
537
|
+
return { computedStyle: Array.from(style).map((name) => ({
|
|
538
|
+
name,
|
|
539
|
+
value: style.getPropertyValue(name)
|
|
540
|
+
})) };
|
|
541
|
+
}
|
|
542
|
+
function mouseEvent(type, params) {
|
|
543
|
+
return new MouseEvent(type, {
|
|
544
|
+
bubbles: true,
|
|
545
|
+
cancelable: true,
|
|
546
|
+
button: params.button === "right" ? 2 : params.button === "middle" ? 1 : 0,
|
|
547
|
+
buttons: Number(params.buttons ?? 0),
|
|
548
|
+
clientX: Number(params.x ?? 0),
|
|
549
|
+
clientY: Number(params.y ?? 0)
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
function wheelEvent(params) {
|
|
553
|
+
return new WheelEvent("wheel", {
|
|
554
|
+
bubbles: true,
|
|
555
|
+
cancelable: true,
|
|
556
|
+
clientX: Number(params.x ?? 0),
|
|
557
|
+
clientY: Number(params.y ?? 0),
|
|
558
|
+
deltaX: Number(params.deltaX ?? 0),
|
|
559
|
+
deltaY: Number(params.deltaY ?? 0)
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
function scrollableAncestor(el) {
|
|
563
|
+
for (let current = el; current; current = current.parentElement) {
|
|
564
|
+
const style = getComputedStyle(current);
|
|
565
|
+
if (/(auto|scroll)/.test(`${style.overflow}${style.overflowX}${style.overflowY}`)) return current;
|
|
566
|
+
}
|
|
567
|
+
return document.scrollingElement;
|
|
568
|
+
}
|
|
569
|
+
function dispatchMouseEvent(params) {
|
|
570
|
+
const target = document.elementFromPoint(Number(params.x ?? 0), Number(params.y ?? 0)) || document.documentElement;
|
|
571
|
+
if (!(target instanceof Element)) return {};
|
|
572
|
+
if (params.type === "mouseMoved") {
|
|
573
|
+
if (hoveredElement !== target) {
|
|
574
|
+
hoveredElement?.dispatchEvent(mouseEvent("mouseout", params));
|
|
575
|
+
target.dispatchEvent(mouseEvent("mouseover", params));
|
|
576
|
+
target.dispatchEvent(mouseEvent("mouseenter", params));
|
|
577
|
+
hoveredElement = target;
|
|
578
|
+
}
|
|
579
|
+
target.dispatchEvent(mouseEvent("mousemove", params));
|
|
580
|
+
} else if (params.type === "mousePressed") {
|
|
581
|
+
pressedElement = target;
|
|
582
|
+
target.dispatchEvent(mouseEvent("mousedown", params));
|
|
583
|
+
} else if (params.type === "mouseReleased") {
|
|
584
|
+
target.dispatchEvent(mouseEvent("mouseup", params));
|
|
585
|
+
if (pressedElement === target) {
|
|
586
|
+
target.click();
|
|
587
|
+
const now = Date.now();
|
|
588
|
+
if (Number(params.clickCount ?? 1) > 1 || lastClickElement === target && now - lastClickTime < 500) target.dispatchEvent(mouseEvent("dblclick", params));
|
|
589
|
+
lastClickElement = target;
|
|
590
|
+
lastClickTime = now;
|
|
591
|
+
}
|
|
592
|
+
pressedElement = null;
|
|
593
|
+
} else if (params.type === "mouseWheel") {
|
|
594
|
+
target.dispatchEvent(wheelEvent(params));
|
|
595
|
+
scrollableAncestor(target)?.scrollBy(Number(params.deltaX ?? 0), Number(params.deltaY ?? 0));
|
|
596
|
+
}
|
|
597
|
+
return {};
|
|
598
|
+
}
|
|
599
|
+
function dispatchKeyEvent(params) {
|
|
600
|
+
const key = String(params.key || params.code || params.text || "");
|
|
601
|
+
const target = document.activeElement || document.body;
|
|
602
|
+
if (params.type === "keyDown" || params.type === "rawKeyDown") {
|
|
603
|
+
target?.dispatchEvent(new KeyboardEvent("keydown", {
|
|
604
|
+
bubbles: true,
|
|
605
|
+
cancelable: true,
|
|
606
|
+
key
|
|
607
|
+
}));
|
|
608
|
+
if (key === "Backspace") deleteBackward();
|
|
609
|
+
} else if (params.type === "keyUp") target?.dispatchEvent(new KeyboardEvent("keyup", {
|
|
610
|
+
bubbles: true,
|
|
611
|
+
cancelable: true,
|
|
612
|
+
key
|
|
613
|
+
}));
|
|
614
|
+
if (typeof params.text === "string" && params.text) insertText(params.text);
|
|
615
|
+
return {};
|
|
616
|
+
}
|
|
617
|
+
function inputInsertText(params) {
|
|
618
|
+
if (typeof params.text === "string" && params.text) insertText(params.text);
|
|
619
|
+
return {};
|
|
620
|
+
}
|
|
621
|
+
function navigate(params) {
|
|
622
|
+
const next = new URL(String(params.url || "/"), location.href);
|
|
623
|
+
if (next.origin !== location.origin) throw new Error("Navigation outside the embedded app's origin is not allowed");
|
|
624
|
+
location.href = next.href;
|
|
625
|
+
return { frameId };
|
|
626
|
+
}
|
|
627
|
+
async function evaluate(params) {
|
|
628
|
+
const indirectEval = globalThis.eval;
|
|
629
|
+
const value = indirectEval(String(params.expression || ""));
|
|
630
|
+
return { result: runtimeValue(params.awaitPromise && value instanceof Promise ? await value : value) };
|
|
631
|
+
}
|
|
632
|
+
async function callFunctionOn(params) {
|
|
633
|
+
const id = String(params.objectId || "").startsWith("backend:") ? Number(String(params.objectId).slice(8)) : NaN;
|
|
634
|
+
const target = Number.isFinite(id) ? elementForBackendId(id) : window;
|
|
635
|
+
const indirectEval = globalThis.eval;
|
|
636
|
+
const value = indirectEval(`(${params.functionDeclaration})`).call(target, ...(params.arguments || []).map((arg) => {
|
|
637
|
+
if (arg.objectId?.startsWith("backend:")) return elementForBackendId(Number(arg.objectId.slice(8)));
|
|
638
|
+
return arg.value;
|
|
639
|
+
}));
|
|
640
|
+
return { result: runtimeValue(params.awaitPromise && value instanceof Promise ? await value : value) };
|
|
641
|
+
}
|
|
642
|
+
cdp.register("Accessibility", {
|
|
643
|
+
disable: noop,
|
|
644
|
+
enable: noop,
|
|
645
|
+
getFullAXTree: getFullAccessibilityTree,
|
|
646
|
+
getPartialAXTree: getPartialAccessibilityTree,
|
|
647
|
+
getRootAXNode: getRootAccessibilityNode,
|
|
648
|
+
getChildAXNodes: getChildAccessibilityNodes,
|
|
649
|
+
getAXNodeAndAncestors: getAccessibilityNodeAndAncestors,
|
|
650
|
+
queryAXTree: queryAccessibilityTree
|
|
651
|
+
});
|
|
652
|
+
cdp.register("Animation", { enable: noop });
|
|
653
|
+
cdp.register("Autofill", { setAddresses: noop });
|
|
654
|
+
cdp.register("CSS", {
|
|
655
|
+
disable: noop,
|
|
656
|
+
enable: noop,
|
|
657
|
+
getComputedStyleForNode
|
|
658
|
+
});
|
|
659
|
+
cdp.register("DOM", {
|
|
660
|
+
discardSearchResults,
|
|
661
|
+
describeNode: describeDomNode,
|
|
662
|
+
enable: noop,
|
|
663
|
+
focus: focusNode,
|
|
664
|
+
getAttributes,
|
|
665
|
+
getBoxModel,
|
|
666
|
+
getContentQuads,
|
|
667
|
+
getDocument,
|
|
668
|
+
getOuterHTML,
|
|
669
|
+
getSearchResults,
|
|
670
|
+
performSearch,
|
|
671
|
+
pushNodesByBackendIdsToFrontend,
|
|
672
|
+
querySelector,
|
|
673
|
+
querySelectorAll,
|
|
674
|
+
requestChildNodes,
|
|
675
|
+
resolveNode,
|
|
676
|
+
scrollIntoViewIfNeeded
|
|
677
|
+
});
|
|
678
|
+
cdp.register("Input", {
|
|
679
|
+
dispatchKeyEvent,
|
|
680
|
+
dispatchMouseEvent,
|
|
681
|
+
insertText: inputInsertText
|
|
682
|
+
});
|
|
683
|
+
cdp.register("Network", {
|
|
684
|
+
emulateNetworkConditionsByRule,
|
|
685
|
+
overrideNetworkState: noop,
|
|
686
|
+
setBlockedURLs: noop
|
|
687
|
+
});
|
|
688
|
+
cdp.register("Page", {
|
|
689
|
+
addScriptToEvaluateOnNewDocument,
|
|
690
|
+
getFrameTree,
|
|
691
|
+
getResourceTree,
|
|
692
|
+
navigate
|
|
693
|
+
});
|
|
694
|
+
cdp.register("Runtime", {
|
|
695
|
+
addBinding: noop,
|
|
696
|
+
callFunctionOn,
|
|
697
|
+
enable: enableRuntime,
|
|
698
|
+
evaluate,
|
|
699
|
+
runIfWaitingForDebugger: noop
|
|
700
|
+
});
|
|
701
|
+
cdp.register("Storage", { getStorageKey });
|
|
702
|
+
const ANNOUNCE_RETRIES = 10;
|
|
703
|
+
const ANNOUNCE_INTERVAL_MS = 300;
|
|
704
|
+
let started = false;
|
|
705
|
+
function parentAllowed(origin, allowed) {
|
|
706
|
+
return allowed === "*" || allowed.includes(origin);
|
|
707
|
+
}
|
|
708
|
+
async function handleCommand(raw) {
|
|
709
|
+
const request = JSON.parse(raw);
|
|
710
|
+
if (request.id != null) outboundMethods.set(request.id, request.method);
|
|
711
|
+
try {
|
|
712
|
+
await chobitsu.sendRawMessage(raw);
|
|
713
|
+
} catch (error) {
|
|
714
|
+
if (request.id != null) outboundMethods.delete(request.id);
|
|
715
|
+
sendToHost({
|
|
716
|
+
id: request.id,
|
|
717
|
+
error: {
|
|
718
|
+
code: CDP_SERVER_ERROR,
|
|
719
|
+
message: error instanceof Error ? error.message : String(error)
|
|
720
|
+
}
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
function adoptPort(next) {
|
|
725
|
+
port?.close();
|
|
726
|
+
port = next;
|
|
727
|
+
next.onmessage = (event) => {
|
|
728
|
+
handleCommand(String(event.data));
|
|
729
|
+
};
|
|
730
|
+
sendToHost({
|
|
731
|
+
method: "Page.frameNavigated",
|
|
732
|
+
params: { frame: pageFrame() }
|
|
733
|
+
});
|
|
734
|
+
sendToHost({
|
|
735
|
+
method: "Page.domContentEventFired",
|
|
736
|
+
params: { timestamp: performance.now() / 1e3 }
|
|
737
|
+
});
|
|
738
|
+
sendToHost({
|
|
739
|
+
method: "Page.loadEventFired",
|
|
740
|
+
params: { timestamp: performance.now() / 1e3 }
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
function announce(allowed) {
|
|
744
|
+
const hello = {
|
|
745
|
+
icdp: "hello",
|
|
746
|
+
v: 1,
|
|
747
|
+
title: document.title || location.href,
|
|
748
|
+
url: location.href
|
|
749
|
+
};
|
|
750
|
+
const targetOrigins = allowed === "*" ? ["*"] : allowed;
|
|
751
|
+
for (const origin of targetOrigins) try {
|
|
752
|
+
window.parent.postMessage(hello, origin);
|
|
753
|
+
} catch {}
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Boot the Frame Agent. No-op when the page is not embedded. The agent stays
|
|
757
|
+
* dormant (announces, but never adopts a channel) unless a parent on the
|
|
758
|
+
* allowlist answers with a welcome.
|
|
759
|
+
*/
|
|
760
|
+
function startFrameAgent(options) {
|
|
761
|
+
if (started || window.parent === window) return;
|
|
762
|
+
started = true;
|
|
763
|
+
const allowed = options.allowedParents;
|
|
764
|
+
window.addEventListener("message", (event) => {
|
|
765
|
+
if (event.source !== window.parent || !isHandshakeMessage(event.data)) return;
|
|
766
|
+
if (!parentAllowed(event.origin, allowed)) return;
|
|
767
|
+
if (event.data.icdp === "probe") announce(allowed);
|
|
768
|
+
else if (event.data.icdp === "welcome" && event.ports[0]) adoptPort(event.ports[0]);
|
|
769
|
+
});
|
|
770
|
+
installConsoleBridge();
|
|
771
|
+
announce(allowed);
|
|
772
|
+
let attempts = 0;
|
|
773
|
+
const retry = window.setInterval(() => {
|
|
774
|
+
if (port || ++attempts >= ANNOUNCE_RETRIES) {
|
|
775
|
+
window.clearInterval(retry);
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
announce(allowed);
|
|
779
|
+
}, ANNOUNCE_INTERVAL_MS);
|
|
780
|
+
}
|
|
781
|
+
//#endregion
|
|
782
|
+
export { startFrameAgent };
|