browser-debugger-cli 0.9.0 → 0.10.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/README.md +4 -1
- package/dist/commands/dom/a11y.js +2 -1
- package/dist/commands/dom/formInteraction.js +56 -25
- package/dist/commands/dom/helpers/keyAttributes.d.ts +20 -0
- package/dist/commands/dom/helpers/keyAttributes.js +54 -0
- package/dist/commands/dom/helpers/query.d.ts +1 -1
- package/dist/commands/dom/helpers/query.js +66 -19
- package/dist/commands/dom/helpers/runElementCommand.js +4 -3
- package/dist/commands/dom/helpers/screenshot.js +85 -12
- package/dist/commands/dom/index.d.ts +1 -0
- package/dist/commands/dom/index.js +8 -3
- package/dist/commands/dom/inspect.d.ts +15 -0
- package/dist/commands/dom/inspect.js +82 -0
- package/dist/commands/dom/layout.js +2 -2
- package/dist/commands/dom/listeners.js +2 -2
- package/dist/commands/dom/semanticUtils.d.ts +14 -1
- package/dist/commands/dom/semanticUtils.js +44 -3
- package/dist/commands/network/list.js +13 -2
- package/dist/commands/optionBehaviors.js +48 -6
- package/dist/commands/page.d.ts +1 -1
- package/dist/commands/page.js +62 -3
- package/dist/commands/shared/commonOptions.d.ts +4 -0
- package/dist/commands/shared/commonOptions.js +9 -0
- package/dist/commands/shared/optionTypes.d.ts +21 -0
- package/dist/commands/shared/startHelpers.d.ts +66 -0
- package/dist/commands/shared/startHelpers.js +91 -10
- package/dist/commands/shared/validation.d.ts +11 -0
- package/dist/commands/shared/validation.js +16 -0
- package/dist/daemon/launcher.d.ts +8 -1
- package/dist/daemon/launcher.js +3 -1
- package/dist/daemon/session/Session.d.ts +7 -0
- package/dist/daemon/session/Session.js +23 -1
- package/dist/daemon/session/commandRegistry.d.ts +14 -1
- package/dist/daemon/session/commandRegistry.js +65 -9
- package/dist/daemon/session/interactions.d.ts +18 -5
- package/dist/daemon/session/interactions.js +22 -12
- package/dist/daemon.js +3565 -329
- package/dist/errors/messages.d.ts +69 -0
- package/dist/errors/messages.js +102 -1
- package/dist/index.js +2416 -1320
- package/dist/ipc/client.d.ts +9 -0
- package/dist/ipc/client.js +13 -0
- package/dist/ipc/protocol/commands.d.ts +56 -1
- package/dist/ipc/protocol/commands.js +2 -0
- package/dist/ipc/protocol/domTypes.d.ts +35 -2
- package/dist/ipc/protocol/inspectTypes.d.ts +388 -0
- package/dist/ipc/protocol/inspectTypes.js +10 -0
- package/dist/runtime/dom/actionEffects.d.ts +94 -15
- package/dist/runtime/dom/actionEffects.js +173 -27
- package/dist/runtime/dom/actionEffectsScripts.d.ts +52 -14
- package/dist/runtime/dom/actionEffectsScripts.js +224 -32
- package/dist/runtime/dom/elementInfo.d.ts +26 -0
- package/dist/runtime/dom/elementInfo.js +65 -0
- package/dist/runtime/dom/eventListeners.js +14 -4
- package/dist/runtime/dom/formFillHelpers/fill.d.ts +3 -4
- package/dist/runtime/dom/formFillHelpers/fill.js +77 -28
- package/dist/runtime/dom/frameSelection.d.ts +11 -0
- package/dist/runtime/dom/frameSelection.js +20 -1
- package/dist/runtime/dom/frames.d.ts +38 -5
- package/dist/runtime/dom/frames.js +136 -21
- package/dist/runtime/dom/inspect.d.ts +28 -0
- package/dist/runtime/dom/inspect.js +557 -0
- package/dist/runtime/dom/inspectAllStyles.d.ts +62 -0
- package/dist/runtime/dom/inspectAllStyles.js +385 -0
- package/dist/runtime/dom/inspectCascade.d.ts +94 -0
- package/dist/runtime/dom/inspectCascade.js +371 -0
- package/dist/runtime/dom/inspectCascadeModel.d.ts +39 -0
- package/dist/runtime/dom/inspectCascadeModel.js +232 -0
- package/dist/runtime/dom/inspectHints.d.ts +62 -0
- package/dist/runtime/dom/inspectHints.js +305 -0
- package/dist/runtime/dom/inspectLayoutModel.d.ts +87 -0
- package/dist/runtime/dom/inspectLayoutModel.js +346 -0
- package/dist/runtime/dom/inspectModel.d.ts +74 -0
- package/dist/runtime/dom/inspectModel.js +184 -0
- package/dist/runtime/dom/inspectPaintModel.d.ts +157 -0
- package/dist/runtime/dom/inspectPaintModel.js +461 -0
- package/dist/runtime/dom/inspectRules.d.ts +37 -0
- package/dist/runtime/dom/inspectRules.js +101 -0
- package/dist/runtime/dom/inspectScripts.d.ts +132 -0
- package/dist/runtime/dom/inspectScripts.js +263 -0
- package/dist/runtime/dom/inspectTree.d.ts +40 -0
- package/dist/runtime/dom/inspectTree.js +134 -0
- package/dist/runtime/dom/inspectVariables.d.ts +33 -0
- package/dist/runtime/dom/inspectVariables.js +94 -0
- package/dist/runtime/dom/inspectWhyModel.d.ts +20 -0
- package/dist/runtime/dom/inspectWhyModel.js +134 -0
- package/dist/runtime/dom/layout.d.ts +5 -1
- package/dist/runtime/dom/layout.js +10 -3
- package/dist/runtime/dom/listenerPageScripts.d.ts +11 -5
- package/dist/runtime/dom/listenerPageScripts.js +95 -9
- package/dist/runtime/dom/listenerSummary.d.ts +4 -0
- package/dist/runtime/dom/listenerSummary.js +26 -9
- package/dist/runtime/dom/reactEventHelpers.d.ts +5 -0
- package/dist/runtime/dom/reactEventHelpers.js +12 -4
- package/dist/runtime/page/emulation.d.ts +20 -0
- package/dist/runtime/page/emulation.js +37 -0
- package/dist/telemetry/a11y.d.ts +10 -0
- package/dist/telemetry/a11y.js +78 -1
- package/dist/telemetry/console.d.ts +1 -0
- package/dist/telemetry/console.js +100 -5
- package/dist/telemetry/network.js +3 -1
- package/dist/types.d.ts +32 -0
- package/dist/ui/formatters/details.d.ts +8 -0
- package/dist/ui/formatters/details.js +59 -3
- package/dist/ui/formatters/dom.d.ts +2 -1
- package/dist/ui/formatters/dom.js +25 -9
- package/dist/ui/formatters/inspect.d.ts +39 -0
- package/dist/ui/formatters/inspect.js +596 -0
- package/dist/ui/formatters/keyAttributes.d.ts +19 -0
- package/dist/ui/formatters/keyAttributes.js +84 -0
- package/dist/ui/formatters/layout.js +2 -2
- package/dist/ui/formatters/networkHeaders.d.ts +13 -0
- package/dist/ui/formatters/networkHeaders.js +23 -3
- package/dist/ui/formatters/networkList.d.ts +29 -1
- package/dist/ui/formatters/networkList.js +86 -20
- package/dist/ui/formatters/status.js +1 -1
- package/dist/ui/formatting.d.ts +9 -0
- package/dist/ui/formatting.js +6 -3
- package/dist/ui/messages/commands.d.ts +123 -7
- package/dist/ui/messages/commands.js +181 -10
- package/dist/ui/messages/networkMessages.d.ts +14 -0
- package/dist/ui/messages/networkMessages.js +18 -0
- package/dist/ui/messages/session.d.ts +14 -0
- package/dist/ui/messages/session.js +20 -0
- package/dist/utils/async.d.ts +9 -0
- package/dist/utils/async.js +17 -0
- package/dist/utils/color.d.ts +84 -0
- package/dist/utils/color.js +376 -0
- package/dist/utils/cssValues.d.ts +109 -0
- package/dist/utils/cssValues.js +236 -0
- package/dist/utils/selectorFilters.d.ts +12 -0
- package/dist/utils/selectorFilters.js +29 -0
- package/package.json +1 -1
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* What a DOM action changed on the page: whether it navigated (to a new
|
|
3
|
-
* document or within the same one), which messages
|
|
4
|
-
* had no visible effect at all
|
|
5
|
-
*
|
|
3
|
+
* document or within the same one), which messages and elements appeared,
|
|
4
|
+
* whether it had no visible effect at all, and whether the page was still
|
|
5
|
+
* working on the result. Costs one page script sent before the action (not
|
|
6
|
+
* waited for: CDP runs it before the action's own scripts) and one read
|
|
6
7
|
* after it, plus a second look 300 ms later when nothing seemed to happen.
|
|
7
|
-
* Worst case, when the page does not answer (a navigation is pending
|
|
8
|
-
* snapshot is given up after {@link START_TIMEOUT_MS}
|
|
9
|
-
* {@link READ_TIMEOUT_MS}.
|
|
8
|
+
* Worst case, when the page does not answer (a navigation is pending, or a
|
|
9
|
+
* long script runs), the snapshot is given up after {@link START_TIMEOUT_MS}
|
|
10
|
+
* and each read after {@link READ_TIMEOUT_MS}.
|
|
10
11
|
*/
|
|
11
12
|
import type { CDPConnection } from '../../connection/cdp.js';
|
|
12
|
-
import type { ActionEffects, NewMessage, PageNavigation } from '../../ipc/protocol/domTypes.js';
|
|
13
|
+
import type { ActionEffects, NewMessage, PageNavigation, PendingChanges, ShownElement, TriggeredRequest } from '../../ipc/protocol/domTypes.js';
|
|
13
14
|
import { type NavigationEvents } from './pageActivity.js';
|
|
14
15
|
/** A message element as a page snapshot lists it */
|
|
15
16
|
export interface SeenMessage {
|
|
@@ -19,6 +20,13 @@ export interface SeenMessage {
|
|
|
19
20
|
element: string;
|
|
20
21
|
}
|
|
21
22
|
export type { NavigationEvents };
|
|
23
|
+
/** Signs that the page was still working at a read (same document only) */
|
|
24
|
+
export interface SettleSignals {
|
|
25
|
+
/** How long ago each recent burst of structural DOM changes was (ms, newest last) */
|
|
26
|
+
burstAges: number[];
|
|
27
|
+
/** A loading indicator shown since the action began, described */
|
|
28
|
+
loading: string | null;
|
|
29
|
+
}
|
|
22
30
|
/** The page after the action */
|
|
23
31
|
export interface ReadSnapshot {
|
|
24
32
|
href: string;
|
|
@@ -29,6 +37,25 @@ export interface ReadSnapshot {
|
|
|
29
37
|
/** Why "no effect" can't be claimed even without changes */
|
|
30
38
|
uncertain?: string;
|
|
31
39
|
messages: SeenMessage[];
|
|
40
|
+
/** Whether the page was still working (same document only) */
|
|
41
|
+
settle?: SettleSignals;
|
|
42
|
+
/** Elements the action showed (when asked for) */
|
|
43
|
+
shown?: ShownElement[];
|
|
44
|
+
}
|
|
45
|
+
/** What collecting saw of the page's work, for {@link pendingChanges} */
|
|
46
|
+
export interface PageWork {
|
|
47
|
+
/** Signals of the last read, when there was one in the same document */
|
|
48
|
+
settle?: SettleSignals;
|
|
49
|
+
/** The DOM kept changing in bursts over a second look ({@link domLooksBusy}) */
|
|
50
|
+
domChanging: boolean;
|
|
51
|
+
/** A read got no answer within its time (a long script) */
|
|
52
|
+
unresponsive: boolean;
|
|
53
|
+
/** A main-frame load was still pending */
|
|
54
|
+
navigating: boolean;
|
|
55
|
+
}
|
|
56
|
+
/** Effects, plus the page's work for deciding whether it had settled */
|
|
57
|
+
export interface CollectedEffects extends ActionEffects {
|
|
58
|
+
work?: PageWork;
|
|
32
59
|
}
|
|
33
60
|
/** What the action did besides changing the page, for the "no effect" decision */
|
|
34
61
|
export interface OtherActivity {
|
|
@@ -55,6 +82,50 @@ export interface OtherActivity {
|
|
|
55
82
|
* @returns New messages
|
|
56
83
|
*/
|
|
57
84
|
export declare function newMessages(before: SeenMessage[], after: SeenMessage[], newDocument: boolean): NewMessage[];
|
|
85
|
+
/**
|
|
86
|
+
* Elements to report as shown: those whose text is not already reported as
|
|
87
|
+
* a new message, at most {@link MAX_SHOWN_ELEMENTS}, texts cut to
|
|
88
|
+
* {@link MAX_MESSAGE_LENGTH} characters.
|
|
89
|
+
*
|
|
90
|
+
* @param shown - Elements the page found shown by the action
|
|
91
|
+
* @param messages - New messages being reported
|
|
92
|
+
* @returns Elements to report
|
|
93
|
+
*/
|
|
94
|
+
export declare function shownElements(shown: ShownElement[], messages: NewMessage[]): ShownElement[];
|
|
95
|
+
/**
|
|
96
|
+
* Whether a read's DOM looks busy, worth a second look: at least
|
|
97
|
+
* {@link BUSY_BURSTS} bursts of structural changes within
|
|
98
|
+
* {@link BUSY_WINDOW_MS}, the last within {@link BUSY_RECENT_MS}. Text-only
|
|
99
|
+
* changes (clocks) and style changes (animations) are not bursts.
|
|
100
|
+
*
|
|
101
|
+
* @param settle - Signals of the read
|
|
102
|
+
* @returns True when the DOM may still be changing
|
|
103
|
+
*/
|
|
104
|
+
export declare function domLooksBusy(settle: SettleSignals | undefined): boolean;
|
|
105
|
+
/**
|
|
106
|
+
* Whether the DOM kept changing during the second look: at least
|
|
107
|
+
* {@link BUSY_BURSTS} new bursts within the time since the first read (a
|
|
108
|
+
* render that ends in two commits, or a poller updating once a second, does
|
|
109
|
+
* not count).
|
|
110
|
+
*
|
|
111
|
+
* @param settle - Signals of the second read
|
|
112
|
+
* @param sinceMs - Time since the first read
|
|
113
|
+
* @returns True when the DOM is still changing
|
|
114
|
+
*/
|
|
115
|
+
export declare function domKeptChanging(settle: SettleSignals | undefined, sinceMs: number): boolean;
|
|
116
|
+
/**
|
|
117
|
+
* What the page was still working on when the action returned, or undefined
|
|
118
|
+
* when it looked settled: content requests (documents, fetch/XHR, scripts)
|
|
119
|
+
* still pending, a new document still loading, a loading indicator that
|
|
120
|
+
* appeared, a DOM still changing ({@link domKeptChanging}), or a page that
|
|
121
|
+
* did not answer (a long script). A result a timer renders later, with no
|
|
122
|
+
* DOM change before it, is not seen.
|
|
123
|
+
*
|
|
124
|
+
* @param work - What collecting saw
|
|
125
|
+
* @param requests - Requests the action triggered (with pending ones)
|
|
126
|
+
* @returns Pending work, or undefined
|
|
127
|
+
*/
|
|
128
|
+
export declare function pendingChanges(work: PageWork, requests?: TriggeredRequest[]): PendingChanges | undefined;
|
|
58
129
|
/**
|
|
59
130
|
* How the page's location changed: a new document committed in the main
|
|
60
131
|
* frame (also when it has the URL it had, as after a form POST that
|
|
@@ -72,8 +143,8 @@ export declare function pageNavigation(startHref: string | undefined, read: Read
|
|
|
72
143
|
/**
|
|
73
144
|
* Whether an action had no visible effect: the page was read before and
|
|
74
145
|
* after in the same document, it counted no DOM change, nothing made the
|
|
75
|
-
* check uncertain, and no navigation, message, request,
|
|
76
|
-
* happened.
|
|
146
|
+
* check uncertain, and no navigation, message, shown element, request,
|
|
147
|
+
* dialog or new window happened.
|
|
77
148
|
*
|
|
78
149
|
* @param read - Page read after the action
|
|
79
150
|
* @param effects - Navigation and messages found
|
|
@@ -81,16 +152,24 @@ export declare function pageNavigation(startHref: string | undefined, read: Read
|
|
|
81
152
|
* @returns True to report `effect: "none"`
|
|
82
153
|
*/
|
|
83
154
|
export declare function hadNoEffect(read: ReadSnapshot | undefined, effects: ActionEffects, activity: OtherActivity): boolean;
|
|
155
|
+
/** What collecting looks at besides navigation and messages */
|
|
156
|
+
export interface CollectOptions {
|
|
157
|
+
/** Dialogs the action opened */
|
|
158
|
+
dialogs: number;
|
|
159
|
+
/** Decide "no effect" (with a second look when nothing seemed to happen) */
|
|
160
|
+
detectNoEffect: boolean;
|
|
161
|
+
/** List the elements the action showed */
|
|
162
|
+
reportShown?: boolean;
|
|
163
|
+
/** Take a second look when the DOM looks busy, to tell whether it is still changing */
|
|
164
|
+
detectUnsettled?: boolean;
|
|
165
|
+
}
|
|
84
166
|
/** Collects what changed, once the action and its wait are done */
|
|
85
167
|
export interface ActionEffectsWatch {
|
|
86
168
|
/**
|
|
87
|
-
* @param options - Dialogs
|
|
88
|
-
* @returns What changed
|
|
169
|
+
* @param options - Dialogs, and what to decide and list
|
|
170
|
+
* @returns What changed, and the page's work at the end
|
|
89
171
|
*/
|
|
90
|
-
collect(options:
|
|
91
|
-
dialogs: number;
|
|
92
|
-
detectNoEffect: boolean;
|
|
93
|
-
}): Promise<ActionEffects>;
|
|
172
|
+
collect(options: CollectOptions): Promise<CollectedEffects>;
|
|
94
173
|
/** Stop listening and stop the page's watch (always call) */
|
|
95
174
|
dispose(): void;
|
|
96
175
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* What a DOM action changed on the page: whether it navigated (to a new
|
|
3
|
-
* document or within the same one), which messages
|
|
4
|
-
* had no visible effect at all
|
|
5
|
-
*
|
|
3
|
+
* document or within the same one), which messages and elements appeared,
|
|
4
|
+
* whether it had no visible effect at all, and whether the page was still
|
|
5
|
+
* working on the result. Costs one page script sent before the action (not
|
|
6
|
+
* waited for: CDP runs it before the action's own scripts) and one read
|
|
6
7
|
* after it, plus a second look 300 ms later when nothing seemed to happen.
|
|
7
|
-
* Worst case, when the page does not answer (a navigation is pending
|
|
8
|
-
* snapshot is given up after {@link START_TIMEOUT_MS}
|
|
9
|
-
* {@link READ_TIMEOUT_MS}.
|
|
8
|
+
* Worst case, when the page does not answer (a navigation is pending, or a
|
|
9
|
+
* long script runs), the snapshot is given up after {@link START_TIMEOUT_MS}
|
|
10
|
+
* and each read after {@link READ_TIMEOUT_MS}.
|
|
10
11
|
*/
|
|
11
12
|
import { EFFECTS_READ_SCRIPT, EFFECTS_START_SCRIPT, EFFECTS_STOP_SCRIPT, } from './actionEffectsScripts.js';
|
|
12
13
|
import { listenForActivity, } from './pageActivity.js';
|
|
@@ -16,6 +17,19 @@ import { getErrorMessage } from '../../utils/errors.js';
|
|
|
16
17
|
const log = createLogger('dom');
|
|
17
18
|
/** Messages reported per action */
|
|
18
19
|
const MAX_NEW_MESSAGES = 3;
|
|
20
|
+
/** Shown elements reported per action */
|
|
21
|
+
const MAX_SHOWN_ELEMENTS = 3;
|
|
22
|
+
/**
|
|
23
|
+
* Bursts of DOM changes that make the DOM look busy: at least this many
|
|
24
|
+
* within {@link BUSY_WINDOW_MS}, the last within {@link BUSY_RECENT_MS}
|
|
25
|
+
*/
|
|
26
|
+
const BUSY_BURSTS = 2;
|
|
27
|
+
const BUSY_WINDOW_MS = 500;
|
|
28
|
+
const BUSY_RECENT_MS = 150;
|
|
29
|
+
/** Second look at a DOM that looked busy; it is still changing with {@link BUSY_BURSTS} new bursts by then (ms) */
|
|
30
|
+
const STILL_CHANGING_RECHECK_MS = 250;
|
|
31
|
+
/** Resource types of pending requests that mean more content is coming */
|
|
32
|
+
const CONTENT_REQUEST_TYPES = new Set(['Document', 'XHR', 'Fetch', 'Script']);
|
|
19
33
|
/** Longest message text reported */
|
|
20
34
|
const MAX_MESSAGE_LENGTH = 120;
|
|
21
35
|
/** How long collecting waits for the snapshot taken before the action */
|
|
@@ -90,6 +104,76 @@ function cutText(text) {
|
|
|
90
104
|
return text;
|
|
91
105
|
return `${characters.slice(0, MAX_MESSAGE_LENGTH - 1).join('')}…`;
|
|
92
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Elements to report as shown: those whose text is not already reported as
|
|
109
|
+
* a new message, at most {@link MAX_SHOWN_ELEMENTS}, texts cut to
|
|
110
|
+
* {@link MAX_MESSAGE_LENGTH} characters.
|
|
111
|
+
*
|
|
112
|
+
* @param shown - Elements the page found shown by the action
|
|
113
|
+
* @param messages - New messages being reported
|
|
114
|
+
* @returns Elements to report
|
|
115
|
+
*/
|
|
116
|
+
export function shownElements(shown, messages) {
|
|
117
|
+
const reported = new Set(messages.map((message) => message.text));
|
|
118
|
+
return shown
|
|
119
|
+
.filter((element) => !reported.has(cutText(element.text)))
|
|
120
|
+
.slice(0, MAX_SHOWN_ELEMENTS)
|
|
121
|
+
.map((element) => ({ ...element, text: cutText(element.text) }));
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Whether a read's DOM looks busy, worth a second look: at least
|
|
125
|
+
* {@link BUSY_BURSTS} bursts of structural changes within
|
|
126
|
+
* {@link BUSY_WINDOW_MS}, the last within {@link BUSY_RECENT_MS}. Text-only
|
|
127
|
+
* changes (clocks) and style changes (animations) are not bursts.
|
|
128
|
+
*
|
|
129
|
+
* @param settle - Signals of the read
|
|
130
|
+
* @returns True when the DOM may still be changing
|
|
131
|
+
*/
|
|
132
|
+
export function domLooksBusy(settle) {
|
|
133
|
+
if (!settle)
|
|
134
|
+
return false;
|
|
135
|
+
const recent = settle.burstAges.filter((age) => age <= BUSY_WINDOW_MS);
|
|
136
|
+
return recent.length >= BUSY_BURSTS && Math.min(...recent) <= BUSY_RECENT_MS;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Whether the DOM kept changing during the second look: at least
|
|
140
|
+
* {@link BUSY_BURSTS} new bursts within the time since the first read (a
|
|
141
|
+
* render that ends in two commits, or a poller updating once a second, does
|
|
142
|
+
* not count).
|
|
143
|
+
*
|
|
144
|
+
* @param settle - Signals of the second read
|
|
145
|
+
* @param sinceMs - Time since the first read
|
|
146
|
+
* @returns True when the DOM is still changing
|
|
147
|
+
*/
|
|
148
|
+
export function domKeptChanging(settle, sinceMs) {
|
|
149
|
+
if (!settle)
|
|
150
|
+
return false;
|
|
151
|
+
return settle.burstAges.filter((age) => age < sinceMs).length >= BUSY_BURSTS;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* What the page was still working on when the action returned, or undefined
|
|
155
|
+
* when it looked settled: content requests (documents, fetch/XHR, scripts)
|
|
156
|
+
* still pending, a new document still loading, a loading indicator that
|
|
157
|
+
* appeared, a DOM still changing ({@link domKeptChanging}), or a page that
|
|
158
|
+
* did not answer (a long script). A result a timer renders later, with no
|
|
159
|
+
* DOM change before it, is not seen.
|
|
160
|
+
*
|
|
161
|
+
* @param work - What collecting saw
|
|
162
|
+
* @param requests - Requests the action triggered (with pending ones)
|
|
163
|
+
* @returns Pending work, or undefined
|
|
164
|
+
*/
|
|
165
|
+
export function pendingChanges(work, requests = []) {
|
|
166
|
+
const settle = work.settle;
|
|
167
|
+
const pendingRequests = requests.filter((request) => request.pending && CONTENT_REQUEST_TYPES.has(request.resourceType ?? '')).length;
|
|
168
|
+
const pending = {
|
|
169
|
+
...(pendingRequests > 0 && { requests: pendingRequests }),
|
|
170
|
+
...(work.navigating && { navigation: true }),
|
|
171
|
+
...(settle?.loading && { loading: settle.loading }),
|
|
172
|
+
...(work.domChanging && { domChanging: true }),
|
|
173
|
+
...(work.unresponsive && { busy: true }),
|
|
174
|
+
};
|
|
175
|
+
return Object.keys(pending).length > 0 ? pending : undefined;
|
|
176
|
+
}
|
|
93
177
|
/**
|
|
94
178
|
* How the page's location changed: a new document committed in the main
|
|
95
179
|
* frame (also when it has the URL it had, as after a form POST that
|
|
@@ -122,8 +206,8 @@ export function pageNavigation(startHref, read, events) {
|
|
|
122
206
|
/**
|
|
123
207
|
* Whether an action had no visible effect: the page was read before and
|
|
124
208
|
* after in the same document, it counted no DOM change, nothing made the
|
|
125
|
-
* check uncertain, and no navigation, message, request,
|
|
126
|
-
* happened.
|
|
209
|
+
* check uncertain, and no navigation, message, shown element, request,
|
|
210
|
+
* dialog or new window happened.
|
|
127
211
|
*
|
|
128
212
|
* @param read - Page read after the action
|
|
129
213
|
* @param effects - Navigation and messages found
|
|
@@ -137,6 +221,7 @@ export function hadNoEffect(read, effects, activity) {
|
|
|
137
221
|
read.uncertain === undefined &&
|
|
138
222
|
effects.navigation === undefined &&
|
|
139
223
|
(effects.messages ?? []).length === 0 &&
|
|
224
|
+
(effects.shown ?? []).length === 0 &&
|
|
140
225
|
activity.requests === 0 &&
|
|
141
226
|
activity.dialogs === 0 &&
|
|
142
227
|
!activity.opened);
|
|
@@ -155,6 +240,7 @@ export function watchActionEffects(cdp) {
|
|
|
155
240
|
listener: listenForActivity(cdp),
|
|
156
241
|
start: evaluate(cdp, EFFECTS_START_SCRIPT),
|
|
157
242
|
stopConfirmed: false,
|
|
243
|
+
unresponsive: false,
|
|
158
244
|
};
|
|
159
245
|
return {
|
|
160
246
|
collect: (options) => collectEffects(watch, options),
|
|
@@ -163,28 +249,82 @@ export function watchActionEffects(cdp) {
|
|
|
163
249
|
}
|
|
164
250
|
/**
|
|
165
251
|
* What changed: the navigation (from CDP events even without a snapshot),
|
|
166
|
-
* new messages
|
|
252
|
+
* new messages, shown elements when asked, "no effect" after a second look
|
|
253
|
+
* when asked, and the page's work at the end.
|
|
167
254
|
*
|
|
168
255
|
* @param watch - The action's watch
|
|
169
|
-
* @param options - Dialogs
|
|
256
|
+
* @param options - Dialogs, and what to decide and list
|
|
170
257
|
* @returns What changed
|
|
171
258
|
*/
|
|
172
259
|
async function collectEffects(watch, options) {
|
|
173
|
-
const start = await
|
|
174
|
-
if (!start)
|
|
175
|
-
return effectsOf(undefined, undefined, watch.listener.events);
|
|
176
|
-
|
|
260
|
+
const start = await awaitStart(watch);
|
|
261
|
+
if (!start) {
|
|
262
|
+
return { ...effectsOf(undefined, undefined, watch.listener.events), work: pageWork(watch) };
|
|
263
|
+
}
|
|
264
|
+
const reportShown = options.reportShown === true;
|
|
265
|
+
let snapshot = await readPage(watch, { stop: false, reportShown });
|
|
177
266
|
let effects = effectsOf(start, snapshot, watch.listener.events);
|
|
178
267
|
const quiet = () => hadNoEffect(snapshot, effects, { ...watch.listener.activity(), dialogs: options.dialogs });
|
|
179
|
-
if (
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
268
|
+
if (options.detectNoEffect && quiet()) {
|
|
269
|
+
await delay(NO_EFFECT_RECHECK_MS);
|
|
270
|
+
snapshot = await readPage(watch, { stop: true, reportShown });
|
|
271
|
+
effects = effectsOf(start, snapshot, watch.listener.events);
|
|
272
|
+
if (quiet())
|
|
273
|
+
effects = { ...effects, effect: 'none' };
|
|
274
|
+
}
|
|
275
|
+
const domChanging = options.detectUnsettled === true && (await stillChanging(watch, snapshot));
|
|
276
|
+
return { ...effects, work: pageWork(watch, snapshot, domChanging) };
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* The snapshot taken before the action, waiting at most
|
|
280
|
+
* {@link START_TIMEOUT_MS}; a snapshot still unanswered then (and no
|
|
281
|
+
* navigation pending) marks the page unresponsive.
|
|
282
|
+
*
|
|
283
|
+
* @param watch - The action's watch
|
|
284
|
+
* @returns The snapshot, or undefined
|
|
285
|
+
*/
|
|
286
|
+
async function awaitStart(watch) {
|
|
287
|
+
const started = await raceTimeout(watch.start.then((value) => ({ value })), START_TIMEOUT_MS);
|
|
288
|
+
if (!started)
|
|
289
|
+
watch.unresponsive = !watch.listener.navigationPending();
|
|
290
|
+
return started?.value;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Whether the DOM is still changing: when the last read looked busy
|
|
294
|
+
* ({@link domLooksBusy}), a second read {@link STILL_CHANGING_RECHECK_MS}
|
|
295
|
+
* later must see it keep changing ({@link domKeptChanging}).
|
|
296
|
+
*
|
|
297
|
+
* @param watch - The action's watch
|
|
298
|
+
* @param snapshot - Last read, if any
|
|
299
|
+
* @returns True when the DOM kept changing
|
|
300
|
+
*/
|
|
301
|
+
async function stillChanging(watch, snapshot) {
|
|
302
|
+
if (!domLooksBusy(snapshot?.settle))
|
|
303
|
+
return false;
|
|
304
|
+
const firstRead = Date.now();
|
|
305
|
+
await delay(STILL_CHANGING_RECHECK_MS);
|
|
306
|
+
const recheck = await readPage(watch, { stop: false, reportShown: false });
|
|
307
|
+
return domKeptChanging(recheck?.settle, Date.now() - firstRead);
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* The page's work as the last read and the CDP events saw it.
|
|
311
|
+
*
|
|
312
|
+
* @param watch - The action's watch
|
|
313
|
+
* @param snapshot - Last read, if any
|
|
314
|
+
* @param domChanging - Whether the DOM kept changing over a second look
|
|
315
|
+
* @returns Page work
|
|
316
|
+
*/
|
|
317
|
+
function pageWork(watch, snapshot, domChanging = false) {
|
|
318
|
+
return {
|
|
319
|
+
...(snapshot?.settle && { settle: snapshot.settle }),
|
|
320
|
+
domChanging,
|
|
321
|
+
unresponsive: watch.unresponsive,
|
|
322
|
+
navigating: watch.listener.navigationPending(),
|
|
323
|
+
};
|
|
185
324
|
}
|
|
186
325
|
/**
|
|
187
|
-
* Navigation
|
|
326
|
+
* Navigation, new messages and shown elements from the snapshots and CDP
|
|
327
|
+
* events.
|
|
188
328
|
*
|
|
189
329
|
* @param start - Snapshot before the action, if taken
|
|
190
330
|
* @param snapshot - Read after the action, if taken
|
|
@@ -194,26 +334,32 @@ async function collectEffects(watch, options) {
|
|
|
194
334
|
function effectsOf(start, snapshot, events) {
|
|
195
335
|
const navigation = pageNavigation(start?.href, snapshot, events);
|
|
196
336
|
const messages = start && snapshot ? newMessages(start.messages, snapshot.messages, snapshot.fresh) : [];
|
|
337
|
+
const shown = shownElements(snapshot?.shown ?? [], messages);
|
|
197
338
|
return {
|
|
198
339
|
...(navigation && { navigation }),
|
|
199
340
|
...(messages.length > 0 && { messages }),
|
|
341
|
+
...(shown.length > 0 && { shown }),
|
|
200
342
|
};
|
|
201
343
|
}
|
|
202
344
|
/**
|
|
203
345
|
* Read the page after the action, unless a main-frame load is pending (the
|
|
204
346
|
* read would wait for the new page). A stopping read that answered stops the
|
|
205
|
-
* page's watch, so disposing need not.
|
|
347
|
+
* page's watch, so disposing need not. A read that got no answer in time
|
|
348
|
+
* marks the page unresponsive.
|
|
206
349
|
*
|
|
207
350
|
* @param watch - The action's watch
|
|
208
|
-
* @param
|
|
351
|
+
* @param options - Also stop the page's watch; list shown elements
|
|
209
352
|
* @returns The read, or undefined
|
|
210
353
|
*/
|
|
211
|
-
async function readPage(watch,
|
|
354
|
+
async function readPage(watch, options) {
|
|
212
355
|
if (watch.listener.navigationPending())
|
|
213
356
|
return undefined;
|
|
214
|
-
const expression = `(${EFFECTS_READ_SCRIPT})(${stop})`;
|
|
215
|
-
const
|
|
216
|
-
if (
|
|
357
|
+
const expression = `(${EFFECTS_READ_SCRIPT})(${options.stop}, ${options.reportShown})`;
|
|
358
|
+
const answer = await raceTimeout(evaluate(watch.cdp, expression).then((value) => ({ value })), READ_TIMEOUT_MS);
|
|
359
|
+
if (!answer)
|
|
360
|
+
watch.unresponsive = !watch.listener.navigationPending();
|
|
361
|
+
const snapshot = answer?.value;
|
|
362
|
+
if (options.stop && snapshot)
|
|
217
363
|
watch.stopConfirmed = true;
|
|
218
364
|
return snapshot;
|
|
219
365
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Page scripts behind the "what changed" part of DOM action results: one
|
|
3
3
|
* snapshot before the action ({@link EFFECTS_START_SCRIPT}) and one read after
|
|
4
|
-
* it ({@link EFFECTS_READ_SCRIPT})
|
|
4
|
+
* it ({@link EFFECTS_READ_SCRIPT}), plus the snapshot a hover takes of what
|
|
5
|
+
* is hidden around its target ({@link REVEAL_SNAPSHOT_JS}).
|
|
5
6
|
*/
|
|
6
7
|
/**
|
|
7
8
|
* Page-side test whether an element is part of a message's chrome rather
|
|
@@ -10,12 +11,43 @@
|
|
|
10
11
|
* not `closeable`, `enclosed` or `disclosure`).
|
|
11
12
|
*/
|
|
12
13
|
export declare const MESSAGE_CHROME_JS = "(node) => {\n const closer = /(^|[-_\\s])(close|dismiss)($|[-_\\s])/i;\n return node.getAttribute('aria-hidden') === 'true' ||\n /^(button|script|style|template)$/.test(node.localName) ||\n node.getAttribute('role') === 'button' ||\n closer.test(node.getAttribute('class') || '') ||\n closer.test(node.getAttribute('aria-label') || '');\n}";
|
|
14
|
+
/**
|
|
15
|
+
* Page-side snapshot a hover takes right before the mouse moves (called by
|
|
16
|
+
* the click script with the hovered element): the elements around it (its
|
|
17
|
+
* parent and everything in it) and the tooltips, menus, listboxes, dialogs
|
|
18
|
+
* and popovers anywhere on the page that are hidden, at most
|
|
19
|
+
* {@link MAX_REVEAL_CANDIDATES} looked at within {@link REVEAL_BUDGET_MS}.
|
|
20
|
+
* They are kept by identity in the action's watch, so its read can tell
|
|
21
|
+
* which of them the hover revealed, also through CSS `:hover` rules that
|
|
22
|
+
* change no DOM, and elements moving in the page can't pass for revealed
|
|
23
|
+
* ones. Does nothing without a running watch (another frame).
|
|
24
|
+
*/
|
|
25
|
+
export declare const REVEAL_SNAPSHOT_JS: string;
|
|
26
|
+
/**
|
|
27
|
+
* Page-side list of the elements an action showed: elements added during
|
|
28
|
+
* the watch inside the target's container ({@link NEAR_SCOPE_JS}) or, anywhere,
|
|
29
|
+
* popups and messages (tooltip, menu, listbox, dialog, alert and status
|
|
30
|
+
* roles, `aria-live`, message-like classes), plus, after a hover, the
|
|
31
|
+
* elements hidden before it that are shown now ({@link REVEAL_SNAPSHOT_JS}).
|
|
32
|
+
* Background widgets elsewhere on the page do not count. Only shown ones
|
|
33
|
+
* with visible text count, the outermost of nested ones, and not those
|
|
34
|
+
* whose text a removed element had (a re-render). At most
|
|
35
|
+
* {@link MAX_SHOWN}, within {@link SHOWN_BUDGET_MS}.
|
|
36
|
+
*/
|
|
37
|
+
export declare const SHOWN_ELEMENTS_JS: string;
|
|
13
38
|
/**
|
|
14
39
|
* Page-side test whether a mutation is only focus/hover churn: a class
|
|
15
40
|
* change on an element the action's events hit (`targets`) that only adds
|
|
16
41
|
* or removes classes containing "focus" or "hover".
|
|
17
42
|
*/
|
|
18
43
|
export declare const CHURN_ONLY_JS = "(record, targets) => {\n if (record.type !== 'attributes' || record.attributeName !== 'class' || !targets.has(record.target)) return false;\n const tokens = (text) => new Set((text || '').split(/\\s+/).filter(Boolean));\n const before = tokens(record.oldValue);\n const after = tokens(record.target.getAttribute('class'));\n const changed = [...before].filter((c) => !after.has(c)).concat([...after].filter((c) => !before.has(c)));\n return changed.every((c) => /focus|hover/i.test(c));\n}";
|
|
44
|
+
/**
|
|
45
|
+
* Page-side test whether a mutation changes the page's structure or state
|
|
46
|
+
* rather than only animating it: elements added or removed, or an attribute
|
|
47
|
+
* other than `style` changed. Text-only changes (clocks, counters) and style
|
|
48
|
+
* changes (script-driven animations) do not count.
|
|
49
|
+
*/
|
|
50
|
+
export declare const STRUCTURAL_CHANGE_JS = "(record) => {\n if (record.type === 'attributes') return record.attributeName !== 'style';\n if (record.type !== 'childList') return false;\n const element = (node) => node.nodeType === 1;\n return Array.from(record.addedNodes).some(element) || Array.from(record.removedNodes).some(element);\n}";
|
|
19
51
|
/**
|
|
20
52
|
* Page-side reason why "no effect" can't be claimed even without DOM
|
|
21
53
|
* changes, or undefined: `clipboard` (a copy or cut happened), `no-event`
|
|
@@ -29,22 +61,28 @@ export declare const CHURN_ONLY_JS = "(record, targets) => {\n if (record.type
|
|
|
29
61
|
export declare const UNCERTAIN_JS = "(state, active) => {\n if (state.copied) return 'clipboard';\n if (state.targets.size === 0) return 'no-event';\n const controls = /^(input|select|textarea|option|label|canvas|video|audio|iframe|embed|object)$/;\n for (const node of state.path) {\n if (controls.test(node.localName) || node.isContentEditable) return 'control';\n if (node.hasAttribute('popovertarget') || node.hasAttribute('commandfor')) return 'control';\n if (node.localName === 'a' && node.hasAttribute('href')) {\n if (node.hasAttribute('download') || (node.target && node.target !== '_self')) return 'new-window';\n if (!/^https?:$/.test(node.protocol)) return 'external-link';\n }\n }\n for (const node of state.targets) if (node.localName.includes('-') && !node.shadowRoot) return 'closed-shadow';\n const plain = (node) => /^(body|button|summary)$/.test(node.localName) ||\n (node.localName === 'a' && node.hasAttribute('href')) ||\n (node.localName === 'input' && /^(button|submit|reset)$/i.test(node.type || ''));\n if (active && active !== state.focus && !plain(active)) return 'focus';\n return undefined;\n}";
|
|
30
62
|
/**
|
|
31
63
|
* Snapshot before an action, left in `window.__bdgEffects`: the messages
|
|
32
|
-
* shown, and a MutationObserver (on the document,
|
|
33
|
-
* any shadow root attached while watching, which
|
|
34
|
-
* counting changes other than
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
64
|
+
* and loading indicators shown, and a MutationObserver (on the document,
|
|
65
|
+
* its open shadow roots and any shadow root attached while watching, which
|
|
66
|
+
* also counts as a change) counting changes other than
|
|
67
|
+
* {@link CHURN_ONLY_JS}, keeping the elements added and removed and the
|
|
68
|
+
* times of {@link STRUCTURAL_CHANGE_JS} bursts. Capture listeners record
|
|
69
|
+
* which elements the action's events reached, the first key press's target,
|
|
70
|
+
* copy/cut events and the scroll position at the first press (a click
|
|
71
|
+
* scrolls its target into view first). The watch stops itself after
|
|
72
|
+
* {@link MAX_WATCH_MS}, so a snapshot that ran late (after a navigation,
|
|
73
|
+
* with nobody reading it) leaves nothing behind. Evaluates to the URL and
|
|
74
|
+
* the messages.
|
|
40
75
|
*/
|
|
41
76
|
export declare const EFFECTS_START_SCRIPT: string;
|
|
42
77
|
/**
|
|
43
|
-
* Read after an action
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
78
|
+
* Read after an action, called with `(stop, shown)`: `stop` also stops
|
|
79
|
+
* watching, `shown` lists the elements the action showed
|
|
80
|
+
* ({@link SHOWN_ELEMENTS_JS}). Returns the URL, the messages shown and,
|
|
81
|
+
* when the snapshot is still there (same document), the number of changes
|
|
82
|
+
* counted (plus one when the page scrolled after the press), why "no
|
|
83
|
+
* effect" could not be claimed ({@link UNCERTAIN_JS}) and whether the page
|
|
84
|
+
* is still working ({@link SETTLE_JS}). `fresh` means a new document
|
|
85
|
+
* (everything shown is new).
|
|
48
86
|
*/
|
|
49
87
|
export declare const EFFECTS_READ_SCRIPT: string;
|
|
50
88
|
/** Stops the watch {@link EFFECTS_START_SCRIPT} left (when no read stopped it) */
|