@silbercue/chrome 0.2.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 +229 -0
- package/build/cache/a11y-tree.d.ts +252 -0
- package/build/cache/a11y-tree.js +1956 -0
- package/build/cache/index.d.ts +8 -0
- package/build/cache/index.js +4 -0
- package/build/cache/selector-cache.d.ts +47 -0
- package/build/cache/selector-cache.js +119 -0
- package/build/cache/session-defaults.d.ts +27 -0
- package/build/cache/session-defaults.js +130 -0
- package/build/cache/tab-state-cache.d.ts +39 -0
- package/build/cache/tab-state-cache.js +171 -0
- package/build/cdp/cdp-client.d.ts +25 -0
- package/build/cdp/cdp-client.js +146 -0
- package/build/cdp/chrome-launcher.d.ts +85 -0
- package/build/cdp/chrome-launcher.js +502 -0
- package/build/cdp/console-collector.d.ts +53 -0
- package/build/cdp/console-collector.js +147 -0
- package/build/cdp/debug.d.ts +1 -0
- package/build/cdp/debug.js +6 -0
- package/build/cdp/dialog-handler.d.ts +54 -0
- package/build/cdp/dialog-handler.js +129 -0
- package/build/cdp/dom-watcher.d.ts +45 -0
- package/build/cdp/dom-watcher.js +195 -0
- package/build/cdp/emulation.d.ts +12 -0
- package/build/cdp/emulation.js +17 -0
- package/build/cdp/index.d.ts +11 -0
- package/build/cdp/index.js +6 -0
- package/build/cdp/network-collector.d.ts +77 -0
- package/build/cdp/network-collector.js +257 -0
- package/build/cdp/protocol.d.ts +20 -0
- package/build/cdp/protocol.js +1 -0
- package/build/cdp/session-manager.d.ts +62 -0
- package/build/cdp/session-manager.js +205 -0
- package/build/cdp/settle.d.ts +16 -0
- package/build/cdp/settle.js +71 -0
- package/build/cli/license-commands.d.ts +19 -0
- package/build/cli/license-commands.js +199 -0
- package/build/cli/top-level-commands.d.ts +49 -0
- package/build/cli/top-level-commands.js +222 -0
- package/build/hooks/index.d.ts +2 -0
- package/build/hooks/index.js +1 -0
- package/build/hooks/pro-hooks.d.ts +126 -0
- package/build/hooks/pro-hooks.js +17 -0
- package/build/index.d.ts +4 -0
- package/build/index.js +86 -0
- package/build/license/free-tier-config.d.ts +14 -0
- package/build/license/free-tier-config.js +18 -0
- package/build/license/index.d.ts +4 -0
- package/build/license/index.js +2 -0
- package/build/license/license-status.d.ts +15 -0
- package/build/license/license-status.js +9 -0
- package/build/overlay/session-overlay.d.ts +22 -0
- package/build/overlay/session-overlay.js +372 -0
- package/build/plan/index.d.ts +7 -0
- package/build/plan/index.js +4 -0
- package/build/plan/plan-conditions.d.ts +12 -0
- package/build/plan/plan-conditions.js +242 -0
- package/build/plan/plan-executor.d.ts +49 -0
- package/build/plan/plan-executor.js +259 -0
- package/build/plan/plan-state-store.d.ts +24 -0
- package/build/plan/plan-state-store.js +43 -0
- package/build/plan/plan-variables.d.ts +16 -0
- package/build/plan/plan-variables.js +71 -0
- package/build/registry.d.ts +124 -0
- package/build/registry.js +884 -0
- package/build/server.d.ts +1 -0
- package/build/server.js +245 -0
- package/build/tools/click.d.ts +34 -0
- package/build/tools/click.js +293 -0
- package/build/tools/configure-session.d.ts +15 -0
- package/build/tools/configure-session.js +45 -0
- package/build/tools/console-logs.d.ts +18 -0
- package/build/tools/console-logs.js +44 -0
- package/build/tools/dom-snapshot.d.ts +13 -0
- package/build/tools/dom-snapshot.js +259 -0
- package/build/tools/element-utils.d.ts +23 -0
- package/build/tools/element-utils.js +133 -0
- package/build/tools/error-utils.d.ts +8 -0
- package/build/tools/error-utils.js +27 -0
- package/build/tools/evaluate.d.ts +34 -0
- package/build/tools/evaluate.js +217 -0
- package/build/tools/file-upload.d.ts +20 -0
- package/build/tools/file-upload.js +174 -0
- package/build/tools/fill-form.d.ts +39 -0
- package/build/tools/fill-form.js +256 -0
- package/build/tools/handle-dialog.d.ts +15 -0
- package/build/tools/handle-dialog.js +48 -0
- package/build/tools/index.d.ts +35 -0
- package/build/tools/index.js +18 -0
- package/build/tools/navigate.d.ts +18 -0
- package/build/tools/navigate.js +111 -0
- package/build/tools/network-monitor.d.ts +18 -0
- package/build/tools/network-monitor.js +66 -0
- package/build/tools/observe.d.ts +44 -0
- package/build/tools/observe.js +339 -0
- package/build/tools/press-key.d.ts +33 -0
- package/build/tools/press-key.js +155 -0
- package/build/tools/read-page.d.ts +22 -0
- package/build/tools/read-page.js +100 -0
- package/build/tools/run-plan.d.ts +205 -0
- package/build/tools/run-plan.js +215 -0
- package/build/tools/screenshot.d.ts +16 -0
- package/build/tools/screenshot.js +283 -0
- package/build/tools/scroll.d.ts +28 -0
- package/build/tools/scroll.js +143 -0
- package/build/tools/switch-tab.d.ts +26 -0
- package/build/tools/switch-tab.js +355 -0
- package/build/tools/tab-status.d.ts +7 -0
- package/build/tools/tab-status.js +50 -0
- package/build/tools/type.d.ts +31 -0
- package/build/tools/type.js +247 -0
- package/build/tools/virtual-desk.d.ts +7 -0
- package/build/tools/virtual-desk.js +108 -0
- package/build/tools/visual-constants.d.ts +3 -0
- package/build/tools/visual-constants.js +10 -0
- package/build/tools/wait-for.d.ts +26 -0
- package/build/tools/wait-for.js +323 -0
- package/build/transport/index.d.ts +3 -0
- package/build/transport/index.js +2 -0
- package/build/transport/pipe-transport.d.ts +18 -0
- package/build/transport/pipe-transport.js +63 -0
- package/build/transport/transport.d.ts +8 -0
- package/build/transport/transport.js +1 -0
- package/build/transport/websocket-transport.d.ts +22 -0
- package/build/transport/websocket-transport.js +200 -0
- package/build/types.d.ts +21 -0
- package/build/types.js +1 -0
- package/package.json +62 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function debug(message: string, ...args: unknown[]): void;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { CdpClient } from "./cdp-client.js";
|
|
2
|
+
export interface DialogEvent {
|
|
3
|
+
type: "alert" | "confirm" | "prompt" | "beforeunload";
|
|
4
|
+
message: string;
|
|
5
|
+
defaultPrompt?: string;
|
|
6
|
+
url: string;
|
|
7
|
+
}
|
|
8
|
+
export interface DialogHandlerConfig {
|
|
9
|
+
autoAccept: boolean;
|
|
10
|
+
promptText?: string;
|
|
11
|
+
timeoutMs: number;
|
|
12
|
+
}
|
|
13
|
+
export declare class DialogHandler {
|
|
14
|
+
private _cdpClient;
|
|
15
|
+
private _sessionId;
|
|
16
|
+
private _callback;
|
|
17
|
+
private _pendingNotifications;
|
|
18
|
+
private _handlerStack;
|
|
19
|
+
private _defaultTimeoutMs;
|
|
20
|
+
private _pendingTimers;
|
|
21
|
+
private _initialized;
|
|
22
|
+
constructor(cdpClient: CdpClient, sessionId: string, timeoutMs?: number);
|
|
23
|
+
/**
|
|
24
|
+
* Start listening for Page.javascriptDialogOpening events.
|
|
25
|
+
*/
|
|
26
|
+
init(): void;
|
|
27
|
+
/**
|
|
28
|
+
* Remove event listener and clear handler stack.
|
|
29
|
+
*/
|
|
30
|
+
detach(): void;
|
|
31
|
+
/**
|
|
32
|
+
* Re-initialize after reconnect or tab switch.
|
|
33
|
+
* Preserves pending notifications (tab-switch should not lose them).
|
|
34
|
+
*/
|
|
35
|
+
reinit(cdpClient: CdpClient, sessionId: string): void;
|
|
36
|
+
/**
|
|
37
|
+
* Push a handler onto the stack. The topmost handler is used for the next dialog.
|
|
38
|
+
*/
|
|
39
|
+
pushHandler(config: DialogHandlerConfig): void;
|
|
40
|
+
/**
|
|
41
|
+
* Remove and return the topmost handler from the stack.
|
|
42
|
+
*/
|
|
43
|
+
popHandler(): DialogHandlerConfig | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Return buffered dialog notifications and clear the buffer.
|
|
46
|
+
*/
|
|
47
|
+
consumeNotifications(): DialogEvent[];
|
|
48
|
+
/**
|
|
49
|
+
* Number of buffered notifications.
|
|
50
|
+
*/
|
|
51
|
+
get pendingCount(): number;
|
|
52
|
+
private _onDialogOpening;
|
|
53
|
+
private _handleDialog;
|
|
54
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { debug } from "./debug.js";
|
|
2
|
+
// --- DialogHandler ---
|
|
3
|
+
export class DialogHandler {
|
|
4
|
+
_cdpClient;
|
|
5
|
+
_sessionId;
|
|
6
|
+
_callback = null;
|
|
7
|
+
_pendingNotifications = [];
|
|
8
|
+
_handlerStack = [];
|
|
9
|
+
_defaultTimeoutMs = 3000;
|
|
10
|
+
_pendingTimers = new Set();
|
|
11
|
+
_initialized = false;
|
|
12
|
+
constructor(cdpClient, sessionId, timeoutMs) {
|
|
13
|
+
this._cdpClient = cdpClient;
|
|
14
|
+
this._sessionId = sessionId;
|
|
15
|
+
if (timeoutMs !== undefined) {
|
|
16
|
+
this._defaultTimeoutMs = timeoutMs;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Start listening for Page.javascriptDialogOpening events.
|
|
21
|
+
*/
|
|
22
|
+
init() {
|
|
23
|
+
if (this._initialized)
|
|
24
|
+
return;
|
|
25
|
+
this._initialized = true;
|
|
26
|
+
this._callback = (params) => {
|
|
27
|
+
this._onDialogOpening(params);
|
|
28
|
+
};
|
|
29
|
+
this._cdpClient.on("Page.javascriptDialogOpening", this._callback, this._sessionId);
|
|
30
|
+
debug("DialogHandler initialized on session %s", this._sessionId);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Remove event listener and clear handler stack.
|
|
34
|
+
*/
|
|
35
|
+
detach() {
|
|
36
|
+
this._initialized = false;
|
|
37
|
+
if (this._callback) {
|
|
38
|
+
this._cdpClient.off("Page.javascriptDialogOpening", this._callback);
|
|
39
|
+
this._callback = null;
|
|
40
|
+
}
|
|
41
|
+
// Clear any pending auto-dismiss timers
|
|
42
|
+
for (const timer of this._pendingTimers) {
|
|
43
|
+
clearTimeout(timer);
|
|
44
|
+
}
|
|
45
|
+
this._pendingTimers.clear();
|
|
46
|
+
this._handlerStack = [];
|
|
47
|
+
debug("DialogHandler detached");
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Re-initialize after reconnect or tab switch.
|
|
51
|
+
* Preserves pending notifications (tab-switch should not lose them).
|
|
52
|
+
*/
|
|
53
|
+
reinit(cdpClient, sessionId) {
|
|
54
|
+
this.detach();
|
|
55
|
+
this._cdpClient = cdpClient;
|
|
56
|
+
this._sessionId = sessionId;
|
|
57
|
+
this.init();
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Push a handler onto the stack. The topmost handler is used for the next dialog.
|
|
61
|
+
*/
|
|
62
|
+
pushHandler(config) {
|
|
63
|
+
this._handlerStack.push(config);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Remove and return the topmost handler from the stack.
|
|
67
|
+
*/
|
|
68
|
+
popHandler() {
|
|
69
|
+
return this._handlerStack.pop();
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Return buffered dialog notifications and clear the buffer.
|
|
73
|
+
*/
|
|
74
|
+
consumeNotifications() {
|
|
75
|
+
const copy = [...this._pendingNotifications];
|
|
76
|
+
this._pendingNotifications = [];
|
|
77
|
+
return copy;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Number of buffered notifications.
|
|
81
|
+
*/
|
|
82
|
+
get pendingCount() {
|
|
83
|
+
return this._pendingNotifications.length;
|
|
84
|
+
}
|
|
85
|
+
// --- Internal ---
|
|
86
|
+
_onDialogOpening(params) {
|
|
87
|
+
const p = params;
|
|
88
|
+
const event = {
|
|
89
|
+
type: p.type ?? "alert",
|
|
90
|
+
message: p.message ?? "",
|
|
91
|
+
defaultPrompt: p.defaultPrompt,
|
|
92
|
+
url: p.url ?? "",
|
|
93
|
+
};
|
|
94
|
+
// Buffer notification for next tool response
|
|
95
|
+
this._pendingNotifications.push(event);
|
|
96
|
+
// Check handler stack (topmost wins, pop after use — fire-and-forget)
|
|
97
|
+
const handler = this._handlerStack.pop();
|
|
98
|
+
if (handler) {
|
|
99
|
+
// Handler configured — respond immediately
|
|
100
|
+
const cdpParams = {
|
|
101
|
+
accept: handler.autoAccept,
|
|
102
|
+
};
|
|
103
|
+
// Only include promptText for prompt dialogs
|
|
104
|
+
if (event.type === "prompt" && handler.promptText !== undefined) {
|
|
105
|
+
cdpParams.promptText = handler.promptText;
|
|
106
|
+
}
|
|
107
|
+
this._handleDialog(cdpParams, event);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
// No handler configured — auto-dismiss after timeout
|
|
111
|
+
// beforeunload MUST always be accepted, otherwise navigation is blocked
|
|
112
|
+
const accept = event.type === "beforeunload";
|
|
113
|
+
const timer = setTimeout(() => {
|
|
114
|
+
this._pendingTimers.delete(timer);
|
|
115
|
+
this._handleDialog({ accept }, event);
|
|
116
|
+
debug("Dialog auto-dismissed after %dms: %s", this._defaultTimeoutMs, event.message);
|
|
117
|
+
}, this._defaultTimeoutMs);
|
|
118
|
+
this._pendingTimers.add(timer);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
_handleDialog(cdpParams, event) {
|
|
122
|
+
// CRITICAL: try/catch — dialog may already have been handled by other code
|
|
123
|
+
this._cdpClient
|
|
124
|
+
.send("Page.handleJavaScriptDialog", cdpParams, this._sessionId)
|
|
125
|
+
.catch(() => {
|
|
126
|
+
debug("Dialog already handled or dismissed: %s (%s)", event.type, event.message);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { CdpClient } from "./cdp-client.js";
|
|
2
|
+
export interface DomWatcherOptions {
|
|
3
|
+
debounceMs?: number;
|
|
4
|
+
}
|
|
5
|
+
export declare class DomWatcher {
|
|
6
|
+
private _cdpClient;
|
|
7
|
+
private _sessionId;
|
|
8
|
+
private _debounceMs;
|
|
9
|
+
private _debounceTimer;
|
|
10
|
+
private _onRefreshCallback;
|
|
11
|
+
private _onInvalidateCallback;
|
|
12
|
+
private _onMutationInvalidateCallback;
|
|
13
|
+
private _refreshInProgress;
|
|
14
|
+
private _axChangeWaiters;
|
|
15
|
+
private _onNodesUpdated;
|
|
16
|
+
private _onDocumentUpdated;
|
|
17
|
+
private _onChildCountUpdated;
|
|
18
|
+
private _onChildInserted;
|
|
19
|
+
private _onChildRemoved;
|
|
20
|
+
private _onFrameNavigated;
|
|
21
|
+
constructor(cdpClient: CdpClient, sessionId: string, options?: DomWatcherOptions);
|
|
22
|
+
/** Registriert den Callback der bei DOM-Aenderungen (nach Debounce) aufgerufen wird */
|
|
23
|
+
onRefresh(callback: () => Promise<void>): void;
|
|
24
|
+
/** Registriert den Callback fuer sofortige Cache-Invalidierung (bei Navigation) */
|
|
25
|
+
onInvalidate(callback: () => void): void;
|
|
26
|
+
/** Registriert den Callback fuer sofortige Precomputed-Cache-Invalidierung bei DOM-Mutationen (BUG-010) */
|
|
27
|
+
onMutationInvalidate(callback: () => void): void;
|
|
28
|
+
/**
|
|
29
|
+
* Story 13a.2: Wait for Accessibility.nodesUpdated event within timeout.
|
|
30
|
+
* Returns true if AX tree changed, false on timeout.
|
|
31
|
+
*/
|
|
32
|
+
waitForAXChange(timeoutMs: number): Promise<boolean>;
|
|
33
|
+
/** Startet DOM-Beobachtung: DOM.enable + Event-Listener registrieren */
|
|
34
|
+
init(): Promise<void>;
|
|
35
|
+
/** Stoppt Beobachtung und raeumt auf */
|
|
36
|
+
detach(): void;
|
|
37
|
+
/** Reconnect: neuer CdpClient, neue SessionId */
|
|
38
|
+
reinit(cdpClient: CdpClient, sessionId: string): Promise<void>;
|
|
39
|
+
/** Debounce-Handler: Wird bei jeder DOM-Mutation aufgerufen */
|
|
40
|
+
private _scheduleMutationRefresh;
|
|
41
|
+
/** Navigation-Handler: Invalidiert Cache SOFORT (kein Debounce) */
|
|
42
|
+
private _handleNavigation;
|
|
43
|
+
/** Fuehrt den Refresh-Callback aus (mit Guard gegen parallele Refreshes) */
|
|
44
|
+
private _executeRefresh;
|
|
45
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { debug } from "./debug.js";
|
|
2
|
+
// --- DomWatcher ---
|
|
3
|
+
export class DomWatcher {
|
|
4
|
+
_cdpClient;
|
|
5
|
+
_sessionId;
|
|
6
|
+
_debounceMs;
|
|
7
|
+
_debounceTimer = null;
|
|
8
|
+
_onRefreshCallback = null;
|
|
9
|
+
_onInvalidateCallback = null;
|
|
10
|
+
_onMutationInvalidateCallback = null;
|
|
11
|
+
_refreshInProgress = false;
|
|
12
|
+
// Story 13a.2: Accessibility.nodesUpdated — resolves pending waitForAXChange promises
|
|
13
|
+
// Each entry: { resolve(changed), cancel() to clear timeout }
|
|
14
|
+
_axChangeWaiters = [];
|
|
15
|
+
_onNodesUpdated = null;
|
|
16
|
+
// Bound callbacks for on/off
|
|
17
|
+
_onDocumentUpdated = null;
|
|
18
|
+
_onChildCountUpdated = null;
|
|
19
|
+
_onChildInserted = null;
|
|
20
|
+
_onChildRemoved = null;
|
|
21
|
+
_onFrameNavigated = null;
|
|
22
|
+
constructor(cdpClient, sessionId, options) {
|
|
23
|
+
this._cdpClient = cdpClient;
|
|
24
|
+
this._sessionId = sessionId;
|
|
25
|
+
this._debounceMs = options?.debounceMs ?? 500;
|
|
26
|
+
}
|
|
27
|
+
/** Registriert den Callback der bei DOM-Aenderungen (nach Debounce) aufgerufen wird */
|
|
28
|
+
onRefresh(callback) {
|
|
29
|
+
this._onRefreshCallback = callback;
|
|
30
|
+
}
|
|
31
|
+
/** Registriert den Callback fuer sofortige Cache-Invalidierung (bei Navigation) */
|
|
32
|
+
onInvalidate(callback) {
|
|
33
|
+
this._onInvalidateCallback = callback;
|
|
34
|
+
}
|
|
35
|
+
/** Registriert den Callback fuer sofortige Precomputed-Cache-Invalidierung bei DOM-Mutationen (BUG-010) */
|
|
36
|
+
onMutationInvalidate(callback) {
|
|
37
|
+
this._onMutationInvalidateCallback = callback;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Story 13a.2: Wait for Accessibility.nodesUpdated event within timeout.
|
|
41
|
+
* Returns true if AX tree changed, false on timeout.
|
|
42
|
+
*/
|
|
43
|
+
waitForAXChange(timeoutMs) {
|
|
44
|
+
return new Promise((resolve) => {
|
|
45
|
+
let settled = false;
|
|
46
|
+
const timer = setTimeout(() => {
|
|
47
|
+
if (!settled) {
|
|
48
|
+
settled = true;
|
|
49
|
+
this._axChangeWaiters = this._axChangeWaiters.filter((w) => w.resolve !== resolve);
|
|
50
|
+
resolve(false);
|
|
51
|
+
}
|
|
52
|
+
}, timeoutMs);
|
|
53
|
+
this._axChangeWaiters.push({
|
|
54
|
+
resolve: (changed) => { if (!settled) {
|
|
55
|
+
settled = true;
|
|
56
|
+
clearTimeout(timer);
|
|
57
|
+
resolve(changed);
|
|
58
|
+
} },
|
|
59
|
+
cancel: () => clearTimeout(timer),
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/** Startet DOM-Beobachtung: DOM.enable + Event-Listener registrieren */
|
|
64
|
+
async init() {
|
|
65
|
+
// DOM.enable ist idempotent — doppeltes Enable schadet nicht
|
|
66
|
+
await this._cdpClient.send("DOM.enable", {}, this._sessionId);
|
|
67
|
+
this._onDocumentUpdated = () => this._scheduleMutationRefresh();
|
|
68
|
+
this._onChildCountUpdated = () => this._scheduleMutationRefresh();
|
|
69
|
+
this._onChildInserted = () => this._scheduleMutationRefresh();
|
|
70
|
+
this._onChildRemoved = () => this._scheduleMutationRefresh();
|
|
71
|
+
this._onFrameNavigated = (params) => this._handleNavigation(params);
|
|
72
|
+
// Story 13a.2: Subscribe to Accessibility.nodesUpdated for post-click detection
|
|
73
|
+
this._onNodesUpdated = () => {
|
|
74
|
+
debug("DomWatcher: Accessibility.nodesUpdated received");
|
|
75
|
+
const waiters = [...this._axChangeWaiters];
|
|
76
|
+
this._axChangeWaiters = [];
|
|
77
|
+
for (const w of waiters)
|
|
78
|
+
w.resolve(true);
|
|
79
|
+
};
|
|
80
|
+
this._cdpClient.on("DOM.documentUpdated", this._onDocumentUpdated, this._sessionId);
|
|
81
|
+
this._cdpClient.on("DOM.childNodeCountUpdated", this._onChildCountUpdated, this._sessionId);
|
|
82
|
+
this._cdpClient.on("DOM.childNodeInserted", this._onChildInserted, this._sessionId);
|
|
83
|
+
this._cdpClient.on("DOM.childNodeRemoved", this._onChildRemoved, this._sessionId);
|
|
84
|
+
this._cdpClient.on("Page.frameNavigated", this._onFrameNavigated, this._sessionId);
|
|
85
|
+
this._cdpClient.on("Accessibility.nodesUpdated", this._onNodesUpdated, this._sessionId);
|
|
86
|
+
debug("DomWatcher initialized on session %s", this._sessionId);
|
|
87
|
+
}
|
|
88
|
+
/** Stoppt Beobachtung und raeumt auf */
|
|
89
|
+
detach() {
|
|
90
|
+
// Cancel pending debounce timer
|
|
91
|
+
if (this._debounceTimer) {
|
|
92
|
+
clearTimeout(this._debounceTimer);
|
|
93
|
+
this._debounceTimer = null;
|
|
94
|
+
}
|
|
95
|
+
// Remove all event listeners
|
|
96
|
+
if (this._onDocumentUpdated) {
|
|
97
|
+
this._cdpClient.off("DOM.documentUpdated", this._onDocumentUpdated);
|
|
98
|
+
this._onDocumentUpdated = null;
|
|
99
|
+
}
|
|
100
|
+
if (this._onChildCountUpdated) {
|
|
101
|
+
this._cdpClient.off("DOM.childNodeCountUpdated", this._onChildCountUpdated);
|
|
102
|
+
this._onChildCountUpdated = null;
|
|
103
|
+
}
|
|
104
|
+
if (this._onChildInserted) {
|
|
105
|
+
this._cdpClient.off("DOM.childNodeInserted", this._onChildInserted);
|
|
106
|
+
this._onChildInserted = null;
|
|
107
|
+
}
|
|
108
|
+
if (this._onChildRemoved) {
|
|
109
|
+
this._cdpClient.off("DOM.childNodeRemoved", this._onChildRemoved);
|
|
110
|
+
this._onChildRemoved = null;
|
|
111
|
+
}
|
|
112
|
+
if (this._onFrameNavigated) {
|
|
113
|
+
this._cdpClient.off("Page.frameNavigated", this._onFrameNavigated);
|
|
114
|
+
this._onFrameNavigated = null;
|
|
115
|
+
}
|
|
116
|
+
if (this._onNodesUpdated) {
|
|
117
|
+
this._cdpClient.off("Accessibility.nodesUpdated", this._onNodesUpdated);
|
|
118
|
+
this._onNodesUpdated = null;
|
|
119
|
+
}
|
|
120
|
+
// Story 13a.2: Resolve any pending AX change waiters as false (detach = no change)
|
|
121
|
+
const waiters = [...this._axChangeWaiters];
|
|
122
|
+
this._axChangeWaiters = [];
|
|
123
|
+
for (const w of waiters) {
|
|
124
|
+
w.cancel();
|
|
125
|
+
w.resolve(false);
|
|
126
|
+
}
|
|
127
|
+
// NICHT DOM.disable — andere Komponenten koennten es brauchen
|
|
128
|
+
debug("DomWatcher detached");
|
|
129
|
+
}
|
|
130
|
+
/** Reconnect: neuer CdpClient, neue SessionId */
|
|
131
|
+
async reinit(cdpClient, sessionId) {
|
|
132
|
+
this.detach();
|
|
133
|
+
this._cdpClient = cdpClient;
|
|
134
|
+
this._sessionId = sessionId;
|
|
135
|
+
await this.init();
|
|
136
|
+
}
|
|
137
|
+
/** Debounce-Handler: Wird bei jeder DOM-Mutation aufgerufen */
|
|
138
|
+
_scheduleMutationRefresh() {
|
|
139
|
+
// H2 fix: Do NOT invalidate selector cache immediately on DOM mutations.
|
|
140
|
+
// The debounced refresh will compute a new fingerprint, and any stale
|
|
141
|
+
// cache entries will self-heal via fingerprint mismatch on next access.
|
|
142
|
+
// BUG-010 fix: Immediately invalidate precomputed A11y-tree cache so the next
|
|
143
|
+
// getTree() call fetches fresh data from CDP instead of serving stale cache.
|
|
144
|
+
// This is idempotent and cheap (just nulls the cached nodes array).
|
|
145
|
+
if (this._onMutationInvalidateCallback) {
|
|
146
|
+
this._onMutationInvalidateCallback();
|
|
147
|
+
}
|
|
148
|
+
// Cancel existing timer
|
|
149
|
+
if (this._debounceTimer) {
|
|
150
|
+
clearTimeout(this._debounceTimer);
|
|
151
|
+
}
|
|
152
|
+
// Set new timer
|
|
153
|
+
this._debounceTimer = setTimeout(() => {
|
|
154
|
+
this._debounceTimer = null;
|
|
155
|
+
this._executeRefresh();
|
|
156
|
+
}, this._debounceMs);
|
|
157
|
+
debug("DomWatcher: DOM mutation detected, scheduling refresh");
|
|
158
|
+
}
|
|
159
|
+
/** Navigation-Handler: Invalidiert Cache SOFORT (kein Debounce) */
|
|
160
|
+
_handleNavigation(params) {
|
|
161
|
+
const p = params;
|
|
162
|
+
// Nur main-frame Navigation (parentId ist undefined/leer fuer main frame)
|
|
163
|
+
if (p.frame?.parentId)
|
|
164
|
+
return; // iframe navigation — ignorieren
|
|
165
|
+
// Cancel pending debounce timer
|
|
166
|
+
if (this._debounceTimer) {
|
|
167
|
+
clearTimeout(this._debounceTimer);
|
|
168
|
+
this._debounceTimer = null;
|
|
169
|
+
}
|
|
170
|
+
// Cache sofort invalidieren
|
|
171
|
+
if (this._onInvalidateCallback) {
|
|
172
|
+
this._onInvalidateCallback();
|
|
173
|
+
}
|
|
174
|
+
debug("DomWatcher: main frame navigation detected, cache invalidated");
|
|
175
|
+
// H2: Hintergrund-Refresh nach Navigation triggern (AC #4)
|
|
176
|
+
// Kurzer Delay damit die neue Seite settlen kann
|
|
177
|
+
this._scheduleMutationRefresh();
|
|
178
|
+
}
|
|
179
|
+
/** Fuehrt den Refresh-Callback aus (mit Guard gegen parallele Refreshes) */
|
|
180
|
+
async _executeRefresh() {
|
|
181
|
+
if (this._refreshInProgress)
|
|
182
|
+
return; // letzter Refresh laeuft noch
|
|
183
|
+
if (!this._onRefreshCallback)
|
|
184
|
+
return;
|
|
185
|
+
this._refreshInProgress = true;
|
|
186
|
+
try {
|
|
187
|
+
await this._onRefreshCallback();
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
// silent — Hintergrund-Refresh soll Server nicht lahmlegen
|
|
191
|
+
debug("DomWatcher: background refresh failed (ignored)");
|
|
192
|
+
}
|
|
193
|
+
this._refreshInProgress = false;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const EMULATED_WIDTH = 1280;
|
|
2
|
+
export declare const EMULATED_HEIGHT = 800;
|
|
3
|
+
export declare const DEVICE_SCALE_FACTOR = 1;
|
|
4
|
+
export declare const MOBILE = false;
|
|
5
|
+
export declare const DEVICE_METRICS_OVERRIDE: {
|
|
6
|
+
readonly width: 1280;
|
|
7
|
+
readonly height: 800;
|
|
8
|
+
readonly deviceScaleFactor: 1;
|
|
9
|
+
readonly mobile: false;
|
|
10
|
+
};
|
|
11
|
+
export declare function setHeadless(value: boolean): void;
|
|
12
|
+
export declare function isHeadless(): boolean;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const EMULATED_WIDTH = 1280;
|
|
2
|
+
export const EMULATED_HEIGHT = 800;
|
|
3
|
+
export const DEVICE_SCALE_FACTOR = 1;
|
|
4
|
+
export const MOBILE = false;
|
|
5
|
+
export const DEVICE_METRICS_OVERRIDE = {
|
|
6
|
+
width: EMULATED_WIDTH,
|
|
7
|
+
height: EMULATED_HEIGHT,
|
|
8
|
+
deviceScaleFactor: DEVICE_SCALE_FACTOR,
|
|
9
|
+
mobile: MOBILE,
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Runtime headless state, set once after connection is established.
|
|
13
|
+
* Used by tools (e.g. switch-tab) to decide emulation vs window-resize.
|
|
14
|
+
*/
|
|
15
|
+
let _headless = true;
|
|
16
|
+
export function setHeadless(value) { _headless = value; }
|
|
17
|
+
export function isHeadless() { return _headless; }
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { CdpClient } from "./cdp-client.js";
|
|
2
|
+
export type { CdpClientOptions } from "./cdp-client.js";
|
|
3
|
+
export type { CdpRequest, CdpResponse, CdpEvent, CdpError } from "./protocol.js";
|
|
4
|
+
export { EMULATED_WIDTH, EMULATED_HEIGHT, DEVICE_SCALE_FACTOR, MOBILE, DEVICE_METRICS_OVERRIDE, } from "./emulation.js";
|
|
5
|
+
export { ChromeLauncher, ChromeConnection, findChromePath, launchChrome, } from "./chrome-launcher.js";
|
|
6
|
+
export type { ChromeConnectionOptions, LaunchOptions } from "./chrome-launcher.js";
|
|
7
|
+
export { debug } from "./debug.js";
|
|
8
|
+
export { settle } from "./settle.js";
|
|
9
|
+
export type { SettleOptions, SettleResult } from "./settle.js";
|
|
10
|
+
export { DomWatcher } from "./dom-watcher.js";
|
|
11
|
+
export type { DomWatcherOptions } from "./dom-watcher.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { CdpClient } from "./cdp-client.js";
|
|
2
|
+
export { EMULATED_WIDTH, EMULATED_HEIGHT, DEVICE_SCALE_FACTOR, MOBILE, DEVICE_METRICS_OVERRIDE, } from "./emulation.js";
|
|
3
|
+
export { ChromeLauncher, ChromeConnection, findChromePath, launchChrome, } from "./chrome-launcher.js";
|
|
4
|
+
export { debug } from "./debug.js";
|
|
5
|
+
export { settle } from "./settle.js";
|
|
6
|
+
export { DomWatcher } from "./dom-watcher.js";
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { CdpClient } from "./cdp-client.js";
|
|
2
|
+
export interface NetworkRequestEntry {
|
|
3
|
+
url: string;
|
|
4
|
+
method: string;
|
|
5
|
+
status: number;
|
|
6
|
+
mimeType: string;
|
|
7
|
+
size: number;
|
|
8
|
+
duration: number;
|
|
9
|
+
initiator: string;
|
|
10
|
+
failed: boolean;
|
|
11
|
+
errorText?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface NetworkCollectorOptions {
|
|
14
|
+
maxEntries?: number;
|
|
15
|
+
maxPending?: number;
|
|
16
|
+
}
|
|
17
|
+
export declare class NetworkCollector {
|
|
18
|
+
private _buffer;
|
|
19
|
+
private _pending;
|
|
20
|
+
private _maxEntries;
|
|
21
|
+
private _maxPending;
|
|
22
|
+
private _cdpClient;
|
|
23
|
+
private _sessionId;
|
|
24
|
+
private _monitoring;
|
|
25
|
+
private _monitoringSince;
|
|
26
|
+
private _callbacks;
|
|
27
|
+
constructor(cdpClient: CdpClient, sessionId: string, options?: NetworkCollectorOptions);
|
|
28
|
+
/**
|
|
29
|
+
* Start Network monitoring. Calls Network.enable and registers 4 event listeners.
|
|
30
|
+
* Idempotent — calling twice has no effect.
|
|
31
|
+
*/
|
|
32
|
+
start(): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Stop Network monitoring. Returns collected requests, clears buffer, calls Network.disable.
|
|
35
|
+
* If not monitoring, returns empty array (graceful).
|
|
36
|
+
*/
|
|
37
|
+
stop(): Promise<NetworkRequestEntry[]>;
|
|
38
|
+
/**
|
|
39
|
+
* Remove event listeners without calling Network.disable (for shutdown — sync, no CDP call).
|
|
40
|
+
*/
|
|
41
|
+
detach(): void;
|
|
42
|
+
/**
|
|
43
|
+
* Re-initialize after reconnect or tab switch.
|
|
44
|
+
* Detaches, sets new client/session, clears buffer.
|
|
45
|
+
* Monitoring stays OFF — agent must call start() again.
|
|
46
|
+
*/
|
|
47
|
+
reinit(cdpClient: CdpClient, sessionId: string): void;
|
|
48
|
+
/**
|
|
49
|
+
* Return a copy of all buffered request entries.
|
|
50
|
+
*/
|
|
51
|
+
getAll(): NetworkRequestEntry[];
|
|
52
|
+
/**
|
|
53
|
+
* Return filtered request entries. Both filters are combined with AND.
|
|
54
|
+
* Throws if the regex pattern is invalid.
|
|
55
|
+
*/
|
|
56
|
+
getFiltered(filter?: string, pattern?: string): NetworkRequestEntry[];
|
|
57
|
+
get isMonitoring(): boolean;
|
|
58
|
+
get monitoringSince(): number;
|
|
59
|
+
get count(): number;
|
|
60
|
+
private _removeListeners;
|
|
61
|
+
private _pushEntry;
|
|
62
|
+
/**
|
|
63
|
+
* H3: Flush all pending requests into the ring buffer as incomplete entries.
|
|
64
|
+
* Called by stop() so in-flight requests are not silently discarded.
|
|
65
|
+
*/
|
|
66
|
+
private _flushPending;
|
|
67
|
+
/**
|
|
68
|
+
* H2: Evict oldest pending entries when the pending map exceeds _maxPending.
|
|
69
|
+
* Evicted entries are pushed into the ring buffer as incomplete.
|
|
70
|
+
*/
|
|
71
|
+
private _evictOldestPending;
|
|
72
|
+
private _finishRequest;
|
|
73
|
+
private _onRequestWillBeSent;
|
|
74
|
+
private _onResponseReceived;
|
|
75
|
+
private _onLoadingFinished;
|
|
76
|
+
private _onLoadingFailed;
|
|
77
|
+
}
|