browser-debugger-cli 0.15.0 → 0.16.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/.claude/skills/bdg/SKILL.md +2 -1
- package/dist/cdp/methodTarget.d.ts +92 -0
- package/dist/cdp/methodTarget.js +159 -0
- package/dist/cdp/protocol.d.ts +16 -1
- package/dist/cdp/protocol.js +21 -0
- package/dist/cdp/schema.d.ts +55 -1
- package/dist/cdp/schema.js +134 -25
- package/dist/cdp/types.d.ts +3 -1
- package/dist/commands/cdp.d.ts +38 -1
- package/dist/commands/cdp.js +200 -133
- package/dist/commands/cleanup.js +18 -4
- package/dist/commands/dom/formInteraction.js +8 -4
- package/dist/commands/dom/helpers/index.d.ts +4 -4
- package/dist/commands/dom/helpers/index.js +3 -3
- package/dist/commands/dom/helpers/query.d.ts +2 -2
- package/dist/commands/dom/helpers/query.js +2 -2
- package/dist/commands/dom/helpers/screenshot.d.ts +21 -26
- package/dist/commands/dom/helpers/screenshot.js +50 -668
- package/dist/commands/dom/screenshot.js +56 -36
- package/dist/commands/optionBehaviors.js +18 -8
- package/dist/commands/shared/CommandRunner.d.ts +5 -0
- package/dist/commands/shared/CommandRunner.js +18 -3
- package/dist/commands/shared/interrupt.d.ts +40 -0
- package/dist/commands/shared/interrupt.js +73 -0
- package/dist/commands/shared/optionTypes.d.ts +2 -0
- package/dist/commands/shared/startHelpers.d.ts +26 -3
- package/dist/commands/shared/startHelpers.js +145 -23
- package/dist/commands/types.d.ts +5 -0
- package/dist/connection/cdp.js +1 -16
- package/dist/connection/chromeIdentity.d.ts +24 -5
- package/dist/connection/chromeIdentity.js +53 -22
- package/dist/connection/launcher.d.ts +34 -1
- package/dist/connection/launcher.js +98 -10
- package/dist/connection/typed-cdp.d.ts +3 -2
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/daemon/SessionController.d.ts +10 -5
- package/dist/daemon/SessionController.js +15 -8
- package/dist/daemon/ipcServer.js +1 -1
- package/dist/daemon/launcher.d.ts +5 -0
- package/dist/daemon/launcher.js +8 -1
- package/dist/daemon/session/Session.d.ts +5 -1
- package/dist/daemon/session/Session.js +9 -8
- package/dist/daemon/session/TelemetryStore.d.ts +5 -0
- package/dist/daemon/session/TelemetryStore.js +4 -0
- package/dist/daemon/session/captureGate.d.ts +59 -0
- package/dist/daemon/session/captureGate.js +96 -0
- package/dist/daemon/session/chromeConnection.d.ts +16 -1
- package/dist/daemon/session/chromeConnection.js +34 -4
- package/dist/daemon/session/collectors.d.ts +15 -0
- package/dist/daemon/session/collectors.js +39 -2
- package/dist/daemon/session/commandRegistry.d.ts +14 -1
- package/dist/daemon/session/commandRegistry.js +46 -11
- package/dist/daemon/session/downloads.d.ts +32 -0
- package/dist/daemon/session/downloads.js +96 -0
- package/dist/daemon/session/interactions.d.ts +3 -2
- package/dist/daemon/session/interactions.js +7 -2
- package/dist/daemon/session/plugins.js +6 -0
- package/dist/daemon.js +12843 -11482
- package/dist/errors/CommandError.d.ts +2 -0
- package/dist/errors/issues.d.ts +1 -1
- package/dist/errors/messages.d.ts +58 -0
- package/dist/errors/messages.js +112 -0
- package/dist/index.js +999 -1020
- package/dist/ipc/client.d.ts +14 -1
- package/dist/ipc/client.js +21 -4
- package/dist/ipc/protocol/commands.d.ts +32 -2
- package/dist/ipc/protocol/commands.js +1 -0
- package/dist/ipc/protocol/domTypes.d.ts +24 -1
- package/dist/ipc/session/queries.d.ts +3 -0
- package/dist/ipc/session/types.d.ts +5 -0
- package/dist/ipc/transport/IPCError.d.ts +9 -0
- package/dist/ipc/transport/IPCError.js +12 -0
- package/dist/ipc/transport/errors.d.ts +2 -1
- package/dist/ipc/transport/errors.js +4 -1
- package/dist/ipc/transport/index.d.ts +4 -2
- package/dist/ipc/transport/index.js +13 -3
- package/dist/runtime/dom/actionEffects.d.ts +48 -9
- package/dist/runtime/dom/actionEffects.js +269 -34
- package/dist/runtime/dom/actionEffectsScripts.d.ts +45 -0
- package/dist/runtime/dom/actionEffectsScripts.js +101 -2
- package/dist/runtime/dom/captureArea.d.ts +35 -0
- package/dist/runtime/dom/captureArea.js +203 -0
- package/dist/runtime/dom/elementInfo.d.ts +10 -8
- package/dist/runtime/dom/elementInfo.js +8 -6
- package/dist/runtime/dom/formDiscovery.d.ts +1 -1
- package/dist/runtime/page/bdgWorld.d.ts +9 -0
- package/dist/runtime/page/bdgWorld.js +11 -0
- package/dist/runtime/page/captureEmulation.d.ts +119 -0
- package/dist/runtime/page/captureEmulation.js +189 -0
- package/dist/runtime/page/captureScroll.d.ts +24 -0
- package/dist/runtime/page/captureScroll.js +124 -0
- package/dist/runtime/page/screenshot.d.ts +41 -0
- package/dist/runtime/page/screenshot.js +394 -0
- package/dist/session/paths.d.ts +14 -0
- package/dist/session/paths.js +25 -0
- package/dist/telemetry/downloads.d.ts +127 -0
- package/dist/telemetry/downloads.js +265 -0
- package/dist/telemetry/har/builder.js +22 -7
- package/dist/telemetry/har/sanitize.d.ts +7 -3
- package/dist/telemetry/har/sanitize.js +52 -6
- package/dist/telemetry/har/sanitizeBody.d.ts +47 -7
- package/dist/telemetry/har/sanitizeBody.js +429 -56
- package/dist/telemetry/har/types.d.ts +2 -0
- package/dist/telemetry/network.d.ts +4 -4
- package/dist/telemetry/network.js +38 -4
- package/dist/telemetry/networkRetention.d.ts +35 -14
- package/dist/telemetry/networkRetention.js +62 -26
- package/dist/types.d.ts +9 -14
- package/dist/ui/OutputBuilder.d.ts +3 -2
- package/dist/ui/OutputBuilder.js +4 -3
- package/dist/ui/formatters/cdp.d.ts +32 -9
- package/dist/ui/formatters/cdp.js +77 -6
- package/dist/ui/formatters/details.js +7 -15
- package/dist/ui/formatters/preview.d.ts +2 -0
- package/dist/ui/formatters/preview.js +7 -1
- package/dist/ui/formatters/status.js +6 -1
- package/dist/ui/formatting.d.ts +7 -0
- package/dist/ui/formatting.js +13 -0
- package/dist/ui/logging/logger.d.ts +1 -1
- package/dist/ui/messages/chrome.d.ts +13 -0
- package/dist/ui/messages/chrome.js +26 -0
- package/dist/ui/messages/commands.d.ts +71 -3
- package/dist/ui/messages/commands.js +98 -3
- package/dist/ui/messages/networkMessages.d.ts +24 -5
- package/dist/ui/messages/networkMessages.js +31 -8
- package/dist/utils/async.d.ts +3 -2
- package/dist/utils/async.js +16 -3
- package/dist/utils/http.d.ts +11 -4
- package/dist/utils/http.js +5 -3
- package/package.json +18 -4
- /package/dist/{commands/dom → runtime/page}/screenshotResize.d.ts +0 -0
- /package/dist/{commands/dom → runtime/page}/screenshotResize.js +0 -0
|
@@ -5,12 +5,17 @@
|
|
|
5
5
|
* working on the result. Costs one page script sent before the action (not
|
|
6
6
|
* waited for: CDP runs it before the action's own scripts) and one read
|
|
7
7
|
* after it, plus a second look 300 ms later when nothing seemed to happen.
|
|
8
|
+
* While watching, a timer in bdg's world notes the stalls during which the
|
|
9
|
+
* page's tasks could not run ({@link STALL_WATCH_START_SCRIPT}); they do not
|
|
10
|
+
* count as quiet time when deciding whether the DOM kept changing.
|
|
8
11
|
* Worst case, when the page does not answer (a navigation is pending, or a
|
|
9
12
|
* long script runs), the snapshot is given up after {@link START_TIMEOUT_MS}
|
|
10
|
-
* and each read after {@link READ_TIMEOUT_MS}
|
|
13
|
+
* and each read after {@link READ_TIMEOUT_MS}, plus as long again, once per
|
|
14
|
+
* action, to ask whether the page ran a long task ({@link pageAnswer}).
|
|
11
15
|
*/
|
|
12
|
-
import { EFFECTS_READ_SCRIPT, EFFECTS_START_SCRIPT, EFFECTS_STOP_SCRIPT, } from './actionEffectsScripts.js';
|
|
16
|
+
import { AWAIT_DUE_TIMERS_SCRIPT, EFFECTS_READ_SCRIPT, EFFECTS_START_SCRIPT, EFFECTS_STOP_SCRIPT, LONG_TASKS_READ_SCRIPT, START_DUE_TIMERS_SCRIPT, STALL_READ_SCRIPT, STALL_WATCH_START_SCRIPT, STALL_WATCH_STOP_SCRIPT, } from './actionEffectsScripts.js';
|
|
13
17
|
import { listenForActivity, } from './pageActivity.js';
|
|
18
|
+
import { evaluateInBdgWorld, hasBdgWorld } from '../page/bdgWorld.js';
|
|
14
19
|
import { createLogger } from '../../ui/logging/index.js';
|
|
15
20
|
import { delay, raceTimeout } from '../../utils/async.js';
|
|
16
21
|
import { getErrorMessage } from '../../utils/errors.js';
|
|
@@ -21,21 +26,53 @@ const MAX_NEW_MESSAGES = 3;
|
|
|
21
26
|
const MAX_SHOWN_ELEMENTS = 3;
|
|
22
27
|
/**
|
|
23
28
|
* 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}
|
|
29
|
+
* within {@link BUSY_WINDOW_MS}, the last within {@link BUSY_RECENT_MS} of
|
|
30
|
+
* quiet time ({@link quietMs})
|
|
25
31
|
*/
|
|
26
32
|
const BUSY_BURSTS = 2;
|
|
27
33
|
const BUSY_WINDOW_MS = 500;
|
|
28
34
|
const BUSY_RECENT_MS = 150;
|
|
29
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* Quiet time since a single burst under which the DOM still looks busy (ms):
|
|
37
|
+
* the page stalled for most of the time since, so its next change could not
|
|
38
|
+
* come. Higher, a short task after a single render (garbage collection, a
|
|
39
|
+
* layout) would cost every such click a second look.
|
|
40
|
+
*/
|
|
41
|
+
const LONE_BURST_QUIET_MS = 75;
|
|
42
|
+
/**
|
|
43
|
+
* Second look at a DOM that looked busy; it is still changing when it
|
|
44
|
+
* changed again and was never quiet longer than {@link BUSY_RECENT_MS}
|
|
45
|
+
* until then (ms)
|
|
46
|
+
*/
|
|
30
47
|
const STILL_CHANGING_RECHECK_MS = 250;
|
|
31
48
|
/** Resource types of pending requests that mean more content is coming */
|
|
32
49
|
const CONTENT_REQUEST_TYPES = new Set(['Document', 'XHR', 'Fetch', 'Script']);
|
|
33
50
|
/** Longest message text reported */
|
|
34
51
|
const MAX_MESSAGE_LENGTH = 120;
|
|
52
|
+
/**
|
|
53
|
+
* How long reading the stalls ({@link STALL_READ_SCRIPT}) may take after a
|
|
54
|
+
* read whose bursts make the stalls matter; without an answer, none are
|
|
55
|
+
* known
|
|
56
|
+
*/
|
|
57
|
+
const STALLS_READ_TIMEOUT_MS = 100;
|
|
35
58
|
/** How long collecting waits for the snapshot taken before the action */
|
|
36
59
|
const START_TIMEOUT_MS = 200;
|
|
37
|
-
/**
|
|
60
|
+
/**
|
|
61
|
+
* How long a read after the action may take before its part is skipped. A
|
|
62
|
+
* read has up to three steps ({@link letDueTimersRun}): setting a timer (up
|
|
63
|
+
* to this), waiting for it (up to {@link DUE_TIMERS_TIMEOUT_MS}) and reading
|
|
64
|
+
* (up to this again), so about 600 ms at worst; a DOM that looks busy is
|
|
65
|
+
* read twice.
|
|
66
|
+
*/
|
|
38
67
|
const READ_TIMEOUT_MS = 250;
|
|
68
|
+
/**
|
|
69
|
+
* How long a read waits for the timer it set once the page has set it,
|
|
70
|
+
* before reading anyway: a 0 ms timer waits for the 0 ms tasks queued before
|
|
71
|
+
* it (measured: under 1 ms on an idle page, 5 ms behind a MessageChannel
|
|
72
|
+
* scheduler, 50 ms behind ten chains of 5 ms tasks), and throttled timers
|
|
73
|
+
* may not run for a second
|
|
74
|
+
*/
|
|
75
|
+
const DUE_TIMERS_TIMEOUT_MS = 100;
|
|
39
76
|
/** Second look before claiming "no effect" (late timers, animations) */
|
|
40
77
|
const NO_EFFECT_RECHECK_MS = 300;
|
|
41
78
|
/**
|
|
@@ -121,26 +158,67 @@ export function shownElements(shown, messages) {
|
|
|
121
158
|
.slice(0, MAX_SHOWN_ELEMENTS)
|
|
122
159
|
.map((element) => ({ ...element, text: cutText(element.text) }));
|
|
123
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Bursts of a read within {@link BUSY_WINDOW_MS} that may make the DOM look
|
|
163
|
+
* busy, else none: at least {@link BUSY_BURSTS} of them, or a single one
|
|
164
|
+
* older than {@link BUSY_RECENT_MS} (quiet since, unless the page stalled
|
|
165
|
+
* and its next change could not come).
|
|
166
|
+
*
|
|
167
|
+
* @param settle - Signals of the read
|
|
168
|
+
* @returns Ages of those bursts (ms), or an empty list
|
|
169
|
+
*/
|
|
170
|
+
function recentBursts(settle) {
|
|
171
|
+
const recent = (settle?.burstAges ?? []).filter((age) => age <= BUSY_WINDOW_MS);
|
|
172
|
+
if (recent.length >= BUSY_BURSTS)
|
|
173
|
+
return recent;
|
|
174
|
+
return recent.length === 1 && (recent[0] ?? 0) > BUSY_RECENT_MS ? recent : [];
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* How long the page was quiet between two moments of a read: the time
|
|
178
|
+
* between them less the stalls in it, during which the page's tasks could
|
|
179
|
+
* not run, so it could not change the DOM either.
|
|
180
|
+
*
|
|
181
|
+
* @param fromAge - Earlier moment, as an age at the read (ms)
|
|
182
|
+
* @param toAge - Later moment, as an age at the read (ms)
|
|
183
|
+
* @param stalls - Stalls of the read (they do not overlap)
|
|
184
|
+
* @returns Quiet time (ms)
|
|
185
|
+
*/
|
|
186
|
+
export function quietMs(fromAge, toAge, stalls = []) {
|
|
187
|
+
const stalled = stalls.reduce((sum, [began, ended]) => sum + Math.max(0, Math.min(fromAge, began) - Math.max(toAge, ended)), 0);
|
|
188
|
+
return fromAge - toAge - stalled;
|
|
189
|
+
}
|
|
124
190
|
/**
|
|
125
191
|
* Whether a read's DOM looks busy, worth a second look: at least
|
|
126
192
|
* {@link BUSY_BURSTS} bursts of structural changes within
|
|
127
|
-
* {@link BUSY_WINDOW_MS},
|
|
128
|
-
*
|
|
193
|
+
* {@link BUSY_WINDOW_MS}, and quiet for at most {@link BUSY_RECENT_MS} since
|
|
194
|
+
* the last ({@link quietMs}: stalls do not count). A single burst older than
|
|
195
|
+
* that counts when the page stalled for most of the time since, quiet for
|
|
196
|
+
* at most {@link LONE_BURST_QUIET_MS}: on a renderer running the page's
|
|
197
|
+
* timers late, a page's second step may not have come by the first read.
|
|
198
|
+
* Text-only changes (clocks) and style changes (animations) are not bursts.
|
|
129
199
|
*
|
|
130
200
|
* @param settle - Signals of the read
|
|
131
201
|
* @returns True when the DOM may still be changing
|
|
132
202
|
*/
|
|
133
203
|
export function domLooksBusy(settle) {
|
|
134
|
-
|
|
204
|
+
const recent = recentBursts(settle);
|
|
205
|
+
if (recent.length === 0)
|
|
135
206
|
return false;
|
|
136
|
-
const
|
|
137
|
-
return
|
|
207
|
+
const limit = recent.length === 1 ? LONE_BURST_QUIET_MS : BUSY_RECENT_MS;
|
|
208
|
+
return quietMs(Math.min(...recent), 0, settle?.stalls) <= limit;
|
|
138
209
|
}
|
|
139
210
|
/**
|
|
140
|
-
* Whether the DOM kept changing during the second look: at least
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
211
|
+
* Whether the DOM kept changing during the second look: at least one new
|
|
212
|
+
* burst since the first read, and no quiet gap longer than
|
|
213
|
+
* {@link BUSY_RECENT_MS} from the last burst the first read saw, through the
|
|
214
|
+
* new ones, to the second read. Stalls in a gap, during which the page's
|
|
215
|
+
* tasks could not run, are not quiet ({@link quietMs}): a page whose steps
|
|
216
|
+
* come 250 ms apart around a 200 ms long task, or whose timers a starved
|
|
217
|
+
* renderer runs 150 ms late, keeps changing. A page changing every 140 ms
|
|
218
|
+
* keeps changing; changes more than 150 ms apart while the page could run,
|
|
219
|
+
* a render that ended over 150 ms before the second read and a poller
|
|
220
|
+
* updating every 300 ms do not. A short render whose last commit came within
|
|
221
|
+
* 150 ms of the second read counts as changing.
|
|
144
222
|
*
|
|
145
223
|
* @param settle - Signals of the second read
|
|
146
224
|
* @param sinceMs - Time since the first read
|
|
@@ -149,7 +227,14 @@ export function domLooksBusy(settle) {
|
|
|
149
227
|
export function domKeptChanging(settle, sinceMs) {
|
|
150
228
|
if (!settle)
|
|
151
229
|
return false;
|
|
152
|
-
|
|
230
|
+
const fresh = settle.burstAges.filter((age) => age < sinceMs);
|
|
231
|
+
if (fresh.length === 0)
|
|
232
|
+
return false;
|
|
233
|
+
const seen = settle.burstAges.filter((age) => age >= sinceMs);
|
|
234
|
+
const times = [...seen.slice(-1), ...fresh, 0];
|
|
235
|
+
return times
|
|
236
|
+
.slice(1)
|
|
237
|
+
.every((age, i) => quietMs(times[i] ?? age, age, settle.stalls) <= BUSY_RECENT_MS);
|
|
153
238
|
}
|
|
154
239
|
/**
|
|
155
240
|
* What the page was still working on when the action returned, or undefined
|
|
@@ -230,8 +315,11 @@ export function hadNoEffect(read, effects, activity) {
|
|
|
230
315
|
}
|
|
231
316
|
/**
|
|
232
317
|
* Start watching an action's effects: listen for main-frame navigations,
|
|
233
|
-
* document statuses, requests and new windows,
|
|
234
|
-
* without waiting for it.
|
|
318
|
+
* document statuses, requests and new windows, send the page snapshot
|
|
319
|
+
* without waiting for it, and start the stall watch in bdg's world. That
|
|
320
|
+
* creates bdg's world for the reads now, while the page is idle (created at
|
|
321
|
+
* the first read, it would wait for a page busy after the action and could
|
|
322
|
+
* leave the read no time to answer).
|
|
235
323
|
*
|
|
236
324
|
* @param cdp - CDP connection
|
|
237
325
|
* @returns Watch to collect from after the action
|
|
@@ -243,7 +331,9 @@ export function watchActionEffects(cdp) {
|
|
|
243
331
|
start: evaluate(cdp, EFFECTS_START_SCRIPT),
|
|
244
332
|
stopConfirmed: false,
|
|
245
333
|
unresponsive: false,
|
|
334
|
+
askedLate: false,
|
|
246
335
|
};
|
|
336
|
+
void evaluateInWorld(watch.cdp, STALL_WATCH_START_SCRIPT, false);
|
|
247
337
|
return {
|
|
248
338
|
collect: (options) => collectEffects(watch, options),
|
|
249
339
|
dispose: () => disposeWatch(watch),
|
|
@@ -283,34 +373,99 @@ async function collectEffects(watch, options) {
|
|
|
283
373
|
}
|
|
284
374
|
/**
|
|
285
375
|
* The snapshot taken before the action, waiting at most
|
|
286
|
-
* {@link START_TIMEOUT_MS}; a snapshot still
|
|
287
|
-
* navigation pending) marks the page unresponsive.
|
|
376
|
+
* {@link START_TIMEOUT_MS} ({@link pageAnswer}); a snapshot still
|
|
377
|
+
* unanswered then (and no navigation pending) marks the page unresponsive.
|
|
288
378
|
*
|
|
289
379
|
* @param watch - The action's watch
|
|
290
380
|
* @returns The snapshot, or undefined
|
|
291
381
|
*/
|
|
292
382
|
async function awaitStart(watch) {
|
|
293
|
-
const started = await
|
|
383
|
+
const started = await pageAnswer(watch, watch.start.then((value) => ({ value })), START_TIMEOUT_MS);
|
|
294
384
|
if (!started)
|
|
295
385
|
watch.unresponsive = !watch.listener.navigationPending();
|
|
296
386
|
return started?.value;
|
|
297
387
|
}
|
|
388
|
+
/**
|
|
389
|
+
* The answer of a page script within a time, or within {@link READ_TIMEOUT_MS}
|
|
390
|
+
* more when the page only answered late ({@link answeredLate}): the same
|
|
391
|
+
* script is waited for, not sent again (a read that stopped the page's watch
|
|
392
|
+
* could not be repeated).
|
|
393
|
+
*
|
|
394
|
+
* @param watch - The action's watch
|
|
395
|
+
* @param answer - The script's answer
|
|
396
|
+
* @param ms - Time it gets first
|
|
397
|
+
* @returns The answer, or undefined when the page is busy
|
|
398
|
+
*/
|
|
399
|
+
async function pageAnswer(watch, answer, ms) {
|
|
400
|
+
const first = await raceTimeout(answer, ms);
|
|
401
|
+
if (first !== undefined || !(await answeredLate(watch)))
|
|
402
|
+
return first;
|
|
403
|
+
return raceTimeout(answer, READ_TIMEOUT_MS);
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Whether a page whose script got no answer in time only answered late: it
|
|
407
|
+
* answers now, within {@link READ_TIMEOUT_MS}, and ran no long task since
|
|
408
|
+
* the watch began ({@link LONG_TASKS_READ_SCRIPT}). The renderer ran the
|
|
409
|
+
* page's tasks late (a starved machine), and CDP runs a page's scripts in
|
|
410
|
+
* the order sent, so the script that got no answer has run by now; a page
|
|
411
|
+
* that ran a long task, or still does not answer, is busy. Asked at most
|
|
412
|
+
* once per action, so a busy or starved page costs at most
|
|
413
|
+
* {@link READ_TIMEOUT_MS} more; not asked while a main-frame load is
|
|
414
|
+
* pending.
|
|
415
|
+
*
|
|
416
|
+
* @param watch - The action's watch
|
|
417
|
+
* @returns True when the page is not busy, only slow
|
|
418
|
+
*/
|
|
419
|
+
async function answeredLate(watch) {
|
|
420
|
+
if (watch.askedLate || watch.listener.navigationPending())
|
|
421
|
+
return false;
|
|
422
|
+
watch.askedLate = true;
|
|
423
|
+
const longTasks = await raceTimeout(evaluateInWorld(watch.cdp, LONG_TASKS_READ_SCRIPT, false), READ_TIMEOUT_MS);
|
|
424
|
+
const late = longTasks === 0;
|
|
425
|
+
log.debug(late
|
|
426
|
+
? 'Page answered late without a long task (a slow renderer); waiting for its answer'
|
|
427
|
+
: `Page busy (long tasks: ${longTasks ?? 'no answer'})`);
|
|
428
|
+
return late;
|
|
429
|
+
}
|
|
298
430
|
/**
|
|
299
431
|
* Whether the DOM is still changing: when the last read looked busy
|
|
300
432
|
* ({@link domLooksBusy}), a second read {@link STILL_CHANGING_RECHECK_MS}
|
|
301
|
-
* later must see it keep changing ({@link domKeptChanging}).
|
|
433
|
+
* later must see it keep changing ({@link domKeptChanging}). The time
|
|
434
|
+
* between the reads is the page's own when both have it.
|
|
302
435
|
*
|
|
303
436
|
* @param watch - The action's watch
|
|
304
437
|
* @param snapshot - Last read, if any
|
|
305
438
|
* @returns True when the DOM kept changing
|
|
306
439
|
*/
|
|
307
440
|
async function stillChanging(watch, snapshot) {
|
|
308
|
-
|
|
441
|
+
const first = snapshot?.settle;
|
|
442
|
+
if (!domLooksBusy(first))
|
|
309
443
|
return false;
|
|
310
444
|
const firstRead = Date.now();
|
|
311
445
|
await delay(STILL_CHANGING_RECHECK_MS);
|
|
312
|
-
const recheck = await readPage(watch, { stop: false, reportShown: false })
|
|
313
|
-
|
|
446
|
+
const recheck = (await readPage(watch, { stop: false, reportShown: false, stalls: true }))
|
|
447
|
+
?.settle;
|
|
448
|
+
const sinceMs = first?.at !== undefined && recheck?.at !== undefined
|
|
449
|
+
? Math.round(recheck.at - first.at)
|
|
450
|
+
: Date.now() - firstRead;
|
|
451
|
+
const changing = domKeptChanging(recheck, sinceMs);
|
|
452
|
+
log.debug(`DOM looked busy (burst ages ${first?.burstAges.join(',')} ms, ` +
|
|
453
|
+
`stalls ${describeStalls(first?.stalls)}); ` +
|
|
454
|
+
`${sinceMs} ms later ${recheck?.burstAges.join(',') ?? 'no answer'}, ` +
|
|
455
|
+
`stalls ${describeStalls(recheck?.stalls)}: ` +
|
|
456
|
+
(changing ? 'still changing' : 'settled'));
|
|
457
|
+
return changing;
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Stalls for a debug line: each as `began-ended` ms ago.
|
|
461
|
+
*
|
|
462
|
+
* @param stalls - Stalls of a read
|
|
463
|
+
* @returns Description, `none` without any
|
|
464
|
+
*/
|
|
465
|
+
function describeStalls(stalls) {
|
|
466
|
+
if (!stalls || stalls.length === 0)
|
|
467
|
+
return 'none';
|
|
468
|
+
return stalls.map(([began, ended]) => `${began}-${ended}`).join(',');
|
|
314
469
|
}
|
|
315
470
|
/**
|
|
316
471
|
* The page's work as the last read and the CDP events saw it.
|
|
@@ -359,36 +514,116 @@ function effectsOf(start, snapshot, events) {
|
|
|
359
514
|
}
|
|
360
515
|
/**
|
|
361
516
|
* Read the page after the action, unless a main-frame load is pending (the
|
|
362
|
-
* read would wait for the new page).
|
|
363
|
-
*
|
|
364
|
-
*
|
|
517
|
+
* read would wait for the new page). The page first gets to run a timer that
|
|
518
|
+
* fell due meanwhile ({@link letDueTimersRun}); the read itself does not wait
|
|
519
|
+
* for timers. A read whose bursts may make the DOM look busy, and the second
|
|
520
|
+
* look at a DOM that did (`stalls`: its first look's bursts may have left
|
|
521
|
+
* the window by then), also gets the stalls up to it ({@link withStalls}).
|
|
522
|
+
* A stopping read that answered stops the page's watch, so disposing need
|
|
523
|
+
* not. A page that did not answer in time is marked unresponsive.
|
|
365
524
|
*
|
|
366
525
|
* @param watch - The action's watch
|
|
367
|
-
* @param options - Also stop the page's watch; list shown elements
|
|
526
|
+
* @param options - Also stop the page's watch; list shown elements; always read stalls
|
|
368
527
|
* @returns The read, or undefined
|
|
369
528
|
*/
|
|
370
529
|
async function readPage(watch, options) {
|
|
371
530
|
if (watch.listener.navigationPending())
|
|
372
531
|
return undefined;
|
|
373
532
|
const expression = `(${EFFECTS_READ_SCRIPT})(${options.stop}, ${options.reportShown})`;
|
|
374
|
-
const
|
|
533
|
+
const answered = await letDueTimersRun(watch);
|
|
534
|
+
const answer = answered
|
|
535
|
+
? await pageAnswer(watch, evaluate(watch.cdp, expression).then((value) => ({ value })), READ_TIMEOUT_MS)
|
|
536
|
+
: undefined;
|
|
375
537
|
if (!answer)
|
|
376
538
|
watch.unresponsive = !watch.listener.navigationPending();
|
|
377
539
|
const snapshot = answer?.value;
|
|
378
540
|
if (options.stop && snapshot)
|
|
379
541
|
watch.stopConfirmed = true;
|
|
380
|
-
return snapshot;
|
|
542
|
+
return snapshot && withStalls(watch.cdp, snapshot, options.stalls === true);
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* A read with the stalls up to it, as ages at the read, when asked to or
|
|
546
|
+
* when it has enough recent bursts for them to matter
|
|
547
|
+
* ({@link recentBursts}); other reads (a static page) are returned as they
|
|
548
|
+
* are, without a further page script. Stalls are read right after the read,
|
|
549
|
+
* in bdg's world, within {@link STALLS_READ_TIMEOUT_MS}; page times after
|
|
550
|
+
* the read are cut off.
|
|
551
|
+
*
|
|
552
|
+
* @param cdp - CDP connection
|
|
553
|
+
* @param snapshot - The read
|
|
554
|
+
* @param always - Read stalls whatever the bursts (the second look at a busy DOM)
|
|
555
|
+
* @returns The read, with stalls when known
|
|
556
|
+
*/
|
|
557
|
+
async function withStalls(cdp, snapshot, always) {
|
|
558
|
+
const settle = snapshot.settle;
|
|
559
|
+
if (settle?.at === undefined || (!always && recentBursts(settle).length === 0))
|
|
560
|
+
return snapshot;
|
|
561
|
+
const at = settle.at;
|
|
562
|
+
const stalls = await raceTimeout(evaluateInWorld(cdp, STALL_READ_SCRIPT, false), STALLS_READ_TIMEOUT_MS);
|
|
563
|
+
if (!Array.isArray(stalls))
|
|
564
|
+
return snapshot;
|
|
565
|
+
const ages = stalls
|
|
566
|
+
.filter(([due]) => due < at)
|
|
567
|
+
.map(([due, ran]) => [Math.round(at - due), Math.max(0, Math.round(at - ran))]);
|
|
568
|
+
return { ...snapshot, settle: { ...settle, stalls: ages } };
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Let the page run a timer that fell due while it was busy, so a read sees
|
|
572
|
+
* its change: set a 0 ms timer ({@link START_DUE_TIMERS_SCRIPT}), then wait
|
|
573
|
+
* for it at most {@link DUE_TIMERS_TIMEOUT_MS}. Both run in bdg's world, whose
|
|
574
|
+
* `setTimeout` the page cannot replace or fake; its timers share the page's
|
|
575
|
+
* queue. Setting the timer has the read's {@link READ_TIMEOUT_MS}
|
|
576
|
+
* ({@link pageAnswer}); a page that does not answer by then is busy and not
|
|
577
|
+
* read. A page that answers just in time, then keeps its timer waiting the
|
|
578
|
+
* full limit and is slow to read can take about 600 ms (250 + 100 + 250)
|
|
579
|
+
* before the read is given up, more when it only answered late.
|
|
580
|
+
*
|
|
581
|
+
* @param watch - The action's watch
|
|
582
|
+
* @returns False when the page did not answer in time (busy)
|
|
583
|
+
*/
|
|
584
|
+
async function letDueTimersRun(watch) {
|
|
585
|
+
const started = await pageAnswer(watch, evaluateInWorld(watch.cdp, START_DUE_TIMERS_SCRIPT, false).then(() => true), READ_TIMEOUT_MS);
|
|
586
|
+
if (!started)
|
|
587
|
+
return false;
|
|
588
|
+
await raceTimeout(evaluateInWorld(watch.cdp, AWAIT_DUE_TIMERS_SCRIPT, true), DUE_TIMERS_TIMEOUT_MS);
|
|
589
|
+
return true;
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Evaluate one of bdg's page scripts in bdg's world for its value, failures
|
|
593
|
+
* logged.
|
|
594
|
+
*
|
|
595
|
+
* @param cdp - CDP connection
|
|
596
|
+
* @param expression - Script
|
|
597
|
+
* @param awaitPromise - Wait for the promise it returns
|
|
598
|
+
* @returns Its value, or undefined on an exception or a failed call
|
|
599
|
+
*/
|
|
600
|
+
async function evaluateInWorld(cdp, expression, awaitPromise) {
|
|
601
|
+
try {
|
|
602
|
+
const reply = await evaluateInBdgWorld(cdp, { expression, awaitPromise, returnByValue: true });
|
|
603
|
+
if (!reply.exceptionDetails)
|
|
604
|
+
return reply.result.value;
|
|
605
|
+
log.debug(`bdg world script failed: ${reply.exceptionDetails.text}`);
|
|
606
|
+
}
|
|
607
|
+
catch (error) {
|
|
608
|
+
log.debug(`bdg world script not run: ${getErrorMessage(error)}`);
|
|
609
|
+
}
|
|
610
|
+
return undefined;
|
|
381
611
|
}
|
|
382
612
|
/**
|
|
383
|
-
* Stop listening, and stop the page's watch unless a
|
|
384
|
-
* sent even when the snapshot never answered:
|
|
385
|
-
* snapshot, wherever that ran (the page also stops
|
|
386
|
-
* after 30 s).
|
|
613
|
+
* Stop listening, stop the stall watch and stop the page's watch unless a
|
|
614
|
+
* read did. The page's stop is sent even when the snapshot never answered:
|
|
615
|
+
* CDP runs it after the snapshot, wherever that ran (the page also stops
|
|
616
|
+
* watching on its own after 30 s). The stall watch is not stopped when a new
|
|
617
|
+
* document committed and bdg's world is gone with the old one: its watch
|
|
618
|
+
* went with it, and stopping would only make a new world.
|
|
387
619
|
*
|
|
388
620
|
* @param watch - The action's watch
|
|
389
621
|
*/
|
|
390
622
|
function disposeWatch(watch) {
|
|
623
|
+
const documentGone = watch.listener.events.document !== undefined && !hasBdgWorld(watch.cdp);
|
|
391
624
|
watch.listener.dispose();
|
|
625
|
+
if (!documentGone)
|
|
626
|
+
void evaluateInWorld(watch.cdp, STALL_WATCH_STOP_SCRIPT, false);
|
|
392
627
|
if (watch.stopConfirmed)
|
|
393
628
|
return;
|
|
394
629
|
void watch.cdp
|
|
@@ -74,6 +74,19 @@ export declare const UNCERTAIN_JS = "(state, active) => {\n if (state.copied) r
|
|
|
74
74
|
* the messages.
|
|
75
75
|
*/
|
|
76
76
|
export declare const EFFECTS_START_SCRIPT: string;
|
|
77
|
+
/**
|
|
78
|
+
* Page-side wait for one turn of the page's timers. A busy or descheduled
|
|
79
|
+
* renderer (a slow machine) runs a CDP read before a timer that fell due
|
|
80
|
+
* meanwhile, so a page changing every 100 ms would look like a single
|
|
81
|
+
* render; Chrome runs the earliest overdue timers before a 0 ms timer posted
|
|
82
|
+
* after them (a longer delay would wait for every due timer). That order is
|
|
83
|
+
* Chrome's scheduler behaviour, not a web standard, verified on Chrome 154.
|
|
84
|
+
*/
|
|
85
|
+
export declare const DUE_TIMERS_JS = "() => new Promise((resolve) => setTimeout(resolve, 0))";
|
|
86
|
+
/** Sets the {@link DUE_TIMERS_JS} timer, kept for {@link AWAIT_DUE_TIMERS_SCRIPT} */
|
|
87
|
+
export declare const START_DUE_TIMERS_SCRIPT = "globalThis.__bdgDueTimers = (() => new Promise((resolve) => setTimeout(resolve, 0)))(), true";
|
|
88
|
+
/** Resolves once the timer {@link START_DUE_TIMERS_SCRIPT} set has run */
|
|
89
|
+
export declare const AWAIT_DUE_TIMERS_SCRIPT = "globalThis.__bdgDueTimers";
|
|
77
90
|
/**
|
|
78
91
|
* Read after an action, called with `(stop, shown)`: `stop` also stops
|
|
79
92
|
* watching, `shown` lists the elements the action showed
|
|
@@ -85,6 +98,38 @@ export declare const EFFECTS_START_SCRIPT: string;
|
|
|
85
98
|
* (everything shown is new).
|
|
86
99
|
*/
|
|
87
100
|
export declare const EFFECTS_READ_SCRIPT: string;
|
|
101
|
+
/**
|
|
102
|
+
* Stall watch, run in bdg's world while an action's effects are watched,
|
|
103
|
+
* left in `globalThis.__bdgStalls`: a timer every {@link STALL_BEAT_MS}
|
|
104
|
+
* that notes when it ran over {@link STALL_MIN_MS} late, as a stall from
|
|
105
|
+
* when it was due to when it ran (the last {@link MAX_STALLS}). bdg's world
|
|
106
|
+
* has its own `setTimeout`, so a page that replaced it is still watched, and
|
|
107
|
+
* its timers share the page's queue: a stall is time the page's own timers
|
|
108
|
+
* could not run either (a long task, or a renderer that runs the page's
|
|
109
|
+
* tasks late). The page cannot see it, except in the main-world fallback
|
|
110
|
+
* for frame-scoped connections (no bdg world), where `__bdgStalls` is a
|
|
111
|
+
* global of the page. It also counts the page's long tasks (over 50 ms of
|
|
112
|
+
* script, style or layout), read by {@link LONG_TASKS_READ_SCRIPT}. It
|
|
113
|
+
* stops itself after {@link MAX_WATCH_MS}.
|
|
114
|
+
*/
|
|
115
|
+
export declare const STALL_WATCH_START_SCRIPT = "(() => {\n if (globalThis.__bdgStalls) globalThis.__bdgStalls.stop();\n const native = String(setTimeout).includes('[native code]');\n const watch = { stalls: [], due: 0, ticked: native, stopped: false, timer: 0, longTasks: 0 };\n const observer = typeof PerformanceObserver === 'function' &&\n (PerformanceObserver.supportedEntryTypes || []).includes('longtask')\n ? new PerformanceObserver((list) => { watch.longTasks += list.getEntries().length; })\n : null;\n if (observer) observer.observe({ type: 'longtask' });\n watch.longTasksSeen = () => {\n if (!observer) return null;\n watch.longTasks += observer.takeRecords().length;\n return watch.longTasks;\n };\n const schedule = () => {\n watch.due = performance.now() + 20;\n watch.timer = setTimeout(beat, 20);\n };\n const beat = () => {\n const now = performance.now();\n watch.ticked = true;\n if (now - watch.due > 10) {\n watch.stalls.push([watch.due, now]);\n if (watch.stalls.length > 50) watch.stalls.shift();\n }\n schedule();\n };\n watch.stop = () => {\n watch.stopped = true;\n clearTimeout(watch.timer);\n clearTimeout(expiry);\n if (observer) observer.disconnect();\n };\n const expiry = setTimeout(watch.stop, 30000);\n schedule();\n globalThis.__bdgStalls = watch;\n return true;\n})()";
|
|
116
|
+
/**
|
|
117
|
+
* Reads the stalls {@link STALL_WATCH_START_SCRIPT} noted, as page times
|
|
118
|
+
* `[due, ran]`, plus the stall still going on (its timer over
|
|
119
|
+
* {@link STALL_MIN_MS} overdue now) as `[due, now]`; that one only with the
|
|
120
|
+
* browser's own `setTimeout` or once the timer has run at least once (in the
|
|
121
|
+
* main world, where bdg's scripts run without bdg's world, a page may have
|
|
122
|
+
* replaced `setTimeout` with one that never runs). Null without a watch.
|
|
123
|
+
*/
|
|
124
|
+
export declare const STALL_READ_SCRIPT = "(() => {\n const watch = globalThis.__bdgStalls;\n if (!watch) return null;\n const now = performance.now();\n const ongoing = watch.ticked && !watch.stopped && now - watch.due > 10;\n return ongoing ? watch.stalls.concat([[watch.due, now]]) : watch.stalls;\n})()";
|
|
125
|
+
/**
|
|
126
|
+
* Reads how many long tasks the page ran since {@link STALL_WATCH_START_SCRIPT}
|
|
127
|
+
* started, also those not yet delivered to its observer. Null without a
|
|
128
|
+
* watch or without long-task support.
|
|
129
|
+
*/
|
|
130
|
+
export declare const LONG_TASKS_READ_SCRIPT = "(() => {\n const watch = globalThis.__bdgStalls;\n return watch && watch.longTasksSeen ? watch.longTasksSeen() : null;\n})()";
|
|
131
|
+
/** Stops the watch {@link STALL_WATCH_START_SCRIPT} left */
|
|
132
|
+
export declare const STALL_WATCH_STOP_SCRIPT = "if (globalThis.__bdgStalls) { globalThis.__bdgStalls.stop(); delete globalThis.__bdgStalls; }";
|
|
88
133
|
/** Stops the watch {@link EFFECTS_START_SCRIPT} left (when no read stopped it) */
|
|
89
134
|
export declare const EFFECTS_STOP_SCRIPT = "if (window.__bdgEffects) { window.__bdgEffects.stop(); delete window.__bdgEffects; }";
|
|
90
135
|
//# sourceMappingURL=actionEffectsScripts.d.ts.map
|
|
@@ -61,6 +61,12 @@ const MAX_MESSAGE_TEXT = 300;
|
|
|
61
61
|
const MAX_WATCH_MS = 30000;
|
|
62
62
|
/** Bursts of DOM changes a watch keeps (their times) */
|
|
63
63
|
const MAX_BURSTS = 20;
|
|
64
|
+
/** Delay of the stall watch's timer ({@link STALL_WATCH_START_SCRIPT}, ms) */
|
|
65
|
+
const STALL_BEAT_MS = 20;
|
|
66
|
+
/** How late the stall watch's timer must run to count as a stall (ms) */
|
|
67
|
+
const STALL_MIN_MS = 10;
|
|
68
|
+
/** Stalls a stall watch keeps */
|
|
69
|
+
const MAX_STALLS = 50;
|
|
64
70
|
/**
|
|
65
71
|
* Page-side test whether an element is shown: rendered, not aria-hidden,
|
|
66
72
|
* not `visibility: hidden` and not fully transparent.
|
|
@@ -299,14 +305,16 @@ export const UNCERTAIN_JS = `(state, active) => {
|
|
|
299
305
|
}`;
|
|
300
306
|
/**
|
|
301
307
|
* Page-side signs, at a read, that the page is still working on the
|
|
302
|
-
* action's result:
|
|
303
|
-
* newest last), and a loading indicator shown
|
|
308
|
+
* action's result: the page time of the read, how long ago each recent
|
|
309
|
+
* burst of DOM changes was (ms, newest last), and a loading indicator shown
|
|
310
|
+
* since the start (described).
|
|
304
311
|
*/
|
|
305
312
|
const SETTLE_JS = `(state) => {
|
|
306
313
|
const now = performance.now();
|
|
307
314
|
const describe = ${ELEMENT_DESCRIPTION_JS};
|
|
308
315
|
const loader = (${LOADERS_JS})().find((el) => !state.loaders.has(el));
|
|
309
316
|
return {
|
|
317
|
+
at: now,
|
|
310
318
|
burstAges: state.bursts.map((time) => Math.round(now - time)),
|
|
311
319
|
loading: loader ? describe(loader) : null
|
|
312
320
|
};
|
|
@@ -392,6 +400,19 @@ export const EFFECTS_START_SCRIPT = `(() => {
|
|
|
392
400
|
window.__bdgEffects = state;
|
|
393
401
|
return { href: location.href, messages: (${MESSAGES_JS})(ids) };
|
|
394
402
|
})()`;
|
|
403
|
+
/**
|
|
404
|
+
* Page-side wait for one turn of the page's timers. A busy or descheduled
|
|
405
|
+
* renderer (a slow machine) runs a CDP read before a timer that fell due
|
|
406
|
+
* meanwhile, so a page changing every 100 ms would look like a single
|
|
407
|
+
* render; Chrome runs the earliest overdue timers before a 0 ms timer posted
|
|
408
|
+
* after them (a longer delay would wait for every due timer). That order is
|
|
409
|
+
* Chrome's scheduler behaviour, not a web standard, verified on Chrome 154.
|
|
410
|
+
*/
|
|
411
|
+
export const DUE_TIMERS_JS = `() => new Promise((resolve) => setTimeout(resolve, 0))`;
|
|
412
|
+
/** Sets the {@link DUE_TIMERS_JS} timer, kept for {@link AWAIT_DUE_TIMERS_SCRIPT} */
|
|
413
|
+
export const START_DUE_TIMERS_SCRIPT = `globalThis.__bdgDueTimers = (${DUE_TIMERS_JS})(), true`;
|
|
414
|
+
/** Resolves once the timer {@link START_DUE_TIMERS_SCRIPT} set has run */
|
|
415
|
+
export const AWAIT_DUE_TIMERS_SCRIPT = 'globalThis.__bdgDueTimers';
|
|
395
416
|
/**
|
|
396
417
|
* Read after an action, called with `(stop, shown)`: `stop` also stops
|
|
397
418
|
* watching, `shown` lists the elements the action showed
|
|
@@ -421,6 +442,84 @@ export const EFFECTS_READ_SCRIPT = `((stop, shown) => {
|
|
|
421
442
|
shown: shown ? (${SHOWN_ELEMENTS_JS})(state) : undefined
|
|
422
443
|
};
|
|
423
444
|
})`;
|
|
445
|
+
/**
|
|
446
|
+
* Stall watch, run in bdg's world while an action's effects are watched,
|
|
447
|
+
* left in `globalThis.__bdgStalls`: a timer every {@link STALL_BEAT_MS}
|
|
448
|
+
* that notes when it ran over {@link STALL_MIN_MS} late, as a stall from
|
|
449
|
+
* when it was due to when it ran (the last {@link MAX_STALLS}). bdg's world
|
|
450
|
+
* has its own `setTimeout`, so a page that replaced it is still watched, and
|
|
451
|
+
* its timers share the page's queue: a stall is time the page's own timers
|
|
452
|
+
* could not run either (a long task, or a renderer that runs the page's
|
|
453
|
+
* tasks late). The page cannot see it, except in the main-world fallback
|
|
454
|
+
* for frame-scoped connections (no bdg world), where `__bdgStalls` is a
|
|
455
|
+
* global of the page. It also counts the page's long tasks (over 50 ms of
|
|
456
|
+
* script, style or layout), read by {@link LONG_TASKS_READ_SCRIPT}. It
|
|
457
|
+
* stops itself after {@link MAX_WATCH_MS}.
|
|
458
|
+
*/
|
|
459
|
+
export const STALL_WATCH_START_SCRIPT = `(() => {
|
|
460
|
+
if (globalThis.__bdgStalls) globalThis.__bdgStalls.stop();
|
|
461
|
+
const native = String(setTimeout).includes('[native code]');
|
|
462
|
+
const watch = { stalls: [], due: 0, ticked: native, stopped: false, timer: 0, longTasks: 0 };
|
|
463
|
+
const observer = typeof PerformanceObserver === 'function' &&
|
|
464
|
+
(PerformanceObserver.supportedEntryTypes || []).includes('longtask')
|
|
465
|
+
? new PerformanceObserver((list) => { watch.longTasks += list.getEntries().length; })
|
|
466
|
+
: null;
|
|
467
|
+
if (observer) observer.observe({ type: 'longtask' });
|
|
468
|
+
watch.longTasksSeen = () => {
|
|
469
|
+
if (!observer) return null;
|
|
470
|
+
watch.longTasks += observer.takeRecords().length;
|
|
471
|
+
return watch.longTasks;
|
|
472
|
+
};
|
|
473
|
+
const schedule = () => {
|
|
474
|
+
watch.due = performance.now() + ${STALL_BEAT_MS};
|
|
475
|
+
watch.timer = setTimeout(beat, ${STALL_BEAT_MS});
|
|
476
|
+
};
|
|
477
|
+
const beat = () => {
|
|
478
|
+
const now = performance.now();
|
|
479
|
+
watch.ticked = true;
|
|
480
|
+
if (now - watch.due > ${STALL_MIN_MS}) {
|
|
481
|
+
watch.stalls.push([watch.due, now]);
|
|
482
|
+
if (watch.stalls.length > ${MAX_STALLS}) watch.stalls.shift();
|
|
483
|
+
}
|
|
484
|
+
schedule();
|
|
485
|
+
};
|
|
486
|
+
watch.stop = () => {
|
|
487
|
+
watch.stopped = true;
|
|
488
|
+
clearTimeout(watch.timer);
|
|
489
|
+
clearTimeout(expiry);
|
|
490
|
+
if (observer) observer.disconnect();
|
|
491
|
+
};
|
|
492
|
+
const expiry = setTimeout(watch.stop, ${MAX_WATCH_MS});
|
|
493
|
+
schedule();
|
|
494
|
+
globalThis.__bdgStalls = watch;
|
|
495
|
+
return true;
|
|
496
|
+
})()`;
|
|
497
|
+
/**
|
|
498
|
+
* Reads the stalls {@link STALL_WATCH_START_SCRIPT} noted, as page times
|
|
499
|
+
* `[due, ran]`, plus the stall still going on (its timer over
|
|
500
|
+
* {@link STALL_MIN_MS} overdue now) as `[due, now]`; that one only with the
|
|
501
|
+
* browser's own `setTimeout` or once the timer has run at least once (in the
|
|
502
|
+
* main world, where bdg's scripts run without bdg's world, a page may have
|
|
503
|
+
* replaced `setTimeout` with one that never runs). Null without a watch.
|
|
504
|
+
*/
|
|
505
|
+
export const STALL_READ_SCRIPT = `(() => {
|
|
506
|
+
const watch = globalThis.__bdgStalls;
|
|
507
|
+
if (!watch) return null;
|
|
508
|
+
const now = performance.now();
|
|
509
|
+
const ongoing = watch.ticked && !watch.stopped && now - watch.due > ${STALL_MIN_MS};
|
|
510
|
+
return ongoing ? watch.stalls.concat([[watch.due, now]]) : watch.stalls;
|
|
511
|
+
})()`;
|
|
512
|
+
/**
|
|
513
|
+
* Reads how many long tasks the page ran since {@link STALL_WATCH_START_SCRIPT}
|
|
514
|
+
* started, also those not yet delivered to its observer. Null without a
|
|
515
|
+
* watch or without long-task support.
|
|
516
|
+
*/
|
|
517
|
+
export const LONG_TASKS_READ_SCRIPT = `(() => {
|
|
518
|
+
const watch = globalThis.__bdgStalls;
|
|
519
|
+
return watch && watch.longTasksSeen ? watch.longTasksSeen() : null;
|
|
520
|
+
})()`;
|
|
521
|
+
/** Stops the watch {@link STALL_WATCH_START_SCRIPT} left */
|
|
522
|
+
export const STALL_WATCH_STOP_SCRIPT = 'if (globalThis.__bdgStalls) { globalThis.__bdgStalls.stop(); delete globalThis.__bdgStalls; }';
|
|
424
523
|
/** Stops the watch {@link EFFECTS_START_SCRIPT} left (when no read stopped it) */
|
|
425
524
|
export const EFFECTS_STOP_SCRIPT = 'if (window.__bdgEffects) { window.__bdgEffects.stop(); delete window.__bdgEffects; }';
|
|
426
525
|
//# sourceMappingURL=actionEffectsScripts.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The area an element screenshot captures: its border box, grown to what
|
|
3
|
+
* the element paints beyond it (overflowing descendants, text, shadows,
|
|
4
|
+
* outline), plus any `--padding`.
|
|
5
|
+
*/
|
|
6
|
+
import type { CDPConnection } from '../../connection/cdp.js';
|
|
7
|
+
import type { ElementBounds } from '../../types.js';
|
|
8
|
+
/** An element of the page */
|
|
9
|
+
export interface ElementRef {
|
|
10
|
+
backendNodeId: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The border box of an element (padding and border included), relative to
|
|
14
|
+
* the viewport.
|
|
15
|
+
*
|
|
16
|
+
* @param cdp - Session connection
|
|
17
|
+
* @param ref - The element
|
|
18
|
+
* @returns Bounds in CSS px
|
|
19
|
+
* @throws CommandError (81) when it is not rendered or has no area
|
|
20
|
+
*/
|
|
21
|
+
export declare function getElementBounds(cdp: CDPConnection, ref: ElementRef): Promise<ElementBounds>;
|
|
22
|
+
/**
|
|
23
|
+
* Area an element screenshot captures: the border box, grown to the content
|
|
24
|
+
* that overflows it ({@link CONTENT_OVERFLOW_JS}), so floated children are
|
|
25
|
+
* not cropped away.
|
|
26
|
+
*
|
|
27
|
+
* @param cdp - Session connection
|
|
28
|
+
* @param ref - The element
|
|
29
|
+
* @param bounds - Border box (DOM.getBoxModel coordinates)
|
|
30
|
+
* @param padding - Extra space around it (CSS px)
|
|
31
|
+
* @returns The area, or the border box (with the padding) when nothing
|
|
32
|
+
* overflows (or the page cannot be asked)
|
|
33
|
+
*/
|
|
34
|
+
export declare function captureArea(cdp: CDPConnection, ref: ElementRef, bounds: ElementBounds, padding: number): Promise<ElementBounds>;
|
|
35
|
+
//# sourceMappingURL=captureArea.d.ts.map
|