browser-debugger-cli 0.12.0 → 0.13.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 +4 -4
- package/README.md +1 -0
- package/dist/commands/cdp.d.ts +22 -1
- package/dist/commands/cdp.js +100 -43
- package/dist/commands/console.d.ts +12 -0
- package/dist/commands/console.js +62 -12
- package/dist/commands/dom/DomElementResolver.d.ts +3 -1
- package/dist/commands/dom/DomElementResolver.js +10 -3
- package/dist/commands/dom/a11y.js +3 -2
- package/dist/commands/dom/eval.d.ts +3 -2
- package/dist/commands/dom/eval.js +11 -5
- package/dist/commands/dom/form.js +10 -9
- package/dist/commands/dom/formInteraction.js +8 -7
- package/dist/commands/dom/get.js +8 -8
- package/dist/commands/dom/helpers/index.d.ts +1 -1
- package/dist/commands/dom/helpers/index.js +1 -1
- package/dist/commands/dom/helpers/query.d.ts +27 -3
- package/dist/commands/dom/helpers/query.js +152 -64
- package/dist/commands/dom/helpers/screenshot.js +13 -13
- package/dist/commands/dom/index.js +4 -2
- package/dist/commands/dom/query.d.ts +19 -2
- package/dist/commands/dom/query.js +37 -6
- package/dist/commands/dom/screenshot.js +2 -1
- package/dist/commands/dom/semanticUtils.d.ts +3 -2
- package/dist/commands/dom/semanticUtils.js +40 -9
- package/dist/commands/helpJson.d.ts +82 -19
- package/dist/commands/helpJson.js +111 -40
- package/dist/commands/helpTopic.d.ts +16 -1
- package/dist/commands/helpTopic.js +59 -1
- package/dist/commands/installSkill.d.ts +15 -5
- package/dist/commands/installSkill.js +86 -16
- package/dist/commands/network/list.js +22 -12
- package/dist/commands/optionBehaviors.js +33 -11
- package/dist/commands/shared/daemonErrorHandler.d.ts +5 -2
- package/dist/commands/shared/daemonErrorHandler.js +20 -9
- package/dist/commands/shared/dataFetcher.d.ts +12 -4
- package/dist/commands/shared/dataFetcher.js +12 -4
- package/dist/commands/shared/followMode.d.ts +9 -1
- package/dist/commands/shared/followMode.js +22 -4
- package/dist/commands/shared/optionTypes.d.ts +4 -1
- package/dist/commands/shared/outputFile.js +6 -1
- package/dist/commands/start.d.ts +7 -5
- package/dist/commands/start.js +65 -21
- package/dist/commands/stop.d.ts +11 -0
- package/dist/commands/stop.js +24 -1
- package/dist/commands.js +1 -1
- package/dist/connection/cdp.d.ts +7 -0
- package/dist/connection/cdp.js +9 -0
- package/dist/connection/launcher.js +3 -2
- package/dist/daemon/SessionController.js +6 -1
- package/dist/daemon/launcher.d.ts +3 -2
- package/dist/daemon/launcher.js +47 -3
- package/dist/daemon/session/Session.d.ts +4 -1
- package/dist/daemon/session/Session.js +33 -2
- package/dist/daemon/session/TelemetryStore.d.ts +8 -1
- package/dist/daemon/session/TelemetryStore.js +13 -1
- package/dist/daemon/session/commandRegistry.js +29 -13
- package/dist/daemon/session/interactions.d.ts +2 -1
- package/dist/daemon/session/interactions.js +13 -1
- package/dist/daemon/session/plugins.js +16 -2
- package/dist/daemon/session/teardown.js +1 -1
- package/dist/daemon.js +1622 -748
- package/dist/errors/messages.d.ts +54 -11
- package/dist/errors/messages.js +109 -22
- package/dist/index.js +13733 -8796
- package/dist/ipc/client.d.ts +18 -2
- package/dist/ipc/client.js +26 -5
- package/dist/ipc/protocol/auditTypes.d.ts +8 -2
- package/dist/ipc/protocol/commands.d.ts +12 -0
- package/dist/ipc/protocol/domTypes.d.ts +12 -0
- package/dist/ipc/protocol/inspectTypes.d.ts +2 -0
- package/dist/ipc/session/types.d.ts +2 -0
- package/dist/runtime/dom/actionEffects.d.ts +5 -1
- package/dist/runtime/dom/actionEffects.js +26 -14
- package/dist/runtime/dom/audit.js +3 -2
- package/dist/runtime/dom/auditModel.js +6 -1
- package/dist/runtime/dom/auditScripts.d.ts +9 -3
- package/dist/runtime/dom/auditScripts.js +41 -5
- package/dist/runtime/dom/elementGeometry.d.ts +10 -3
- package/dist/runtime/dom/elementGeometry.js +27 -4
- package/dist/runtime/dom/elementInfo.d.ts +74 -18
- package/dist/runtime/dom/elementInfo.js +187 -40
- package/dist/runtime/dom/evalHelpers.d.ts +12 -2
- package/dist/runtime/dom/evalHelpers.js +67 -7
- package/dist/runtime/dom/formDiscovery.d.ts +6 -2
- package/dist/runtime/dom/formDiscovery.js +20 -3
- package/dist/runtime/dom/formFillHelpers/fill.js +7 -11
- package/dist/runtime/dom/formFillHelpers/pressKey.js +2 -2
- package/dist/runtime/dom/formFillHelpers/shared.d.ts +16 -10
- package/dist/runtime/dom/formFillHelpers/shared.js +19 -52
- package/dist/runtime/dom/formSubmitHelpers.js +4 -3
- package/dist/runtime/dom/frameLayout.js +1 -0
- package/dist/runtime/dom/inspect.js +5 -6
- package/dist/runtime/dom/inspectAllStyles.js +1 -0
- package/dist/runtime/dom/inspectHints.d.ts +1 -1
- package/dist/runtime/dom/inspectModel.d.ts +2 -1
- package/dist/runtime/dom/inspectModel.js +7 -3
- package/dist/runtime/dom/inspectPaintModel.d.ts +2 -0
- package/dist/runtime/dom/inspectPaintModel.js +3 -1
- package/dist/runtime/dom/inspectScripts.d.ts +29 -2
- package/dist/runtime/dom/inspectScripts.js +49 -10
- package/dist/runtime/dom/layout.js +9 -7
- package/dist/runtime/dom/reactEventHelpers.d.ts +14 -4
- package/dist/runtime/dom/reactEventHelpers.js +63 -27
- package/dist/runtime/dom/targetNode.d.ts +18 -5
- package/dist/runtime/dom/targetNode.js +268 -8
- package/dist/runtime/dom/wait.js +2 -1
- package/dist/runtime/page/bdgWorld.d.ts +57 -0
- package/dist/runtime/page/bdgWorld.js +180 -0
- package/dist/runtime/page/replacedBuiltins.d.ts +28 -0
- package/dist/runtime/page/replacedBuiltins.js +136 -0
- package/dist/session/QueryCacheManager.d.ts +4 -1
- package/dist/session/QueryCacheManager.js +5 -2
- package/dist/session/chrome.d.ts +4 -1
- package/dist/session/chrome.js +7 -1
- package/dist/session/cleanup/staleSession.d.ts +21 -4
- package/dist/session/cleanup/staleSession.js +79 -9
- package/dist/session/cleanup/userCommands.d.ts +4 -1
- package/dist/session/cleanup/userCommands.js +10 -5
- package/dist/session/daemonSocket.d.ts +10 -0
- package/dist/session/daemonSocket.js +22 -0
- package/dist/session/lastSession.d.ts +6 -3
- package/dist/session/lastSession.js +11 -5
- package/dist/session/paths.d.ts +3 -1
- package/dist/session/paths.js +5 -5
- package/dist/session/portClaims.js +4 -3
- package/dist/session/sessionList.d.ts +13 -5
- package/dist/session/sessionList.js +31 -7
- package/dist/telemetry/a11y.js +2 -2
- package/dist/telemetry/console.d.ts +2 -1
- package/dist/telemetry/console.js +30 -21
- package/dist/telemetry/pageCrash.d.ts +26 -0
- package/dist/telemetry/pageCrash.js +53 -0
- package/dist/types.d.ts +16 -0
- package/dist/ui/formatters/audit.js +14 -5
- package/dist/ui/formatters/cdp.d.ts +138 -0
- package/dist/ui/formatters/cdp.js +131 -0
- package/dist/ui/formatters/console/chronological.js +3 -1
- package/dist/ui/formatters/console/follow.d.ts +2 -1
- package/dist/ui/formatters/console/follow.js +2 -2
- package/dist/ui/formatters/console/json.d.ts +2 -2
- package/dist/ui/formatters/console/json.js +11 -5
- package/dist/ui/formatters/console/shared.d.ts +30 -0
- package/dist/ui/formatters/console/shared.js +16 -0
- package/dist/ui/formatters/console/summarize.d.ts +9 -2
- package/dist/ui/formatters/console/summarize.js +40 -9
- package/dist/ui/formatters/console.d.ts +2 -1
- package/dist/ui/formatters/console.js +7 -5
- package/dist/ui/formatters/details.js +3 -1
- package/dist/ui/formatters/dom.d.ts +1 -1
- package/dist/ui/formatters/dom.js +5 -6
- package/dist/ui/formatters/helpFormatters.js +1 -1
- package/dist/ui/formatters/inspect.js +9 -3
- package/dist/ui/formatters/installSkill.d.ts +9 -1
- package/dist/ui/formatters/installSkill.js +32 -6
- package/dist/ui/formatters/layout.js +2 -1
- package/dist/ui/formatters/networkList.d.ts +1 -1
- package/dist/ui/formatters/networkList.js +1 -2
- package/dist/ui/formatters/preview.d.ts +2 -0
- package/dist/ui/formatters/preview.js +17 -7
- package/dist/ui/formatters/sessions.d.ts +2 -2
- package/dist/ui/formatters/sessions.js +9 -2
- package/dist/ui/logging/logger.d.ts +1 -1
- package/dist/ui/messages/commands.d.ts +124 -4
- package/dist/ui/messages/commands.js +162 -7
- package/dist/ui/messages/consoleMessages.d.ts +24 -0
- package/dist/ui/messages/consoleMessages.js +32 -0
- package/dist/ui/messages/preview.d.ts +6 -0
- package/dist/ui/messages/preview.js +9 -1
- package/dist/ui/messages/session.d.ts +13 -2
- package/dist/ui/messages/session.js +22 -3
- package/dist/utils/directories.d.ts +34 -0
- package/dist/utils/directories.js +88 -0
- package/dist/utils/display.d.ts +16 -0
- package/dist/utils/display.js +42 -0
- package/dist/utils/exitCodes.d.ts +1 -0
- package/dist/utils/exitCodes.js +6 -0
- package/dist/utils/process.d.ts +12 -0
- package/dist/utils/process.js +25 -0
- package/package.json +1 -1
package/dist/ipc/client.d.ts
CHANGED
|
@@ -178,8 +178,10 @@ export declare function getNetworkHeaders(options?: {
|
|
|
178
178
|
*
|
|
179
179
|
* @param method - CDP method name (e.g., 'Network.getCookies')
|
|
180
180
|
* @param params - Optional method parameters
|
|
181
|
+
* @param options - `isolated`: a bdg page script, run in bdg's isolated world
|
|
182
|
+
* (`Runtime.evaluate`, `DOM.resolveNode`)
|
|
181
183
|
* @returns Response with CDP method result
|
|
182
|
-
* @throws Error if connection fails; CommandError (102) when the page was busy and its scripts were terminated
|
|
184
|
+
* @throws Error if connection fails; CommandError (102) when the page was busy and its scripts were terminated, (107) when the page crashed
|
|
183
185
|
*
|
|
184
186
|
* @example
|
|
185
187
|
* ```typescript
|
|
@@ -189,7 +191,21 @@ export declare function getNetworkHeaders(options?: {
|
|
|
189
191
|
* }
|
|
190
192
|
* ```
|
|
191
193
|
*/
|
|
192
|
-
export declare function callCDP(method: string, params?: Record<string, unknown
|
|
194
|
+
export declare function callCDP(method: string, params?: Record<string, unknown>, options?: {
|
|
195
|
+
isolated?: boolean;
|
|
196
|
+
}): Promise<ClientResponse<'cdp_call'>>;
|
|
197
|
+
/**
|
|
198
|
+
* Send a CDP call for one of bdg's own page scripts: `Runtime.evaluate` and
|
|
199
|
+
* `DOM.resolveNode` run in bdg's isolated world, so a page that replaced
|
|
200
|
+
* built-ins (`querySelectorAll`, `JSON.stringify`) cannot change what they
|
|
201
|
+
* find or return.
|
|
202
|
+
*
|
|
203
|
+
* @param method - CDP method name
|
|
204
|
+
* @param params - Method parameters
|
|
205
|
+
* @returns Response with the CDP method result
|
|
206
|
+
* @throws Like {@link callCDP}
|
|
207
|
+
*/
|
|
208
|
+
export declare function callBdgScript(method: string, params?: Record<string, unknown>): Promise<ClientResponse<'cdp_call'>>;
|
|
193
209
|
/**
|
|
194
210
|
* Evaluate a JavaScript expression in the active page (or one of its iframes) via the daemon.
|
|
195
211
|
*/
|
package/dist/ipc/client.js
CHANGED
|
@@ -251,8 +251,10 @@ export async function getNetworkHeaders(options) {
|
|
|
251
251
|
*
|
|
252
252
|
* @param method - CDP method name (e.g., 'Network.getCookies')
|
|
253
253
|
* @param params - Optional method parameters
|
|
254
|
+
* @param options - `isolated`: a bdg page script, run in bdg's isolated world
|
|
255
|
+
* (`Runtime.evaluate`, `DOM.resolveNode`)
|
|
254
256
|
* @returns Response with CDP method result
|
|
255
|
-
* @throws Error if connection fails; CommandError (102) when the page was busy and its scripts were terminated
|
|
257
|
+
* @throws Error if connection fails; CommandError (102) when the page was busy and its scripts were terminated, (107) when the page crashed
|
|
256
258
|
*
|
|
257
259
|
* @example
|
|
258
260
|
* ```typescript
|
|
@@ -262,13 +264,32 @@ export async function getNetworkHeaders(options) {
|
|
|
262
264
|
* }
|
|
263
265
|
* ```
|
|
264
266
|
*/
|
|
265
|
-
export async function callCDP(method, params) {
|
|
266
|
-
const response = await sendCommand('cdp_call', {
|
|
267
|
-
|
|
268
|
-
|
|
267
|
+
export async function callCDP(method, params, options = {}) {
|
|
268
|
+
const response = await sendCommand('cdp_call', {
|
|
269
|
+
method,
|
|
270
|
+
...(params && { params }),
|
|
271
|
+
...(options.isolated && { isolated: true }),
|
|
272
|
+
});
|
|
273
|
+
const fatal = [EXIT_CODES.CDP_TIMEOUT, EXIT_CODES.PAGE_CRASHED];
|
|
274
|
+
if (response.status === 'error' && response.exitCode && fatal.includes(response.exitCode)) {
|
|
275
|
+
throw new CommandError(response.error ?? `${method} failed`, response.suggestion ? { suggestion: response.suggestion } : {}, response.exitCode);
|
|
269
276
|
}
|
|
270
277
|
return response;
|
|
271
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* Send a CDP call for one of bdg's own page scripts: `Runtime.evaluate` and
|
|
281
|
+
* `DOM.resolveNode` run in bdg's isolated world, so a page that replaced
|
|
282
|
+
* built-ins (`querySelectorAll`, `JSON.stringify`) cannot change what they
|
|
283
|
+
* find or return.
|
|
284
|
+
*
|
|
285
|
+
* @param method - CDP method name
|
|
286
|
+
* @param params - Method parameters
|
|
287
|
+
* @returns Response with the CDP method result
|
|
288
|
+
* @throws Like {@link callCDP}
|
|
289
|
+
*/
|
|
290
|
+
export function callBdgScript(method, params) {
|
|
291
|
+
return callCDP(method, params, { isolated: true });
|
|
292
|
+
}
|
|
272
293
|
/**
|
|
273
294
|
* Evaluate a JavaScript expression in the active page (or one of its iframes) via the daemon.
|
|
274
295
|
*/
|
|
@@ -19,7 +19,9 @@ export interface AuditContrastItem {
|
|
|
19
19
|
size: number;
|
|
20
20
|
weight: number;
|
|
21
21
|
inView: boolean;
|
|
22
|
-
/**
|
|
22
|
+
/** Opacity of the text and its ancestors, when below 1 (the colors are faded by it) */
|
|
23
|
+
opacity?: number;
|
|
24
|
+
/** Why the ratio is approximate (blend modes, filters, an element behind or on top, out of view) */
|
|
23
25
|
approximate?: string[];
|
|
24
26
|
}
|
|
25
27
|
/** An image drawn larger than its pixels, or with another aspect ratio */
|
|
@@ -51,8 +53,10 @@ export interface AuditResult {
|
|
|
51
53
|
level: 'AA' | 'AAA';
|
|
52
54
|
/** Text holders checked */
|
|
53
55
|
checked: number;
|
|
54
|
-
/** How many are below the level */
|
|
56
|
+
/** How many are below the level (measured exactly) */
|
|
55
57
|
failing: number;
|
|
58
|
+
/** How many more look below the level but cannot be measured exactly (text over images, blend modes, layers): not listed, `dom inspect` them */
|
|
59
|
+
uncertain?: number;
|
|
56
60
|
/** The weakest ones, at most `--limit` */
|
|
57
61
|
items: AuditContrastItem[];
|
|
58
62
|
};
|
|
@@ -109,6 +113,8 @@ export interface AuditResult {
|
|
|
109
113
|
/** Identical animations this one stands for (2 or more) */
|
|
110
114
|
count?: number;
|
|
111
115
|
}>;
|
|
116
|
+
/** Visible `<canvas>` elements: scripts may animate them, which `animations` cannot see */
|
|
117
|
+
canvases?: number;
|
|
112
118
|
}
|
|
113
119
|
/** A stylesheet line where `css search` found the text */
|
|
114
120
|
export interface CssSearchMatch {
|
|
@@ -66,10 +66,14 @@ export interface SessionPeekData {
|
|
|
66
66
|
}>;
|
|
67
67
|
/** Navigation id of the page currently loaded. */
|
|
68
68
|
currentNavigationId: number;
|
|
69
|
+
/** When the page's renderer crashed (epoch ms), while it is not loaded again */
|
|
70
|
+
pageCrashedAt?: number;
|
|
69
71
|
/** Total number of network requests (for pagination). */
|
|
70
72
|
totalNetwork: number;
|
|
71
73
|
/** Total number of console messages (for pagination). */
|
|
72
74
|
totalConsole: number;
|
|
75
|
+
/** Console messages dropped at the limit (the oldest; indices start after them) */
|
|
76
|
+
droppedConsole?: number;
|
|
73
77
|
/** Whether there are more network items available. */
|
|
74
78
|
hasMoreNetwork?: boolean;
|
|
75
79
|
/** Whether there are more console items available. */
|
|
@@ -99,6 +103,12 @@ export interface CdpCallCommand {
|
|
|
99
103
|
method: string;
|
|
100
104
|
/** Optional parameters for the CDP method. */
|
|
101
105
|
params?: Record<string, unknown>;
|
|
106
|
+
/**
|
|
107
|
+
* A bdg page script: `Runtime.evaluate` and `DOM.resolveNode` run in bdg's
|
|
108
|
+
* isolated world, out of reach of built-ins the page replaced (not for
|
|
109
|
+
* `bdg cdp`, whose calls stay in the page's world)
|
|
110
|
+
*/
|
|
111
|
+
isolated?: boolean;
|
|
102
112
|
}
|
|
103
113
|
/**
|
|
104
114
|
* CDP call command response data.
|
|
@@ -183,6 +193,8 @@ export interface DomEvalData {
|
|
|
183
193
|
subtype?: string;
|
|
184
194
|
/** URL of the iframe the script ran in (with `frame`; empty when it has none) */
|
|
185
195
|
frame?: string;
|
|
196
|
+
/** Set when the page replaced built-ins bdg's copy of the result uses */
|
|
197
|
+
warning?: string;
|
|
186
198
|
}
|
|
187
199
|
/** An iframe of the page, as listed by `bdg dom frames` */
|
|
188
200
|
export interface DomFrame {
|
|
@@ -96,6 +96,8 @@ export interface ActionEffects {
|
|
|
96
96
|
settled?: false;
|
|
97
97
|
/** What the page was still working on (with `settled: false`) */
|
|
98
98
|
pending?: PendingChanges;
|
|
99
|
+
/** All built-ins the page replaced that bdg's action scripts use (when the warning mentions them) */
|
|
100
|
+
replacedBuiltins?: string[];
|
|
99
101
|
}
|
|
100
102
|
/** A filled field's value differing from the one given */
|
|
101
103
|
export interface FillValueMismatch {
|
|
@@ -165,6 +167,11 @@ export interface ClickResult extends ActionEffects {
|
|
|
165
167
|
method?: 'mouse' | 'dom';
|
|
166
168
|
/** What was done: click, double (click), right (click) or hover */
|
|
167
169
|
action?: 'click' | 'double' | 'right' | 'hover';
|
|
170
|
+
/** How far the page scrolled to bring the element into view (CSS px; absent when it did not move) */
|
|
171
|
+
scrolledBy?: {
|
|
172
|
+
x: number;
|
|
173
|
+
y: number;
|
|
174
|
+
};
|
|
168
175
|
/** Exit code for a failure */
|
|
169
176
|
exitCode?: number;
|
|
170
177
|
/** Why the DOM fallback was used (element covered or without size) */
|
|
@@ -425,6 +432,11 @@ export interface ElementLayout {
|
|
|
425
432
|
* `opacity: 0 on div#menu` (`inViewport` still says where it is)
|
|
426
433
|
*/
|
|
427
434
|
invisible?: string;
|
|
435
|
+
/**
|
|
436
|
+
* A `mask-image` on it or an ancestor, e.g. `mask-image on div.hero`: part
|
|
437
|
+
* or all of it may not show (how much is not evaluated)
|
|
438
|
+
*/
|
|
439
|
+
masked?: string;
|
|
428
440
|
/** Inside an `inert` element: shown, but a user cannot interact with it */
|
|
429
441
|
inert?: true;
|
|
430
442
|
computed: LayoutComputedStyle;
|
|
@@ -270,6 +270,8 @@ export interface InspectVisibility {
|
|
|
270
270
|
coveredBy?: string;
|
|
271
271
|
/** The cover paints nothing there: the element shows, but clicks land on the cover */
|
|
272
272
|
coverTransparent?: true;
|
|
273
|
+
/** A `mask-image` on it or an ancestor, e.g. `mask-image on div.hero` (how much it hides is not evaluated) */
|
|
274
|
+
masked?: string;
|
|
273
275
|
}
|
|
274
276
|
/** A property asked for with `--props` */
|
|
275
277
|
export interface InspectProp {
|
|
@@ -29,5 +29,7 @@ export interface PageState {
|
|
|
29
29
|
viewport?: ViewportSize;
|
|
30
30
|
/** `prefers-color-scheme` the page sees (left out when the page did not answer in time). */
|
|
31
31
|
colorScheme?: ColorScheme;
|
|
32
|
+
/** When the page's renderer crashed (epoch ms); `bdg page reload` brings it back */
|
|
33
|
+
crashedAt?: number;
|
|
32
34
|
}
|
|
33
35
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -65,6 +65,8 @@ export interface OtherActivity {
|
|
|
65
65
|
dialogs: number;
|
|
66
66
|
/** A window, tab or download was opened */
|
|
67
67
|
opened: boolean;
|
|
68
|
+
/** Console messages logged during the action */
|
|
69
|
+
consoleMessages: number;
|
|
68
70
|
}
|
|
69
71
|
/**
|
|
70
72
|
* Messages that are new after the action: all of them after a new document
|
|
@@ -147,7 +149,7 @@ export declare function pageNavigation(startHref: string | undefined, read: Read
|
|
|
147
149
|
* Whether an action had no visible effect: the page was read before and
|
|
148
150
|
* after in the same document, it counted no DOM change, nothing made the
|
|
149
151
|
* check uncertain, and no navigation, message, shown element, request,
|
|
150
|
-
* dialog
|
|
152
|
+
* dialog, new window or console message happened.
|
|
151
153
|
*
|
|
152
154
|
* @param read - Page read after the action
|
|
153
155
|
* @param effects - Navigation and messages found
|
|
@@ -159,6 +161,8 @@ export declare function hadNoEffect(read: ReadSnapshot | undefined, effects: Act
|
|
|
159
161
|
export interface CollectOptions {
|
|
160
162
|
/** Dialogs the action opened */
|
|
161
163
|
dialogs: number;
|
|
164
|
+
/** Console messages logged since the action started, so far */
|
|
165
|
+
consoleMessages: () => number;
|
|
162
166
|
/** Decide "no effect" (with a second look when nothing seemed to happen) */
|
|
163
167
|
detectNoEffect: boolean;
|
|
164
168
|
/** List the elements the action showed */
|
|
@@ -208,7 +208,7 @@ export function pageNavigation(startHref, read, events) {
|
|
|
208
208
|
* Whether an action had no visible effect: the page was read before and
|
|
209
209
|
* after in the same document, it counted no DOM change, nothing made the
|
|
210
210
|
* check uncertain, and no navigation, message, shown element, request,
|
|
211
|
-
* dialog
|
|
211
|
+
* dialog, new window or console message happened.
|
|
212
212
|
*
|
|
213
213
|
* @param read - Page read after the action
|
|
214
214
|
* @param effects - Navigation and messages found
|
|
@@ -225,7 +225,8 @@ export function hadNoEffect(read, effects, activity) {
|
|
|
225
225
|
(effects.shown ?? []).length === 0 &&
|
|
226
226
|
activity.requests === 0 &&
|
|
227
227
|
activity.dialogs === 0 &&
|
|
228
|
-
!activity.opened
|
|
228
|
+
!activity.opened &&
|
|
229
|
+
activity.consoleMessages === 0);
|
|
229
230
|
}
|
|
230
231
|
/**
|
|
231
232
|
* Start watching an action's effects: listen for main-frame navigations,
|
|
@@ -265,7 +266,11 @@ async function collectEffects(watch, options) {
|
|
|
265
266
|
const reportShown = options.reportShown === true;
|
|
266
267
|
let snapshot = await readPage(watch, { stop: false, reportShown });
|
|
267
268
|
let effects = effectsOf(start, snapshot, watch.listener.events);
|
|
268
|
-
const quiet = () => hadNoEffect(snapshot, effects, {
|
|
269
|
+
const quiet = () => hadNoEffect(snapshot, effects, {
|
|
270
|
+
...watch.listener.activity(),
|
|
271
|
+
dialogs: options.dialogs,
|
|
272
|
+
consoleMessages: options.consoleMessages(),
|
|
273
|
+
});
|
|
269
274
|
if (options.detectNoEffect && quiet()) {
|
|
270
275
|
await delay(NO_EFFECT_RECHECK_MS);
|
|
271
276
|
snapshot = await readPage(watch, { stop: true, reportShown });
|
|
@@ -325,7 +330,8 @@ function pageWork(watch, snapshot, domChanging = false) {
|
|
|
325
330
|
}
|
|
326
331
|
/**
|
|
327
332
|
* Navigation, new messages and shown elements from the snapshots and CDP
|
|
328
|
-
* events.
|
|
333
|
+
* events. Snapshots a page broke (it replaced the built-ins the snapshot
|
|
334
|
+
* script uses) give the navigation only.
|
|
329
335
|
*
|
|
330
336
|
* @param start - Snapshot before the action, if taken
|
|
331
337
|
* @param snapshot - Read after the action, if taken
|
|
@@ -334,16 +340,22 @@ function pageWork(watch, snapshot, domChanging = false) {
|
|
|
334
340
|
*/
|
|
335
341
|
function effectsOf(start, snapshot, events) {
|
|
336
342
|
const navigation = pageNavigation(start?.href, snapshot, events);
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
343
|
+
try {
|
|
344
|
+
const { messages, more } = start && snapshot
|
|
345
|
+
? newMessages(start.messages, snapshot.messages, snapshot.fresh)
|
|
346
|
+
: { messages: [], more: 0 };
|
|
347
|
+
const shown = shownElements(snapshot?.shown ?? [], messages);
|
|
348
|
+
return {
|
|
349
|
+
...(navigation && { navigation }),
|
|
350
|
+
...(messages.length > 0 && { messages }),
|
|
351
|
+
...(more > 0 && { moreMessages: more }),
|
|
352
|
+
...(shown.length > 0 && { shown }),
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
catch (error) {
|
|
356
|
+
log.debug(`Page effects not readable (the page replaced built-ins?): ${getErrorMessage(error)}`);
|
|
357
|
+
return { ...(navigation && { navigation }) };
|
|
358
|
+
}
|
|
347
359
|
}
|
|
348
360
|
/**
|
|
349
361
|
* Read the page after the action, unless a main-frame load is pending (the
|
|
@@ -6,6 +6,7 @@ import { CommandError } from '../../errors/index.js';
|
|
|
6
6
|
import { operationFailedError } from '../../errors/messages.js';
|
|
7
7
|
import { buildAudit } from './auditModel.js';
|
|
8
8
|
import { AUDIT_PAGE_JS } from './auditScripts.js';
|
|
9
|
+
import { evaluateInBdgWorld } from '../page/bdgWorld.js';
|
|
9
10
|
import { EXIT_CODES } from '../../utils/exitCodes.js';
|
|
10
11
|
/** Findings listed per check without `--limit` */
|
|
11
12
|
export const DEFAULT_AUDIT_LIMIT = 20;
|
|
@@ -18,10 +19,10 @@ export const DEFAULT_AUDIT_LIMIT = 20;
|
|
|
18
19
|
* @throws CommandError (91) when the page script fails
|
|
19
20
|
*/
|
|
20
21
|
export async function auditPage(cdp, params) {
|
|
21
|
-
const response =
|
|
22
|
+
const response = await evaluateInBdgWorld(cdp, {
|
|
22
23
|
expression: `(${AUDIT_PAGE_JS})(${JSON.stringify(params.checks)})`,
|
|
23
24
|
returnByValue: true,
|
|
24
|
-
})
|
|
25
|
+
});
|
|
25
26
|
const raw = response.result.value;
|
|
26
27
|
if (response.exceptionDetails || !raw) {
|
|
27
28
|
const err = operationFailedError('audit the page', response.exceptionDetails?.exception?.description ?? 'the page script failed');
|
|
@@ -48,6 +48,7 @@ export function buildAudit(raw, options) {
|
|
|
48
48
|
...(animation.scrollDriven && { scrollDriven: true }),
|
|
49
49
|
}))).slice(0, options.limit),
|
|
50
50
|
}),
|
|
51
|
+
...(raw.canvases && { canvases: raw.canvases }),
|
|
51
52
|
};
|
|
52
53
|
}
|
|
53
54
|
/**
|
|
@@ -59,15 +60,18 @@ export function buildAudit(raw, options) {
|
|
|
59
60
|
*/
|
|
60
61
|
function contrastFindings(raw, options) {
|
|
61
62
|
const checked = (raw.texts ?? []).map((text) => contrastItem(text, raw.canvasDark));
|
|
62
|
-
const
|
|
63
|
+
const below = checked
|
|
63
64
|
.filter((item) => item !== undefined)
|
|
64
65
|
.filter((item) => item.ratio < requiredRatio(item, options.level))
|
|
65
66
|
.sort((a, b) => a.ratio - b.ratio);
|
|
67
|
+
const failing = below.filter((item) => !item.approximate);
|
|
68
|
+
const uncertain = below.length - failing.length;
|
|
66
69
|
return {
|
|
67
70
|
level: options.level,
|
|
68
71
|
checked: checked.length,
|
|
69
72
|
failing: failing.length,
|
|
70
73
|
items: failing.slice(0, options.limit),
|
|
74
|
+
...(uncertain > 0 && { uncertain }),
|
|
71
75
|
};
|
|
72
76
|
}
|
|
73
77
|
/**
|
|
@@ -95,6 +99,7 @@ function contrastItem(text, canvasDark) {
|
|
|
95
99
|
size: pxNumber(text.fontSize) ?? 16,
|
|
96
100
|
weight: Number(text.fontWeight) || 400,
|
|
97
101
|
inView: text.inView,
|
|
102
|
+
...(contrast.opacity !== undefined && { opacity: contrast.opacity }),
|
|
98
103
|
...(contrast.approximate && { approximate: contrast.approximate }),
|
|
99
104
|
};
|
|
100
105
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* that collects what each check needs. Colors and contrast are computed in
|
|
7
7
|
* the daemon ({@link buildAudit}), the same way `dom inspect` computes them.
|
|
8
8
|
*/
|
|
9
|
-
import type
|
|
9
|
+
import { type RawBackground } from './inspectScripts.js';
|
|
10
10
|
/** Elements the walk looks at, at most */
|
|
11
11
|
export declare const AUDIT_ELEMENT_CAP = 20000;
|
|
12
12
|
/** A text holder: an element that draws text of its own */
|
|
@@ -19,7 +19,11 @@ export interface RawAuditText {
|
|
|
19
19
|
/** Backgrounds from the element up to the root, each with its own opacity */
|
|
20
20
|
backgrounds: RawBackground[];
|
|
21
21
|
opacity: number;
|
|
22
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* Why its contrast is approximate: blend modes and filters on it or an
|
|
24
|
+
* ancestor, an element painted behind or on top of it (in view), or that
|
|
25
|
+
* only its ancestors were checked (out of view, none of them paints)
|
|
26
|
+
*/
|
|
23
27
|
risks: string[];
|
|
24
28
|
/** Inside the viewport */
|
|
25
29
|
inView: boolean;
|
|
@@ -98,10 +102,12 @@ export interface RawAudit {
|
|
|
98
102
|
}>;
|
|
99
103
|
layers?: RawLayer[];
|
|
100
104
|
animations?: RawAnimation[];
|
|
105
|
+
/** Visible `<canvas>` elements (their animations are drawn by scripts, not CSS or Web Animations) */
|
|
106
|
+
canvases?: number;
|
|
101
107
|
}
|
|
102
108
|
/**
|
|
103
109
|
* Page-side audit walk. Arguments: the checks to collect for
|
|
104
110
|
* (`contrast`, `overflow`, `layers`, `animations`).
|
|
105
111
|
*/
|
|
106
|
-
export declare const AUDIT_PAGE_JS
|
|
112
|
+
export declare const AUDIT_PAGE_JS: string;
|
|
107
113
|
//# sourceMappingURL=auditScripts.d.ts.map
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
* that collects what each check needs. Colors and contrast are computed in
|
|
7
7
|
* the daemon ({@link buildAudit}), the same way `dom inspect` computes them.
|
|
8
8
|
*/
|
|
9
|
+
import { LABEL_CLASSES_JS } from './elementInfo.js';
|
|
10
|
+
import { FLAT_TREE_JS, HIT_EVERYTHING_JS, PAINT_RISKS_JS, } from './inspectScripts.js';
|
|
11
|
+
import { AUDIT_OUT_OF_VIEW_RISK } from '../../ui/messages/commands.js';
|
|
9
12
|
/** Elements the walk looks at, at most */
|
|
10
13
|
export const AUDIT_ELEMENT_CAP = 20000;
|
|
11
14
|
/**
|
|
@@ -19,7 +22,8 @@ export const AUDIT_PAGE_JS = `function (checks) {
|
|
|
19
22
|
const style = (n) => view.getComputedStyle(n);
|
|
20
23
|
const scroller = doc.scrollingElement || doc.documentElement;
|
|
21
24
|
const viewport = { width: doc.documentElement.clientWidth, height: view.innerHeight };
|
|
22
|
-
const
|
|
25
|
+
const firstClass = (n) => (${LABEL_CLASSES_JS})(n)[0];
|
|
26
|
+
const label = (n) => n.localName + (n.id ? '#' + n.id : firstClass(n) ? '.' + firstClass(n) : '');
|
|
23
27
|
const parentOf = (n) => n.assignedSlot || n.parentElement || (n.parentNode && n.parentNode.host) || null;
|
|
24
28
|
const short = (text) => text.replace(/\\s+/g, ' ').trim().slice(0, 60);
|
|
25
29
|
const ownText = (n) => Array.from(n.childNodes).filter((c) => c.nodeType === 3).map((c) => c.data).join(' ').replace(/\\s+/g, ' ').trim();
|
|
@@ -30,20 +34,42 @@ export const AUDIT_PAGE_JS = `function (checks) {
|
|
|
30
34
|
const canvasDark = /dark/.test(scheme) && (!/light/.test(scheme) || view.matchMedia('(prefers-color-scheme: dark)').matches);
|
|
31
35
|
const result = { viewport: viewport, pixelRatio: view.devicePixelRatio || 1, pageWidth: scroller.scrollWidth, canvasDark: canvasDark, walked: 0, capped: false };
|
|
32
36
|
const texts = [], wide = [], truncated = [], images = [], layers = [], scrollers = [];
|
|
37
|
+
let canvases = 0;
|
|
38
|
+
const tree = (${FLAT_TREE_JS})(view);
|
|
39
|
+
const paintRisks = ${PAINT_RISKS_JS};
|
|
40
|
+
const clear = (color) => color === 'transparent' || /^rgba\\(.*,\\s*0\\)$/.test(color) || /\\/\\s*0\\)$/.test(color);
|
|
33
41
|
const chainOf = (n) => {
|
|
34
42
|
const backgrounds = [];
|
|
35
43
|
const risks = [];
|
|
36
44
|
let opacity = 1;
|
|
45
|
+
let painted = false;
|
|
46
|
+
let solid = false;
|
|
37
47
|
for (let p = n; p && backgrounds.length < 60; p = parentOf(p)) {
|
|
38
48
|
const s = style(p);
|
|
39
49
|
const own = Number(s.opacity) || 0;
|
|
40
50
|
const background = { color: s.backgroundColor, image: s.backgroundImage !== 'none' };
|
|
41
51
|
backgrounds.push(own < 1 ? Object.assign(background, { opacity: own }) : background);
|
|
42
52
|
opacity *= own;
|
|
53
|
+
if (p !== doc.documentElement && p !== doc.body && (background.image || !clear(s.backgroundColor))) painted = true;
|
|
54
|
+
if (p !== doc.documentElement && p !== doc.body && !background.image && /^rgb\\(/.test(s.backgroundColor)) solid = true;
|
|
43
55
|
if (s.mixBlendMode !== 'normal') risks.push('mix-blend-mode ' + s.mixBlendMode + ' on ' + label(p));
|
|
44
56
|
if (s.filter !== 'none') risks.push('filter on ' + label(p));
|
|
45
57
|
}
|
|
46
|
-
return { backgrounds: backgrounds, risks: risks, opacity: opacity };
|
|
58
|
+
return { backgrounds: backgrounds, risks: risks, opacity: opacity, painted: painted, solid: solid };
|
|
59
|
+
};
|
|
60
|
+
const unhit = [];
|
|
61
|
+
const painters = [];
|
|
62
|
+
const markPaintedBehind = () => {
|
|
63
|
+
for (const text of unhit) {
|
|
64
|
+
const painter = painters.find((p) => p.el !== text.el && !p.el.contains(text.el) && !text.el.contains(p.el) &&
|
|
65
|
+
text.x >= p.left && text.x <= p.right && text.y >= p.top && text.y <= p.bottom);
|
|
66
|
+
if (painter && !text.entry.risks.includes(${JSON.stringify(AUDIT_OUT_OF_VIEW_RISK)})) text.entry.risks.push(label(painter.el) + ' under or over it (out of view, not hit-tested)');
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const risksOf = (n, slot, chain, shown) => {
|
|
70
|
+
if (!shown) return chain.painted ? chain.risks : chain.risks.concat(${JSON.stringify(AUDIT_OUT_OF_VIEW_RISK)});
|
|
71
|
+
const text = slot && n.assignedNodes({ flatten: true }).find((c) => c.nodeType === 3 && c.data.trim() !== '');
|
|
72
|
+
return paintRisks(n, tree, text ? text.parentElement || n : n);
|
|
47
73
|
};
|
|
48
74
|
const visit = (n, fixed) => {
|
|
49
75
|
if (result.walked >= ${AUDIT_ELEMENT_CAP}) { result.capped = true; return; }
|
|
@@ -57,14 +83,21 @@ export const AUDIT_PAGE_JS = `function (checks) {
|
|
|
57
83
|
const box = slot && parentOf(n) ? parentOf(n) : n;
|
|
58
84
|
const r = box.getBoundingClientRect();
|
|
59
85
|
const shown = r.width > 0 && r.height > 0 && s.visibility === 'visible';
|
|
60
|
-
const gradientText =
|
|
86
|
+
const gradientText = /rgba\\(0, 0, 0, 0\\)|transparent/.test(s.webkitTextFillColor);
|
|
61
87
|
if (shown && want('contrast') && !gradientText) {
|
|
62
88
|
const text = slot ? short(n.assignedNodes({ flatten: true }).filter((c) => c.nodeType === 3).map((c) => c.data).join(' ')) : n.shadowRoot ? '' : ownText(n);
|
|
63
89
|
if (text && Number(s.opacity) > 0 && r.width > 2 && r.height > 2) {
|
|
64
90
|
const chain = chainOf(n);
|
|
65
|
-
|
|
91
|
+
const seen = inView(r);
|
|
92
|
+
if (chain.opacity > 0) {
|
|
93
|
+
texts.push({ label: label(n), text: short(text), color: s.color, fontSize: s.fontSize, fontWeight: s.fontWeight, inView: seen, backgrounds: chain.backgrounds, opacity: chain.opacity, risks: risksOf(n, slot, chain, seen) });
|
|
94
|
+
if (!seen && !chain.solid) unhit.push({ entry: texts[texts.length - 1], el: box, x: (r.left + r.right) / 2 + view.scrollX, y: (r.top + r.bottom) / 2 + view.scrollY });
|
|
95
|
+
}
|
|
66
96
|
}
|
|
67
97
|
}
|
|
98
|
+
if (shown && want('contrast') && (/^(img|video|canvas|picture|iframe)$/.test(n.localName) || s.backgroundImage !== 'none')) {
|
|
99
|
+
painters.push({ el: n, left: r.left + view.scrollX, top: r.top + view.scrollY, right: r.right + view.scrollX, bottom: r.bottom + view.scrollY });
|
|
100
|
+
}
|
|
68
101
|
if (shown && want('overflow')) {
|
|
69
102
|
if (r.right > viewport.width + 1 && !inFixed) wide.push({ label: label(n), right: r.right + view.scrollX, width: r.width });
|
|
70
103
|
const cut = s.overflowX !== 'visible' && n.scrollWidth > n.clientWidth + 1 ? (s.textOverflow === 'ellipsis' ? 'ellipsis' : 'clip')
|
|
@@ -78,6 +111,7 @@ export const AUDIT_PAGE_JS = `function (checks) {
|
|
|
78
111
|
if (shown && want('overflow') && /^(auto|scroll)$/.test(s.overflowX) && n !== doc.documentElement && n !== doc.body && n.scrollWidth > n.clientWidth + 1) {
|
|
79
112
|
scrollers.push({ label: label(n), scrollWidth: n.scrollWidth, width: n.clientWidth });
|
|
80
113
|
}
|
|
114
|
+
if (want('animations') && shown && n.localName === 'canvas') canvases++;
|
|
81
115
|
if (want('layers') && shown && (s.position === 'fixed' || s.position === 'sticky')) {
|
|
82
116
|
layers.push({ label: label(n), position: s.position, zIndex: s.zIndex, rect: { x: r.left, y: r.top, w: r.width, h: r.height }, inView: inView(r) });
|
|
83
117
|
}
|
|
@@ -89,11 +123,13 @@ export const AUDIT_PAGE_JS = `function (checks) {
|
|
|
89
123
|
}
|
|
90
124
|
for (const c of kids) visit(c, inFixed);
|
|
91
125
|
};
|
|
92
|
-
visit(doc.documentElement, false);
|
|
126
|
+
(${HIT_EVERYTHING_JS})([doc], () => visit(doc.documentElement, false));
|
|
127
|
+
markPaintedBehind();
|
|
93
128
|
if (want('contrast')) result.texts = texts;
|
|
94
129
|
if (want('overflow')) Object.assign(result, { wide: wide, truncated: truncated, images: images, scrollers: scrollers });
|
|
95
130
|
if (want('layers')) result.layers = layers;
|
|
96
131
|
if (want('animations')) {
|
|
132
|
+
result.canvases = canvases;
|
|
97
133
|
result.animations = doc.getAnimations().filter((a) => a.playState === 'running').slice(0, 200).map((a) => {
|
|
98
134
|
const timing = a.effect && a.effect.getTiming ? a.effect.getTiming() : {};
|
|
99
135
|
const target = a.effect && a.effect.target;
|
|
@@ -16,8 +16,9 @@ export interface ElementGeometry {
|
|
|
16
16
|
rect: LayoutBox;
|
|
17
17
|
/**
|
|
18
18
|
* For a zero-size element whose children show (an inline list around
|
|
19
|
-
* floated items
|
|
20
|
-
*
|
|
19
|
+
* floated items, a `display: contents` web component whose shadow root
|
|
20
|
+
* shows a dialog): the box around those children in the flat tree, which
|
|
21
|
+
* is what can be seen of it
|
|
21
22
|
*/
|
|
22
23
|
content?: LayoutBox | null;
|
|
23
24
|
/** Area its iframes and overflow-clipping ancestors leave visible, in the same coordinates */
|
|
@@ -33,6 +34,11 @@ export interface ElementGeometry {
|
|
|
33
34
|
* e.g. `opacity: 0 on div#menu`
|
|
34
35
|
*/
|
|
35
36
|
invisible: string | null;
|
|
37
|
+
/**
|
|
38
|
+
* Masked by `mask-image` on it or an ancestor ({@link MASKED_BY_JS}), e.g.
|
|
39
|
+
* `mask-image on div.hero`: part or all of it may not show
|
|
40
|
+
*/
|
|
41
|
+
masked?: string | null;
|
|
36
42
|
/** Inside an `inert` element: shown, but not interactive */
|
|
37
43
|
inert: boolean;
|
|
38
44
|
/** Fixed to the top-level viewport (it or a container is `position: fixed`): page scroll does not move it */
|
|
@@ -126,7 +132,8 @@ export declare const CLIP_RECT_CUTS_ALL_JS = "(position, clip) => {\n if ((posi
|
|
|
126
132
|
* top-level viewport coordinates, its clip by ancestors
|
|
127
133
|
* ({@link ANCESTOR_CLIP_JS}) and by the viewports of its iframes, why it
|
|
128
134
|
* cannot be seen at all (not rendered, or inside a clipping container
|
|
129
|
-
* collapsed to zero size
|
|
135
|
+
* collapsed to zero size; a `display: contents` element is measured by
|
|
136
|
+
* its children in the flat tree and is hidden only when none shows), why a rendered one is still invisible
|
|
130
137
|
* ({@link INVISIBLE_REASON_JS}), whether it is inert (an `inert` element
|
|
131
138
|
* around it, through shadow roots) or fixed to the top-level viewport, and how
|
|
132
139
|
* far the top-level page can scroll ({@link SCROLL_RANGE_JS}) or what locks
|
|
@@ -268,12 +268,28 @@ const INVISIBLE_REASON_JS = `(el, describe) => {
|
|
|
268
268
|
}
|
|
269
269
|
return null;
|
|
270
270
|
}`;
|
|
271
|
+
/**
|
|
272
|
+
* Page-side mask over an element: the nearest of it and its ancestors in the
|
|
273
|
+
* flat tree (slots, shadow hosts, iframes) with a `mask-image`. How much the
|
|
274
|
+
* mask hides is not evaluated. Null without one.
|
|
275
|
+
*/
|
|
276
|
+
const MASKED_BY_JS = `(el, describe) => {
|
|
277
|
+
const flatParent = ${FLAT_PARENT_JS};
|
|
278
|
+
const parentOf = (n) => flatParent(n) || n.ownerDocument.defaultView.frameElement || null;
|
|
279
|
+
for (let n = el; n; n = parentOf(n)) {
|
|
280
|
+
const style = n.ownerDocument.defaultView.getComputedStyle(n);
|
|
281
|
+
const mask = style.maskImage || style.webkitMaskImage;
|
|
282
|
+
if (mask && mask !== 'none') return n === el ? 'mask-image' : 'mask-image on ' + describe(n);
|
|
283
|
+
}
|
|
284
|
+
return null;
|
|
285
|
+
}`;
|
|
271
286
|
/**
|
|
272
287
|
* Page-side measurement of an element ({@link ElementGeometry}): its box in
|
|
273
288
|
* top-level viewport coordinates, its clip by ancestors
|
|
274
289
|
* ({@link ANCESTOR_CLIP_JS}) and by the viewports of its iframes, why it
|
|
275
290
|
* cannot be seen at all (not rendered, or inside a clipping container
|
|
276
|
-
* collapsed to zero size
|
|
291
|
+
* collapsed to zero size; a `display: contents` element is measured by
|
|
292
|
+
* its children in the flat tree and is hidden only when none shows), why a rendered one is still invisible
|
|
277
293
|
* ({@link INVISIBLE_REASON_JS}), whether it is inert (an `inert` element
|
|
278
294
|
* around it, through shadow roots) or fixed to the top-level viewport, and how
|
|
279
295
|
* far the top-level page can scroll ({@link SCROLL_RANGE_JS}) or what locks
|
|
@@ -292,6 +308,7 @@ export const ELEMENT_GEOMETRY_JS = `(el) => {
|
|
|
292
308
|
const scrollRange = ${SCROLL_RANGE_JS};
|
|
293
309
|
const scrollLock = ${SCROLL_LOCK_JS};
|
|
294
310
|
const invisibleReason = ${INVISIBLE_REASON_JS};
|
|
311
|
+
const maskedBy = ${MASKED_BY_JS};
|
|
295
312
|
const describe = ${ELEMENT_DESCRIPTION_JS};
|
|
296
313
|
const reasons = ${JSON.stringify(LAYOUT_REASONS)};
|
|
297
314
|
const styleOf = (node) => node.ownerDocument.defaultView.getComputedStyle(node);
|
|
@@ -311,11 +328,16 @@ export const ELEMENT_GEOMETRY_JS = `(el) => {
|
|
|
311
328
|
}
|
|
312
329
|
return 'not rendered';
|
|
313
330
|
};
|
|
331
|
+
const flatChildren = (node) =>
|
|
332
|
+
Array.from((node.shadowRoot || node).children).flatMap((c) => {
|
|
333
|
+
const shown = c.localName === 'slot' ? c.assignedElements({ flatten: true }) : [c];
|
|
334
|
+
return shown.flatMap((s) => (styleOf(s).display === 'contents' ? flatChildren(s) : [s]));
|
|
335
|
+
});
|
|
314
336
|
const shownChildren = (node) => {
|
|
315
337
|
const s = styleOf(node);
|
|
316
338
|
if (s.overflowX !== 'visible' || s.overflowY !== 'visible') return null;
|
|
317
339
|
let union = null;
|
|
318
|
-
for (const c of
|
|
340
|
+
for (const c of flatChildren(node).slice(0, 50)) {
|
|
319
341
|
const r = c.getBoundingClientRect();
|
|
320
342
|
if (r.width <= 0 || r.height <= 0 || styleOf(c).visibility !== 'visible') continue;
|
|
321
343
|
union = union
|
|
@@ -326,7 +348,7 @@ export const ELEMENT_GEOMETRY_JS = `(el) => {
|
|
|
326
348
|
};
|
|
327
349
|
const hiddenReason = (style, box, content) => {
|
|
328
350
|
if (style.display === 'none') return 'display: none';
|
|
329
|
-
if (style.display === 'contents') return 'display: contents (no box of its own)';
|
|
351
|
+
if (style.display === 'contents') return content ? null : 'display: contents (no box of its own)';
|
|
330
352
|
if (el.getClientRects().length === 0) {
|
|
331
353
|
return el.tagName === 'OPTION' ? reasons.option : 'not rendered (an ancestor has display: none)';
|
|
332
354
|
}
|
|
@@ -386,9 +408,10 @@ export const ELEMENT_GEOMETRY_JS = `(el) => {
|
|
|
386
408
|
while (top.frameElement) top = top.parent;
|
|
387
409
|
const toBox = (r) => r && { x: r.left, y: r.top, width: r.right - r.left, height: r.bottom - r.top };
|
|
388
410
|
const invisible = hidden ? null : invisibleReason(el, describe);
|
|
411
|
+
const masked = hidden || invisible ? null : maskedBy(el, describe);
|
|
389
412
|
const lock = scrollLock(top, describe);
|
|
390
413
|
if (lock && fixedBy === top.document.body) fixed = false;
|
|
391
|
-
return { rect: toBox(rect), content: toBox(content), clip: toBox(clip), clipOverlay: overlay, clipper: clipper, hidden: hidden, invisible: invisible, inert: isInert(), fixed: fixed, sticky: !fixed && isSticky(), pageScroll: scrollRange(top), scrollLock: lock, offset: { x: x, y: y } };
|
|
414
|
+
return { rect: toBox(rect), content: toBox(content), clip: toBox(clip), clipOverlay: overlay, clipper: clipper, hidden: hidden, invisible: invisible, masked: masked, inert: isInert(), fixed: fixed, sticky: !fixed && isSticky(), pageScroll: scrollRange(top), scrollLock: lock, offset: { x: x, y: y } };
|
|
392
415
|
}`;
|
|
393
416
|
/**
|
|
394
417
|
* Edges of a box.
|