@olimsaidov/icdp 0.2.1 → 0.3.1
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/README.md +4 -117
- package/dist/frame/index.mjs +24350 -29
- package/dist/host/index.d.mts +1 -1
- package/dist/protocol-CZthc7CO.d.mts +121 -0
- package/dist/protocol.d.mts +1 -120
- package/dist/relay/core.d.mts +1 -1
- package/package.json +11 -1
- package/src/frame/index.ts +43 -24
- package/dist/frame/ax-tree.mjs +0 -2186
package/dist/host/index.d.mts
CHANGED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import Protocol from "devtools-protocol";
|
|
2
|
+
|
|
3
|
+
//#region src/protocol.d.ts
|
|
4
|
+
declare const PROTOCOL_VERSION = 1;
|
|
5
|
+
type CdpId = Protocol.integer | string;
|
|
6
|
+
/** A raw CDP message: command, response, or event. */
|
|
7
|
+
type CdpMessage = {
|
|
8
|
+
id?: CdpId;
|
|
9
|
+
method?: string;
|
|
10
|
+
params?: Record<string, unknown>;
|
|
11
|
+
sessionId?: string;
|
|
12
|
+
result?: unknown;
|
|
13
|
+
error?: CdpError;
|
|
14
|
+
};
|
|
15
|
+
type CdpError = {
|
|
16
|
+
code: number;
|
|
17
|
+
message: string;
|
|
18
|
+
};
|
|
19
|
+
declare const CDP_SERVER_ERROR = -32000;
|
|
20
|
+
declare const CDP_METHOD_NOT_FOUND = -32601;
|
|
21
|
+
/** Metadata a Frame Agent reports about its document. */
|
|
22
|
+
type FrameInfo = {
|
|
23
|
+
title: string;
|
|
24
|
+
url: string;
|
|
25
|
+
};
|
|
26
|
+
/** Target metadata as the Host reports it to the Relay. */
|
|
27
|
+
type TargetSummary = FrameInfo & {
|
|
28
|
+
targetId: string;
|
|
29
|
+
};
|
|
30
|
+
/** Sent by the Frame Agent to window.parent when it boots (and on probe). */
|
|
31
|
+
type HelloMessage = {
|
|
32
|
+
icdp: "hello";
|
|
33
|
+
v: number;
|
|
34
|
+
} & FrameInfo;
|
|
35
|
+
/** Sent by the Host to an iframe it doesn't yet have a channel for. */
|
|
36
|
+
type ProbeMessage = {
|
|
37
|
+
icdp: "probe";
|
|
38
|
+
v: number;
|
|
39
|
+
};
|
|
40
|
+
/** Sent by the Host in reply to hello, transferring a MessagePort. */
|
|
41
|
+
type WelcomeMessage = {
|
|
42
|
+
icdp: "welcome";
|
|
43
|
+
v: number;
|
|
44
|
+
};
|
|
45
|
+
type HandshakeMessage = HelloMessage | ProbeMessage | WelcomeMessage;
|
|
46
|
+
declare function isHandshakeMessage(data: unknown): data is HandshakeMessage;
|
|
47
|
+
/** Host -> Relay: announces itself and its current targets. New-wins: the Relay drops any previous Host. */
|
|
48
|
+
type BridgeReady = {
|
|
49
|
+
kind: "ready";
|
|
50
|
+
v: number;
|
|
51
|
+
targets: TargetSummary[];
|
|
52
|
+
/** Browser-level methods (e.g. "Target.createTarget") the Host handles itself.
|
|
53
|
+
* The Relay forwards these as a BridgeBrowserRequest instead of using its
|
|
54
|
+
* built-in default; omitted/empty means the Relay keeps its defaults. */
|
|
55
|
+
handles?: string[];
|
|
56
|
+
};
|
|
57
|
+
/** Host -> Relay: a Pairing appeared. */
|
|
58
|
+
type BridgeTargetCreated = {
|
|
59
|
+
kind: "targetCreated";
|
|
60
|
+
target: TargetSummary;
|
|
61
|
+
};
|
|
62
|
+
/** Host -> Relay: a Pairing was destroyed by the Host. */
|
|
63
|
+
type BridgeTargetDestroyed = {
|
|
64
|
+
kind: "targetDestroyed";
|
|
65
|
+
targetId: string;
|
|
66
|
+
};
|
|
67
|
+
/** Host -> Relay: a Target's document changed (reload / navigation under a stable targetId). */
|
|
68
|
+
type BridgeTargetInfoChanged = {
|
|
69
|
+
kind: "targetInfoChanged";
|
|
70
|
+
target: TargetSummary;
|
|
71
|
+
};
|
|
72
|
+
/** Relay -> Host: a Client command routed to one session. */
|
|
73
|
+
type BridgeCommand = {
|
|
74
|
+
kind: "command";
|
|
75
|
+
sessionId: string;
|
|
76
|
+
targetId: string;
|
|
77
|
+
id: number;
|
|
78
|
+
method: string;
|
|
79
|
+
params: Record<string, unknown>;
|
|
80
|
+
};
|
|
81
|
+
/** Host -> Relay: the response to a BridgeCommand. */
|
|
82
|
+
type BridgeResponse = {
|
|
83
|
+
kind: "response";
|
|
84
|
+
sessionId: string;
|
|
85
|
+
id: number;
|
|
86
|
+
result?: unknown;
|
|
87
|
+
error?: CdpError;
|
|
88
|
+
};
|
|
89
|
+
/** Host -> Relay: a CDP event from a Target; the Relay fans it out to every session attached to it. */
|
|
90
|
+
type BridgeEvent = {
|
|
91
|
+
kind: "event";
|
|
92
|
+
targetId: string;
|
|
93
|
+
method: string;
|
|
94
|
+
params: Record<string, unknown>;
|
|
95
|
+
};
|
|
96
|
+
/** Relay -> Host: a session detached (Client disconnected or detached explicitly). */
|
|
97
|
+
type BridgeDetached = {
|
|
98
|
+
kind: "detached";
|
|
99
|
+
sessionId: string;
|
|
100
|
+
targetId: string;
|
|
101
|
+
};
|
|
102
|
+
/** Relay -> Host: a browser-level method the Host advertised it handles
|
|
103
|
+
* (e.g. Target.createTarget / Target.closeTarget). Not session-scoped. */
|
|
104
|
+
type BridgeBrowserRequest = {
|
|
105
|
+
kind: "browserRequest";
|
|
106
|
+
id: number;
|
|
107
|
+
method: string;
|
|
108
|
+
params: Record<string, unknown>;
|
|
109
|
+
};
|
|
110
|
+
/** Host -> Relay: the response to a BridgeBrowserRequest. */
|
|
111
|
+
type BridgeBrowserResult = {
|
|
112
|
+
kind: "browserResult";
|
|
113
|
+
id: number;
|
|
114
|
+
result?: unknown;
|
|
115
|
+
error?: CdpError;
|
|
116
|
+
};
|
|
117
|
+
type HostToRelayMessage = BridgeReady | BridgeTargetCreated | BridgeTargetDestroyed | BridgeTargetInfoChanged | BridgeResponse | BridgeEvent | BridgeBrowserResult;
|
|
118
|
+
type RelayToHostMessage = BridgeCommand | BridgeDetached | BridgeBrowserRequest;
|
|
119
|
+
declare function parseJson<T>(raw: string | Buffer | ArrayBuffer | Uint8Array): T | null;
|
|
120
|
+
//#endregion
|
|
121
|
+
export { TargetSummary as C, parseJson as E, RelayToHostMessage as S, isHandshakeMessage as T, HandshakeMessage as _, BridgeEvent as a, PROTOCOL_VERSION as b, BridgeTargetCreated as c, CDP_METHOD_NOT_FOUND as d, CDP_SERVER_ERROR as f, FrameInfo as g, CdpMessage as h, BridgeDetached as i, BridgeTargetDestroyed as l, CdpId as m, BridgeBrowserResult as n, BridgeReady as o, CdpError as p, BridgeCommand as r, BridgeResponse as s, BridgeBrowserRequest as t, BridgeTargetInfoChanged as u, HelloMessage as v, WelcomeMessage as w, ProbeMessage as x, HostToRelayMessage as y };
|
package/dist/protocol.d.mts
CHANGED
|
@@ -1,121 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
//#region src/protocol.d.ts
|
|
4
|
-
declare const PROTOCOL_VERSION = 1;
|
|
5
|
-
type CdpId = Protocol.integer | string;
|
|
6
|
-
/** A raw CDP message: command, response, or event. */
|
|
7
|
-
type CdpMessage = {
|
|
8
|
-
id?: CdpId;
|
|
9
|
-
method?: string;
|
|
10
|
-
params?: Record<string, unknown>;
|
|
11
|
-
sessionId?: string;
|
|
12
|
-
result?: unknown;
|
|
13
|
-
error?: CdpError;
|
|
14
|
-
};
|
|
15
|
-
type CdpError = {
|
|
16
|
-
code: number;
|
|
17
|
-
message: string;
|
|
18
|
-
};
|
|
19
|
-
declare const CDP_SERVER_ERROR = -32000;
|
|
20
|
-
declare const CDP_METHOD_NOT_FOUND = -32601;
|
|
21
|
-
/** Metadata a Frame Agent reports about its document. */
|
|
22
|
-
type FrameInfo = {
|
|
23
|
-
title: string;
|
|
24
|
-
url: string;
|
|
25
|
-
};
|
|
26
|
-
/** Target metadata as the Host reports it to the Relay. */
|
|
27
|
-
type TargetSummary = FrameInfo & {
|
|
28
|
-
targetId: string;
|
|
29
|
-
};
|
|
30
|
-
/** Sent by the Frame Agent to window.parent when it boots (and on probe). */
|
|
31
|
-
type HelloMessage = {
|
|
32
|
-
icdp: "hello";
|
|
33
|
-
v: number;
|
|
34
|
-
} & FrameInfo;
|
|
35
|
-
/** Sent by the Host to an iframe it doesn't yet have a channel for. */
|
|
36
|
-
type ProbeMessage = {
|
|
37
|
-
icdp: "probe";
|
|
38
|
-
v: number;
|
|
39
|
-
};
|
|
40
|
-
/** Sent by the Host in reply to hello, transferring a MessagePort. */
|
|
41
|
-
type WelcomeMessage = {
|
|
42
|
-
icdp: "welcome";
|
|
43
|
-
v: number;
|
|
44
|
-
};
|
|
45
|
-
type HandshakeMessage = HelloMessage | ProbeMessage | WelcomeMessage;
|
|
46
|
-
declare function isHandshakeMessage(data: unknown): data is HandshakeMessage;
|
|
47
|
-
/** Host -> Relay: announces itself and its current targets. New-wins: the Relay drops any previous Host. */
|
|
48
|
-
type BridgeReady = {
|
|
49
|
-
kind: "ready";
|
|
50
|
-
v: number;
|
|
51
|
-
targets: TargetSummary[];
|
|
52
|
-
/** Browser-level methods (e.g. "Target.createTarget") the Host handles itself.
|
|
53
|
-
* The Relay forwards these as a BridgeBrowserRequest instead of using its
|
|
54
|
-
* built-in default; omitted/empty means the Relay keeps its defaults. */
|
|
55
|
-
handles?: string[];
|
|
56
|
-
};
|
|
57
|
-
/** Host -> Relay: a Pairing appeared. */
|
|
58
|
-
type BridgeTargetCreated = {
|
|
59
|
-
kind: "targetCreated";
|
|
60
|
-
target: TargetSummary;
|
|
61
|
-
};
|
|
62
|
-
/** Host -> Relay: a Pairing was destroyed by the Host. */
|
|
63
|
-
type BridgeTargetDestroyed = {
|
|
64
|
-
kind: "targetDestroyed";
|
|
65
|
-
targetId: string;
|
|
66
|
-
};
|
|
67
|
-
/** Host -> Relay: a Target's document changed (reload / navigation under a stable targetId). */
|
|
68
|
-
type BridgeTargetInfoChanged = {
|
|
69
|
-
kind: "targetInfoChanged";
|
|
70
|
-
target: TargetSummary;
|
|
71
|
-
};
|
|
72
|
-
/** Relay -> Host: a Client command routed to one session. */
|
|
73
|
-
type BridgeCommand = {
|
|
74
|
-
kind: "command";
|
|
75
|
-
sessionId: string;
|
|
76
|
-
targetId: string;
|
|
77
|
-
id: number;
|
|
78
|
-
method: string;
|
|
79
|
-
params: Record<string, unknown>;
|
|
80
|
-
};
|
|
81
|
-
/** Host -> Relay: the response to a BridgeCommand. */
|
|
82
|
-
type BridgeResponse = {
|
|
83
|
-
kind: "response";
|
|
84
|
-
sessionId: string;
|
|
85
|
-
id: number;
|
|
86
|
-
result?: unknown;
|
|
87
|
-
error?: CdpError;
|
|
88
|
-
};
|
|
89
|
-
/** Host -> Relay: a CDP event from a Target; the Relay fans it out to every session attached to it. */
|
|
90
|
-
type BridgeEvent = {
|
|
91
|
-
kind: "event";
|
|
92
|
-
targetId: string;
|
|
93
|
-
method: string;
|
|
94
|
-
params: Record<string, unknown>;
|
|
95
|
-
};
|
|
96
|
-
/** Relay -> Host: a session detached (Client disconnected or detached explicitly). */
|
|
97
|
-
type BridgeDetached = {
|
|
98
|
-
kind: "detached";
|
|
99
|
-
sessionId: string;
|
|
100
|
-
targetId: string;
|
|
101
|
-
};
|
|
102
|
-
/** Relay -> Host: a browser-level method the Host advertised it handles
|
|
103
|
-
* (e.g. Target.createTarget / Target.closeTarget). Not session-scoped. */
|
|
104
|
-
type BridgeBrowserRequest = {
|
|
105
|
-
kind: "browserRequest";
|
|
106
|
-
id: number;
|
|
107
|
-
method: string;
|
|
108
|
-
params: Record<string, unknown>;
|
|
109
|
-
};
|
|
110
|
-
/** Host -> Relay: the response to a BridgeBrowserRequest. */
|
|
111
|
-
type BridgeBrowserResult = {
|
|
112
|
-
kind: "browserResult";
|
|
113
|
-
id: number;
|
|
114
|
-
result?: unknown;
|
|
115
|
-
error?: CdpError;
|
|
116
|
-
};
|
|
117
|
-
type HostToRelayMessage = BridgeReady | BridgeTargetCreated | BridgeTargetDestroyed | BridgeTargetInfoChanged | BridgeResponse | BridgeEvent | BridgeBrowserResult;
|
|
118
|
-
type RelayToHostMessage = BridgeCommand | BridgeDetached | BridgeBrowserRequest;
|
|
119
|
-
declare function parseJson<T>(raw: string | Buffer | ArrayBuffer | Uint8Array): T | null;
|
|
120
|
-
//#endregion
|
|
1
|
+
import { C as TargetSummary, E as parseJson, S as RelayToHostMessage, T as isHandshakeMessage, _ as HandshakeMessage, a as BridgeEvent, b as PROTOCOL_VERSION, c as BridgeTargetCreated, d as CDP_METHOD_NOT_FOUND, f as CDP_SERVER_ERROR, g as FrameInfo, h as CdpMessage, i as BridgeDetached, l as BridgeTargetDestroyed, m as CdpId, n as BridgeBrowserResult, o as BridgeReady, p as CdpError, r as BridgeCommand, s as BridgeResponse, t as BridgeBrowserRequest, u as BridgeTargetInfoChanged, v as HelloMessage, w as WelcomeMessage, x as ProbeMessage, y as HostToRelayMessage } from "./protocol-CZthc7CO.mjs";
|
|
121
2
|
export { BridgeBrowserRequest, BridgeBrowserResult, BridgeCommand, BridgeDetached, BridgeEvent, BridgeReady, BridgeResponse, BridgeTargetCreated, BridgeTargetDestroyed, BridgeTargetInfoChanged, CDP_METHOD_NOT_FOUND, CDP_SERVER_ERROR, CdpError, CdpId, CdpMessage, FrameInfo, HandshakeMessage, HelloMessage, HostToRelayMessage, PROTOCOL_VERSION, ProbeMessage, RelayToHostMessage, TargetSummary, WelcomeMessage, isHandshakeMessage, parseJson };
|
package/dist/relay/core.d.mts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@olimsaidov/icdp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Chrome DevTools Protocol over an iframe boundary: drive and inspect embedded (including cross-origin) apps with CDP tools, without a real browser debugging session.",
|
|
5
5
|
"homepage": "https://github.com/olimsaidov/icdp#readme",
|
|
6
6
|
"bugs": "https://github.com/olimsaidov/icdp/issues",
|
|
@@ -45,6 +45,10 @@
|
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "tsdown",
|
|
47
47
|
"check": "tsc --noEmit && oxlint && oxfmt --check .",
|
|
48
|
+
"docs:build": "node docs/scripts/build-demo-frame.mjs && vitepress build docs",
|
|
49
|
+
"docs:demo-frame": "node docs/scripts/build-demo-frame.mjs",
|
|
50
|
+
"docs:dev": "node docs/scripts/build-demo-frame.mjs && vitepress dev docs",
|
|
51
|
+
"docs:preview": "vitepress preview docs",
|
|
48
52
|
"fmt": "oxfmt .",
|
|
49
53
|
"gen:conformance": "node scripts/gen-conformance.ts",
|
|
50
54
|
"playground": "node playground/server.ts",
|
|
@@ -60,15 +64,21 @@
|
|
|
60
64
|
"ws": "^8.21.0"
|
|
61
65
|
},
|
|
62
66
|
"devDependencies": {
|
|
67
|
+
"@olimsaidov/agent-browser-wasm": "^0.27.3",
|
|
63
68
|
"@types/aria-query": "^5.0.4",
|
|
64
69
|
"@types/node": "^24",
|
|
65
70
|
"@types/ws": "^8",
|
|
71
|
+
"@xterm/addon-fit": "^0.11.0",
|
|
72
|
+
"@xterm/xterm": "^6.0.0",
|
|
66
73
|
"jsdom": "^29.1.1",
|
|
74
|
+
"mermaid": "^11.15.0",
|
|
67
75
|
"oxfmt": "^0.54.0",
|
|
68
76
|
"oxlint": "^1.69.0",
|
|
69
77
|
"rolldown": "^1.1.1",
|
|
70
78
|
"tsdown": "^0.22.2",
|
|
71
79
|
"typescript": "^5",
|
|
80
|
+
"vitepress": "^1.6.4",
|
|
81
|
+
"vitepress-plugin-mermaid": "^2.0.17",
|
|
72
82
|
"vitest": "^4.1.8"
|
|
73
83
|
},
|
|
74
84
|
"engines": {
|
package/src/frame/index.ts
CHANGED
|
@@ -382,24 +382,6 @@ function wrapConsoleMethod(fn: ConsoleMethod, type: string): ConsoleMethod {
|
|
|
382
382
|
return wrapped;
|
|
383
383
|
}
|
|
384
384
|
|
|
385
|
-
function highlight(el: Element): void {
|
|
386
|
-
const rect = el.getBoundingClientRect();
|
|
387
|
-
const marker = document.createElement("div");
|
|
388
|
-
marker.style.cssText = [
|
|
389
|
-
"position:fixed",
|
|
390
|
-
`left:${rect.left}px`,
|
|
391
|
-
`top:${rect.top}px`,
|
|
392
|
-
`width:${rect.width}px`,
|
|
393
|
-
`height:${rect.height}px`,
|
|
394
|
-
"z-index:2147483647",
|
|
395
|
-
"pointer-events:none",
|
|
396
|
-
"outline:2px solid #005fb8",
|
|
397
|
-
"background:rgba(0,95,184,.08)",
|
|
398
|
-
].join(";");
|
|
399
|
-
document.documentElement.appendChild(marker);
|
|
400
|
-
setTimeout(() => marker.remove(), 500);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
385
|
function setNativeValue(el: HTMLInputElement | HTMLTextAreaElement, value: string): void {
|
|
404
386
|
const proto =
|
|
405
387
|
el instanceof HTMLInputElement ? HTMLInputElement.prototype : HTMLTextAreaElement.prototype;
|
|
@@ -414,7 +396,6 @@ function canSelectText(el: HTMLInputElement): boolean {
|
|
|
414
396
|
function insertText(text: string): void {
|
|
415
397
|
const el = document.activeElement as HTMLElement | null;
|
|
416
398
|
if (!el) return;
|
|
417
|
-
highlight(el);
|
|
418
399
|
if (el instanceof HTMLInputElement && !canSelectText(el)) {
|
|
419
400
|
setNativeValue(el, text);
|
|
420
401
|
el.dispatchEvent(
|
|
@@ -665,20 +646,45 @@ function getOuterHTML(params: CdpParams<"DOM.getOuterHTML">): Protocol.DOM.GetOu
|
|
|
665
646
|
|
|
666
647
|
function focusNode(params: CdpParams<"DOM.focus">): Record<string, never> {
|
|
667
648
|
const element = elementForBackendId(Number(params.backendNodeId ?? params.nodeId));
|
|
668
|
-
|
|
649
|
+
// preventScroll: a default focus() scrolls the element into view across every
|
|
650
|
+
// ancestor — including the embedding page across the iframe boundary, yanking
|
|
651
|
+
// the host. Revealing the element is scrollIntoViewIfNeeded's job (and it stays
|
|
652
|
+
// within the frame); focus must not move the parent.
|
|
653
|
+
if (element instanceof HTMLElement) element.focus({ preventScroll: true });
|
|
669
654
|
return {};
|
|
670
655
|
}
|
|
671
656
|
|
|
672
657
|
function scrollIntoViewIfNeeded(
|
|
673
658
|
params: CdpParams<"DOM.scrollIntoViewIfNeeded">,
|
|
674
659
|
): Record<string, never> {
|
|
675
|
-
elementForBackendId(Number(params.backendNodeId ?? params.nodeId))
|
|
676
|
-
block: "center",
|
|
677
|
-
inline: "center",
|
|
678
|
-
});
|
|
660
|
+
revealWithinFrame(elementForBackendId(Number(params.backendNodeId ?? params.nodeId)));
|
|
679
661
|
return {};
|
|
680
662
|
}
|
|
681
663
|
|
|
664
|
+
/**
|
|
665
|
+
* Bring an element into view by scrolling its own scroll containers and the
|
|
666
|
+
* frame's viewport — never `Element.scrollIntoView`, which cascades across the
|
|
667
|
+
* iframe boundary and yanks the embedding page to re-position the frame. Honours
|
|
668
|
+
* "IfNeeded": a no-op when the element is already on screen, minimal scroll
|
|
669
|
+
* otherwise.
|
|
670
|
+
*/
|
|
671
|
+
function revealWithinFrame(el: Element): void {
|
|
672
|
+
const container = scrollableAncestor(el);
|
|
673
|
+
if (container instanceof Element && container !== document.scrollingElement) {
|
|
674
|
+
const c = container.getBoundingClientRect();
|
|
675
|
+
const r = el.getBoundingClientRect();
|
|
676
|
+
if (r.top < c.top) container.scrollTop += r.top - c.top;
|
|
677
|
+
else if (r.bottom > c.bottom) container.scrollTop += r.bottom - c.bottom;
|
|
678
|
+
if (r.left < c.left) container.scrollLeft += r.left - c.left;
|
|
679
|
+
else if (r.right > c.right) container.scrollLeft += r.right - c.right;
|
|
680
|
+
}
|
|
681
|
+
// window.scrollBy scrolls this frame's own document, not the parent's.
|
|
682
|
+
const r = el.getBoundingClientRect();
|
|
683
|
+
const dy = r.top < 0 ? r.top : r.bottom > window.innerHeight ? r.bottom - window.innerHeight : 0;
|
|
684
|
+
const dx = r.left < 0 ? r.left : r.right > window.innerWidth ? r.right - window.innerWidth : 0;
|
|
685
|
+
if (dx || dy) window.scrollBy(dx, dy);
|
|
686
|
+
}
|
|
687
|
+
|
|
682
688
|
function requestChildNodes(params: CdpParams<"DOM.requestChildNodes">): Record<string, never> {
|
|
683
689
|
const node = registry.nodeForBackendId(Number(params.nodeId));
|
|
684
690
|
if (!node) throw new Error(`No element for backendDOMNodeId=${params.nodeId}`);
|
|
@@ -1022,6 +1028,19 @@ export function startFrameAgent(options: FrameAgentOptions): void {
|
|
|
1022
1028
|
if (started || window.parent === window) return;
|
|
1023
1029
|
started = true;
|
|
1024
1030
|
|
|
1031
|
+
// The embedded app shipped this agent to be automated, and automation must
|
|
1032
|
+
// never scroll the *host* page. Element.scrollIntoView cascades across the
|
|
1033
|
+
// iframe boundary — it scrolls every scrollable ancestor, including the
|
|
1034
|
+
// embedding document — and agent-browser evals it to position elements before
|
|
1035
|
+
// clicking. Replace it (and the legacy scrollIntoViewIfNeeded) with a
|
|
1036
|
+
// frame-local reveal so commands move the frame, never the page around it.
|
|
1037
|
+
const revealOnly = function (this: Element): void {
|
|
1038
|
+
revealWithinFrame(this);
|
|
1039
|
+
};
|
|
1040
|
+
Element.prototype.scrollIntoView = revealOnly as Element["scrollIntoView"];
|
|
1041
|
+
const proto = Element.prototype as Element & { scrollIntoViewIfNeeded?: () => void };
|
|
1042
|
+
if (typeof proto.scrollIntoViewIfNeeded === "function") proto.scrollIntoViewIfNeeded = revealOnly;
|
|
1043
|
+
|
|
1025
1044
|
const allowed = options.allowedParents;
|
|
1026
1045
|
|
|
1027
1046
|
window.addEventListener("message", (event) => {
|