browser-debugger-cli 0.10.0 → 0.12.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.
Files changed (103) hide show
  1. package/.claude/skills/bdg/SKILL.md +268 -0
  2. package/README.md +148 -74
  3. package/dist/commands/css.d.ts +13 -0
  4. package/dist/commands/css.js +53 -0
  5. package/dist/commands/dom/audit.d.ts +14 -0
  6. package/dist/commands/dom/audit.js +87 -0
  7. package/dist/commands/dom/formInteraction.js +36 -6
  8. package/dist/commands/dom/helpers/keyAttributes.d.ts +3 -2
  9. package/dist/commands/dom/helpers/keyAttributes.js +6 -4
  10. package/dist/commands/dom/helpers/screenshot.d.ts +1 -0
  11. package/dist/commands/dom/helpers/screenshot.js +158 -38
  12. package/dist/commands/dom/index.js +4 -1
  13. package/dist/commands/dom/screenshot.js +10 -6
  14. package/dist/commands/dom/wait.js +5 -3
  15. package/dist/commands/helpJson.js +1 -1
  16. package/dist/commands/installSkill.d.ts +20 -0
  17. package/dist/commands/installSkill.js +87 -0
  18. package/dist/commands/optionBehaviors.js +21 -6
  19. package/dist/commands/page.js +7 -4
  20. package/dist/commands/peek.d.ts +7 -0
  21. package/dist/commands/peek.js +65 -23
  22. package/dist/commands/shared/optionTypes.d.ts +5 -1
  23. package/dist/commands/start.d.ts +13 -0
  24. package/dist/commands/start.js +19 -2
  25. package/dist/commands/tail.d.ts +7 -1
  26. package/dist/commands/tail.js +13 -62
  27. package/dist/commands.js +5 -0
  28. package/dist/daemon/session/commandRegistry.js +7 -1
  29. package/dist/daemon/session/plugins.js +4 -52
  30. package/dist/daemon.js +7986 -6848
  31. package/dist/errors/messages.d.ts +50 -4
  32. package/dist/errors/messages.js +94 -5
  33. package/dist/index.js +709 -190
  34. package/dist/ipc/client.d.ts +4 -0
  35. package/dist/ipc/client.js +8 -0
  36. package/dist/ipc/protocol/auditTypes.d.ts +129 -0
  37. package/dist/ipc/protocol/auditTypes.js +6 -0
  38. package/dist/ipc/protocol/commands.d.ts +23 -0
  39. package/dist/ipc/protocol/commands.js +2 -0
  40. package/dist/ipc/protocol/domTypes.d.ts +4 -0
  41. package/dist/ipc/protocol/inspectTypes.d.ts +71 -8
  42. package/dist/runtime/css/search.d.ts +39 -0
  43. package/dist/runtime/css/search.js +122 -0
  44. package/dist/runtime/dom/actionEffects.d.ts +4 -1
  45. package/dist/runtime/dom/actionEffects.js +8 -4
  46. package/dist/runtime/dom/audit.d.ts +19 -0
  47. package/dist/runtime/dom/audit.js +36 -0
  48. package/dist/runtime/dom/auditModel.d.ts +45 -0
  49. package/dist/runtime/dom/auditModel.js +215 -0
  50. package/dist/runtime/dom/auditScripts.d.ts +107 -0
  51. package/dist/runtime/dom/auditScripts.js +112 -0
  52. package/dist/runtime/dom/elementGeometry.d.ts +8 -2
  53. package/dist/runtime/dom/elementGeometry.js +24 -8
  54. package/dist/runtime/dom/elementInfo.d.ts +3 -2
  55. package/dist/runtime/dom/elementInfo.js +8 -2
  56. package/dist/runtime/dom/formFillHelpers/fill.js +2 -2
  57. package/dist/runtime/dom/inspect.d.ts +7 -0
  58. package/dist/runtime/dom/inspect.js +88 -23
  59. package/dist/runtime/dom/inspectAllStyles.d.ts +16 -4
  60. package/dist/runtime/dom/inspectAllStyles.js +89 -7
  61. package/dist/runtime/dom/inspectCascade.d.ts +19 -2
  62. package/dist/runtime/dom/inspectCascade.js +214 -44
  63. package/dist/runtime/dom/inspectCascadeModel.d.ts +8 -0
  64. package/dist/runtime/dom/inspectCascadeModel.js +108 -34
  65. package/dist/runtime/dom/inspectHints.d.ts +26 -3
  66. package/dist/runtime/dom/inspectHints.js +125 -9
  67. package/dist/runtime/dom/inspectModel.d.ts +3 -0
  68. package/dist/runtime/dom/inspectModel.js +30 -7
  69. package/dist/runtime/dom/inspectPaintModel.d.ts +48 -22
  70. package/dist/runtime/dom/inspectPaintModel.js +180 -68
  71. package/dist/runtime/dom/inspectRules.d.ts +19 -0
  72. package/dist/runtime/dom/inspectRules.js +21 -5
  73. package/dist/runtime/dom/inspectScripts.d.ts +85 -12
  74. package/dist/runtime/dom/inspectScripts.js +314 -28
  75. package/dist/runtime/dom/inspectTree.js +10 -2
  76. package/dist/runtime/dom/inspectWhyModel.d.ts +2 -1
  77. package/dist/runtime/dom/inspectWhyModel.js +52 -10
  78. package/dist/runtime/dom/layout.js +31 -9
  79. package/dist/runtime/dom/reactEventHelpers.d.ts +7 -0
  80. package/dist/runtime/dom/reactEventHelpers.js +27 -9
  81. package/dist/runtime/page/emulation.d.ts +13 -4
  82. package/dist/runtime/page/emulation.js +69 -4
  83. package/dist/runtime/page/userAgent.d.ts +17 -0
  84. package/dist/runtime/page/userAgent.js +57 -0
  85. package/dist/types.d.ts +12 -0
  86. package/dist/ui/formatters/audit.d.ts +19 -0
  87. package/dist/ui/formatters/audit.js +106 -0
  88. package/dist/ui/formatters/dom.d.ts +1 -1
  89. package/dist/ui/formatters/dom.js +6 -3
  90. package/dist/ui/formatters/inspect.js +42 -15
  91. package/dist/ui/formatters/installSkill.d.ts +11 -0
  92. package/dist/ui/formatters/installSkill.js +31 -0
  93. package/dist/ui/formatters/status.js +1 -1
  94. package/dist/ui/messages/commands.d.ts +44 -7
  95. package/dist/ui/messages/commands.js +83 -11
  96. package/dist/ui/messages/preview.d.ts +6 -0
  97. package/dist/ui/messages/preview.js +9 -1
  98. package/dist/utils/cssValues.js +36 -4
  99. package/dist/utils/decisionTrees.js +0 -5
  100. package/dist/utils/suggestions.d.ts +4 -2
  101. package/dist/utils/suggestions.js +7 -5
  102. package/dist/utils/taskMappings.js +1 -1
  103. package/package.json +4 -2
@@ -231,6 +231,10 @@ export declare function domFormDiscover(): Promise<ClientResponse<'dom_form_disc
231
231
  export declare function domListeners(params: NoType<(typeof COMMANDS)['dom_listeners']['requestSchema']>): Promise<ClientResponse<'dom_listeners'>>;
232
232
  /** Positions, sizes and visibility of elements. */
233
233
  export declare function domLayout(params: NoType<(typeof COMMANDS)['dom_layout']['requestSchema']>): Promise<ClientResponse<'dom_layout'>>;
234
+ /** Page-wide checks: contrast, overflow, layers, animations. */
235
+ export declare function domAudit(params: NoType<(typeof COMMANDS)['dom_audit']['requestSchema']>): Promise<ClientResponse<'dom_audit'>>;
236
+ /** Find text in the page's stylesheets. */
237
+ export declare function cssSearch(params: NoType<(typeof COMMANDS)['css_search']['requestSchema']>): Promise<ClientResponse<'css_search'>>;
234
238
  /** What one element looks like: styles, box, layout and child tree. */
235
239
  export declare function domInspect(params: NoType<(typeof COMMANDS)['dom_inspect']['requestSchema']>): Promise<ClientResponse<'dom_inspect'>>;
236
240
  /**
@@ -334,6 +334,14 @@ export async function domListeners(params) {
334
334
  export async function domLayout(params) {
335
335
  return sendCommand('dom_layout', params);
336
336
  }
337
+ /** Page-wide checks: contrast, overflow, layers, animations. */
338
+ export async function domAudit(params) {
339
+ return sendCommand('dom_audit', params);
340
+ }
341
+ /** Find text in the page's stylesheets. */
342
+ export async function cssSearch(params) {
343
+ return sendCommand('css_search', params);
344
+ }
337
345
  /** What one element looks like: styles, box, layout and child tree. */
338
346
  export async function domInspect(params) {
339
347
  return sendCommand('dom_inspect', params);
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Types of `bdg dom audit` (page-wide checks) and `bdg css search`.
3
+ */
4
+ /** A check `dom audit` can run */
5
+ export type AuditCheck = 'contrast' | 'overflow' | 'layers' | 'animations';
6
+ /** Every check, in the order they are shown */
7
+ export declare const AUDIT_CHECKS: readonly AuditCheck[];
8
+ /** Text below the contrast level */
9
+ export interface AuditContrastItem {
10
+ /** `tag#id` or `tag.firstClass` */
11
+ element: string;
12
+ text: string;
13
+ /** WCAG ratio, rounded down to 2 decimals */
14
+ ratio: number;
15
+ color: string;
16
+ /** Background behind the text, composited */
17
+ background: string;
18
+ /** Font size (px) and weight: large text needs less contrast */
19
+ size: number;
20
+ weight: number;
21
+ inView: boolean;
22
+ /** Why the ratio is approximate (blend modes, filters) */
23
+ approximate?: string[];
24
+ }
25
+ /** An image drawn larger than its pixels, or with another aspect ratio */
26
+ export interface AuditImage {
27
+ element: string;
28
+ natural: {
29
+ w: number;
30
+ h: number;
31
+ };
32
+ rendered: {
33
+ w: number;
34
+ h: number;
35
+ };
36
+ /** Pixels needed (rendered size × pixel ratio) over the image's pixels, the larger of width and height */
37
+ scale: number;
38
+ /** Identical findings this one stands for (2 or more) */
39
+ count?: number;
40
+ upscaled?: true;
41
+ distorted?: true;
42
+ }
43
+ /** What `dom audit` found */
44
+ export interface AuditResult {
45
+ checks: AuditCheck[];
46
+ /** Elements walked */
47
+ walked: number;
48
+ /** The walk stopped at its cap: the page has more elements */
49
+ capped?: true;
50
+ contrast?: {
51
+ level: 'AA' | 'AAA';
52
+ /** Text holders checked */
53
+ checked: number;
54
+ /** How many are below the level */
55
+ failing: number;
56
+ /** The weakest ones, at most `--limit` */
57
+ items: AuditContrastItem[];
58
+ };
59
+ overflow?: {
60
+ pageWidth: number;
61
+ viewportWidth: number;
62
+ /** The page is wider than its viewport (it scrolls sideways) */
63
+ scrollsSideways: boolean;
64
+ /** Elements reaching past the viewport's right edge (not inside a scroller), farthest first */
65
+ wide: Array<{
66
+ element: string;
67
+ right: number;
68
+ width: number;
69
+ }>;
70
+ /** Text cut off: `ellipsis`, `clamp` or `clip` */
71
+ truncated: Array<{
72
+ element: string;
73
+ text: string;
74
+ kind: string;
75
+ count?: number;
76
+ }>;
77
+ images: AuditImage[];
78
+ /** Device pixel ratio the image scale counts in (an image needs that many pixels per CSS px) */
79
+ pixelRatio: number;
80
+ /** Elements whose content scrolls sideways inside them (carousels, tab strips): fine, but cut off at first sight */
81
+ scrollers: Array<{
82
+ element: string;
83
+ scrollWidth: number;
84
+ width: number;
85
+ }>;
86
+ };
87
+ layers?: Array<{
88
+ element: string;
89
+ position: string;
90
+ zIndex: string;
91
+ /** Viewport position and size */
92
+ rect: {
93
+ x: number;
94
+ y: number;
95
+ w: number;
96
+ h: number;
97
+ };
98
+ inView: boolean;
99
+ }>;
100
+ animations?: Array<{
101
+ element: string;
102
+ name: string;
103
+ type: string;
104
+ /** Duration (ms) */
105
+ duration: number | string;
106
+ iterations: number | string;
107
+ /** Driven by scrolling, not time */
108
+ scrollDriven?: true;
109
+ /** Identical animations this one stands for (2 or more) */
110
+ count?: number;
111
+ }>;
112
+ }
113
+ /** A stylesheet line where `css search` found the text */
114
+ export interface CssSearchMatch {
115
+ /** `app.css:12`, `bootstrap.min.css:5:52628`, `<style> in index.html:40` */
116
+ source: string;
117
+ /** The rule (or line) around the match, cut to a few hundred characters */
118
+ text: string;
119
+ }
120
+ /** What `css search` found */
121
+ export interface CssSearchResult {
122
+ query: string;
123
+ /** Stylesheets searched */
124
+ sheets: number;
125
+ /** Matches found (the list may be shorter: `--limit`) */
126
+ total: number;
127
+ matches: CssSearchMatch[];
128
+ }
129
+ //# sourceMappingURL=auditTypes.d.ts.map
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Types of `bdg dom audit` (page-wide checks) and `bdg css search`.
3
+ */
4
+ /** Every check, in the order they are shown */
5
+ export const AUDIT_CHECKS = ['contrast', 'overflow', 'layers', 'animations'];
6
+ //# sourceMappingURL=auditTypes.js.map
@@ -5,6 +5,7 @@
5
5
  * Each command has a request schema (input) and response data schema (output).
6
6
  */
7
7
  import type { HintDetails } from '../../errors/notices.js';
8
+ import type { AuditCheck, AuditResult, CssSearchResult } from './auditTypes.js';
8
9
  import type { ClickResult, FillResult, LayoutResult, ListenersResult, PressKeyResult, RawFormData, ScrollResult, SubmitResult } from './domTypes.js';
9
10
  import type { InspectResult } from './inspectTypes.js';
10
11
  import type { PageState, SessionActivity } from '../session/types.js';
@@ -312,6 +313,26 @@ export interface DomLayoutCommand {
312
313
  backendNodeId?: number;
313
314
  }
314
315
  export type DomLayoutData = LayoutResult;
316
+ /**
317
+ * dom_audit: page-wide checks (contrast, overflow, layers, animations).
318
+ */
319
+ export interface DomAuditCommand {
320
+ checks: AuditCheck[];
321
+ /** WCAG level text must reach (default AA) */
322
+ level?: 'AA' | 'AAA';
323
+ /** Findings listed per check */
324
+ limit?: number;
325
+ }
326
+ export type DomAuditData = AuditResult;
327
+ /**
328
+ * css_search: find text in the page's stylesheets.
329
+ */
330
+ export interface CssSearchCommand {
331
+ query: string;
332
+ /** Matches listed at most */
333
+ limit?: number;
334
+ }
335
+ export type CssSearchData = CssSearchResult;
315
336
  /**
316
337
  * dom_inspect: what one element looks like (styles, box, layout, child tree).
317
338
  */
@@ -369,6 +390,8 @@ export type RegistryShape = {
369
390
  dom_form_discover: CommandDef<DomFormDiscoverCommand, DomFormDiscoverData>;
370
391
  dom_listeners: CommandDef<DomListenersCommand, DomListenersData>;
371
392
  dom_layout: CommandDef<DomLayoutCommand, DomLayoutData>;
393
+ dom_audit: CommandDef<DomAuditCommand, DomAuditData>;
394
+ css_search: CommandDef<CssSearchCommand, CssSearchData>;
372
395
  dom_inspect: CommandDef<DomInspectCommand, DomInspectData>;
373
396
  dom_wait: CommandDef<DomWaitCommand, DomWaitData>;
374
397
  page_navigate: CommandDef<PageNavigateCommand, PageNavigationResult>;
@@ -35,6 +35,8 @@ export const COMMANDS = {
35
35
  dom_form_discover: defineCommand(),
36
36
  dom_listeners: defineCommand(),
37
37
  dom_layout: defineCommand(),
38
+ dom_audit: defineCommand(),
39
+ css_search: defineCommand(),
38
40
  dom_inspect: defineCommand(),
39
41
  dom_wait: defineCommand(),
40
42
  };
@@ -86,6 +86,8 @@ export interface ActionEffects {
86
86
  navigation?: PageNavigation;
87
87
  /** Messages that appeared or changed (at most 3; absent when none did) */
88
88
  messages?: NewMessage[];
89
+ /** How many more new messages there were than `messages` lists */
90
+ moreMessages?: number;
89
91
  /** Elements a hover or key press showed (at most 3, outermost first; absent when none) */
90
92
  shown?: ShownElement[];
91
93
  /** "none" when the action had no visible effect: no DOM change, request or navigation */
@@ -405,6 +407,8 @@ export interface ElementLayout {
405
407
  scrollBy?: LayoutPoint;
406
408
  /** Ancestor or iframe cutting it off (scroll that container instead of the page) */
407
409
  clippedBy?: string;
410
+ /** It or a container is `position: fixed`: page scroll does not move it */
411
+ fixed?: true;
408
412
  /** Why page scroll cannot bring it fully into view: it is fixed, or beyond the page's scroll range */
409
413
  offScreenReason?: string;
410
414
  /** Topmost element at the center of its visible part, when that is another element */
@@ -14,6 +14,13 @@ export interface InspectRect {
14
14
  y: number;
15
15
  w: number;
16
16
  h: number;
17
+ /** `viewport`: x and y are in the viewport (a fixed element stays there however the page scrolls) */
18
+ in?: 'viewport';
19
+ /** Size of the box it covers on screen when a transform (rotation, skew) makes that differ; x and y are its corner */
20
+ screen?: {
21
+ w: number;
22
+ h: number;
23
+ };
17
24
  }
18
25
  /** Sides top, right, bottom, left */
19
26
  export type Sides = [CssLength, CssLength, CssLength, CssLength];
@@ -104,17 +111,29 @@ export interface InspectContrast {
104
111
  /** A background image or gradient is behind the text: the ratio uses the colors only */
105
112
  overImage?: boolean;
106
113
  /**
107
- * Opacity of the element and its ancestors (below 1): the text color is
108
- * faded by it before the ratio is taken (backgrounds inside the faded
109
- * subtree are not, so the ratio is approximate)
114
+ * Opacity of the element and its ancestors (below 1): each translucent
115
+ * element fades its background and the text over it before the ratio is
116
+ * taken
110
117
  */
111
118
  opacity?: number;
119
+ /**
120
+ * Why the ratio is approximate: `mix-blend-mode hard-light on h1`,
121
+ * `filter on div.skin-invert`, `canvas behind`, `div.overlay on top`
122
+ */
123
+ approximate?: string[];
112
124
  }
113
125
  /** Typography (for containers without text of their own: only what differs from the parent) */
114
126
  export interface InspectText {
127
+ /**
128
+ * Label of the descendant that draws most of the text when it is not the
129
+ * element (`abbr`, `slot.button__label`): the fields describe its text
130
+ */
131
+ holder?: string;
115
132
  family?: string;
116
- /** Font Chrome rendered the text with, when it is not the first family */
133
+ /** Font Chrome rendered the text with, when it is a fallback for the first family */
117
134
  rendered?: string;
135
+ /** Font a generic first family (`sans-serif`, `system-ui`) resolved to */
136
+ resolved?: string;
118
137
  /** The rendered font is a web font */
119
138
  webfont?: boolean;
120
139
  weight?: number;
@@ -132,19 +151,30 @@ export interface InspectText {
132
151
  clamp?: string;
133
152
  shadow?: string;
134
153
  features?: string;
154
+ /** The text is cut off (clipped by overflow, with or without an ellipsis, or by a line clamp) */
155
+ truncated?: true;
156
+ /** The text is painted with its background (`background-clip: text`, transparent fill): no single color, so no contrast */
157
+ gradientFill?: true;
135
158
  }
136
159
  /** A background layer */
137
160
  export type InspectFill = {
138
161
  type: 'solid';
139
162
  color: string;
140
163
  } | {
141
- type: 'gradient';
142
- value: string;
143
- } | {
144
- type: 'image';
164
+ type: 'gradient' | 'image';
145
165
  value: string;
146
166
  size?: string;
167
+ position?: string;
147
168
  };
169
+ /** How an SVG element is painted */
170
+ export interface InspectSvgPaint {
171
+ /** Fill color (hex), `none` or a paint server (`url(#grad)`) */
172
+ fill: string;
173
+ /** Stroke color, `none` or a paint server */
174
+ stroke: string;
175
+ /** Stroke width (px), when there is a stroke */
176
+ strokeWidth?: CssLength;
177
+ }
148
178
  /** A border side (or all four) */
149
179
  export interface InspectStroke {
150
180
  side: 'all' | 'top' | 'right' | 'bottom' | 'left';
@@ -184,6 +214,8 @@ export interface InspectPseudo {
184
214
  content?: string;
185
215
  display?: string;
186
216
  position?: string;
217
+ /** Offsets of a positioned one (top right bottom left) */
218
+ inset?: string;
187
219
  size?: {
188
220
  w: number;
189
221
  h: number;
@@ -212,6 +244,12 @@ export interface InspectTreeNode {
212
244
  /** `flex` or `grid` container */
213
245
  layout?: 'flex' | 'grid';
214
246
  text?: string;
247
+ /** `display: contents` (a text-only slot): no box of its own */
248
+ contents?: true;
249
+ /** Reached through a slot or a `display: contents` wrapper (`slot.label`, `div.row (contents)`) */
250
+ via?: string;
251
+ /** In the shadow root of its parent */
252
+ shadow?: true;
215
253
  /** Identical siblings this row stands for (2 or more) */
216
254
  count?: number;
217
255
  children?: InspectTreeNode[];
@@ -251,6 +289,10 @@ export interface InspectHint {
251
289
  reason: string;
252
290
  /** e.g. `use display: flex or grid on this element` */
253
291
  fix: string;
292
+ /** The longhands of a shorthand that have no effect, when the others do (`margin-top`, `margin-bottom`) */
293
+ only?: string[];
294
+ /** The custom properties that are not set (`unset-variable`) */
295
+ variables?: string[];
254
296
  /** e.g. `.hero (app.css:12)` */
255
297
  source: string;
256
298
  }
@@ -264,6 +306,8 @@ export interface InspectRule {
264
306
  computed?: string;
265
307
  /** e.g. `.btn-primary (bootstrap.min.css:5:52628)`, `style attribute` */
266
308
  source: string;
309
+ /** The rule as written (selector and declarations); a rule over 300 characters is cut to its selector and this declaration */
310
+ rule?: string;
267
311
  /** Selectors of the declarations it beats */
268
312
  overrides?: string[];
269
313
  /** Set on an ancestor this many levels up (inherited) */
@@ -286,8 +330,15 @@ export interface InspectWhyEntry {
286
330
  source: string;
287
331
  /** Specificity of the rule's selector (ids, classes, types) */
288
332
  specificity?: [number, number, number];
333
+ /** The rule as written (selector and declarations); a rule over 300 characters is cut to its selector and this declaration */
334
+ rule?: string;
289
335
  /** `applied` (wins), `overridden`, or `inherited` (from an ancestor: the winner, or one it beat there) */
290
336
  status: 'applied' | 'overridden' | 'inherited';
337
+ /**
338
+ * Why the winner changes nothing: `no effect: position is static`, or for
339
+ * an invalid `var()`, what applies instead (`falls back to the initial value`)
340
+ */
341
+ note?: string;
291
342
  important?: true;
292
343
  layer?: string;
293
344
  condition?: string;
@@ -300,6 +351,12 @@ export interface InspectWhy {
300
351
  chain: InspectWhyEntry[];
301
352
  /** Where the custom properties of the winning value are set */
302
353
  variables?: InspectVariable[];
354
+ /** Rules for the element that set it under a `@media`/`@supports` condition that does not apply now */
355
+ inactive?: Array<{
356
+ value: string;
357
+ selector: string;
358
+ condition: string;
359
+ }>;
303
360
  }
304
361
  /** A custom property a winning value uses, and where it is set */
305
362
  export interface InspectVariable {
@@ -324,6 +381,8 @@ export interface InspectResult {
324
381
  * rendered) or the first
325
382
  */
326
383
  picked?: 'first-visible' | 'first';
384
+ /** The selector named a pseudo-element (`a::after`): its element was inspected, the pseudo-element is under `pseudo` */
385
+ pseudoOf?: '::before' | '::after';
327
386
  /** `tag#id.c1.c2(+N)` */
328
387
  element: string;
329
388
  /** Its text (innerText) or form value, at most 30 characters; not for containers */
@@ -341,6 +400,8 @@ export interface InspectResult {
341
400
  * theme's
342
401
  */
343
402
  theme?: 'dark';
403
+ /** The dark preference behind `theme` comes from `page emulate`, not the system */
404
+ themeFrom?: 'emulation';
344
405
  /**
345
406
  * Running CSS transitions (their property) and animations (their name):
346
407
  * the values read are mid-way and will still change
@@ -352,6 +413,8 @@ export interface InspectResult {
352
413
  layout?: InspectLayout;
353
414
  text?: InspectText;
354
415
  fills?: InspectFill[];
416
+ /** SVG paint: `fill` and `stroke` (with its width) of an SVG element */
417
+ paint?: InspectSvgPaint;
355
418
  opacity?: number;
356
419
  blend?: string;
357
420
  strokes?: InspectStroke[];
@@ -0,0 +1,39 @@
1
+ /**
2
+ * `bdg css search <text>`: find text in the page's stylesheets, cross-origin
3
+ * ones included (CDP reads every stylesheet's text), and show the rule
4
+ * around each match with its `file:line`.
5
+ */
6
+ import type { CDPConnection } from '../../connection/cdp.js';
7
+ import type { CssSearchResult } from '../../ipc/protocol/auditTypes.js';
8
+ import type { CssSearchCommand } from '../../ipc/protocol/commands.js';
9
+ /** Matches listed without `--limit` */
10
+ export declare const DEFAULT_CSS_SEARCH_LIMIT = 20;
11
+ /**
12
+ * Search the stylesheets for a text (case-insensitive).
13
+ *
14
+ * @param cdp - CDP connection
15
+ * @param params - Text and limit
16
+ * @returns Matches with their place
17
+ */
18
+ export declare function searchStyleSheets(cdp: CDPConnection, params: CssSearchCommand): Promise<CssSearchResult>;
19
+ /**
20
+ * Where a text occurs in a stylesheet (case-insensitive): how many times,
21
+ * and for the first `limit` matches the 0-based line and column and the
22
+ * rule around it (from the end of the previous rule to the end of this one,
23
+ * whitespace collapsed, at most {@link RULE_CONTEXT} characters each side).
24
+ * Lines are counted as the search moves on, so a big sheet is read once.
25
+ *
26
+ * @param text - Stylesheet text
27
+ * @param query - Text to find
28
+ * @param limit - Matches described at most
29
+ * @returns Match count and the described matches
30
+ */
31
+ export declare function findInSheet(text: string, query: string, limit?: number): {
32
+ total: number;
33
+ matches: Array<{
34
+ line: number;
35
+ column: number;
36
+ rule: string;
37
+ }>;
38
+ };
39
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1,122 @@
1
+ /**
2
+ * `bdg css search <text>`: find text in the page's stylesheets, cross-origin
3
+ * ones included (CDP reads every stylesheet's text), and show the rule
4
+ * around each match with its `file:line`.
5
+ */
6
+ import { enableStyleDomains } from '../dom/inspect.js';
7
+ import { stylesheetPositionLabel, styleSheetHeaders } from '../dom/inspectRules.js';
8
+ import { createLogger } from '../../ui/logging/index.js';
9
+ import { getErrorMessage } from '../../utils/errors.js';
10
+ const log = createLogger('dom');
11
+ /** Matches listed without `--limit` */
12
+ export const DEFAULT_CSS_SEARCH_LIMIT = 20;
13
+ /** Characters of a rule shown around a match */
14
+ const RULE_CONTEXT = 240;
15
+ /**
16
+ * Search the stylesheets for a text (case-insensitive).
17
+ *
18
+ * @param cdp - CDP connection
19
+ * @param params - Text and limit
20
+ * @returns Matches with their place
21
+ */
22
+ export async function searchStyleSheets(cdp, params) {
23
+ await enableStyleDomains(cdp);
24
+ const headers = [...styleSheetHeaders(cdp)].filter((header) => header.origin !== 'user-agent');
25
+ const limit = params.limit ?? DEFAULT_CSS_SEARCH_LIMIT;
26
+ const texts = await Promise.all(headers.map((header) => sheetText(cdp, header.styleSheetId)));
27
+ const matches = [];
28
+ let total = 0;
29
+ headers.forEach((header, i) => {
30
+ const found = findInSheet(texts[i] ?? '', params.query, Number.POSITIVE_INFINITY);
31
+ const seen = new Set();
32
+ for (const match of found.matches) {
33
+ if (seen.has(match.rule))
34
+ continue;
35
+ seen.add(match.rule);
36
+ total++;
37
+ if (matches.length >= limit)
38
+ continue;
39
+ matches.push({
40
+ source: stylesheetPositionLabel(header, match.line, match.column),
41
+ text: match.rule,
42
+ });
43
+ }
44
+ });
45
+ return { query: params.query, sheets: headers.length, total, matches };
46
+ }
47
+ /**
48
+ * A stylesheet's text; empty when Chrome cannot give it.
49
+ *
50
+ * @param cdp - CDP connection
51
+ * @param styleSheetId - Stylesheet
52
+ * @returns Text
53
+ */
54
+ async function sheetText(cdp, styleSheetId) {
55
+ try {
56
+ const response = (await cdp.send('CSS.getStyleSheetText', {
57
+ styleSheetId,
58
+ }));
59
+ return response.text;
60
+ }
61
+ catch (error) {
62
+ log.debug(`No text for stylesheet ${styleSheetId}: ${getErrorMessage(error)}`);
63
+ return '';
64
+ }
65
+ }
66
+ /**
67
+ * Where a text occurs in a stylesheet (case-insensitive): how many times,
68
+ * and for the first `limit` matches the 0-based line and column and the
69
+ * rule around it (from the end of the previous rule to the end of this one,
70
+ * whitespace collapsed, at most {@link RULE_CONTEXT} characters each side).
71
+ * Lines are counted as the search moves on, so a big sheet is read once.
72
+ *
73
+ * @param text - Stylesheet text
74
+ * @param query - Text to find
75
+ * @param limit - Matches described at most
76
+ * @returns Match count and the described matches
77
+ */
78
+ export function findInSheet(text, query, limit = Number.POSITIVE_INFINITY) {
79
+ const matches = [];
80
+ const haystack = text.toLowerCase();
81
+ const needle = query.toLowerCase();
82
+ if (needle === '')
83
+ return { total: 0, matches };
84
+ let total = 0;
85
+ let line = 0;
86
+ let lineStart = 0;
87
+ let scanned = 0;
88
+ for (let at = haystack.indexOf(needle); at >= 0; at = haystack.indexOf(needle, at + needle.length)) {
89
+ total++;
90
+ if (matches.length >= limit)
91
+ continue;
92
+ for (let i = text.indexOf('\n', scanned); i >= 0 && i < at; i = text.indexOf('\n', i + 1)) {
93
+ line++;
94
+ lineStart = i + 1;
95
+ }
96
+ scanned = at;
97
+ matches.push({ line, column: at - lineStart, rule: ruleAround(text, at) });
98
+ }
99
+ return { total, matches };
100
+ }
101
+ /**
102
+ * The rule around a position: whole when short, else its selector and the
103
+ * declarations near the position (`.btn { … color: var(--brand); … }`),
104
+ * whitespace collapsed.
105
+ *
106
+ * @param text - Stylesheet text
107
+ * @param at - Position of the match
108
+ * @returns Rule text
109
+ */
110
+ function ruleAround(text, at) {
111
+ const ruleStart = text.lastIndexOf('}', at) + 1;
112
+ const open = text.indexOf('{', ruleStart);
113
+ const close = text.indexOf('}', at);
114
+ const ruleEnd = close < 0 ? text.length : close + 1;
115
+ const collapse = (part) => part.replace(/\s+/g, ' ').trim();
116
+ if (ruleEnd - ruleStart <= 2 * RULE_CONTEXT || open < 0 || open > at) {
117
+ return collapse(text.slice(Math.max(ruleStart, at - RULE_CONTEXT), Math.min(ruleEnd, at + RULE_CONTEXT)));
118
+ }
119
+ const near = collapse(text.slice(Math.max(open + 1, at - RULE_CONTEXT / 2), Math.min(ruleEnd - 1, at + RULE_CONTEXT / 2)));
120
+ return `${collapse(text.slice(ruleStart, open))} { … ${near} … }`;
121
+ }
122
+ //# sourceMappingURL=search.js.map
@@ -81,7 +81,10 @@ export interface OtherActivity {
81
81
  * @param newDocument - Whether a new document loaded
82
82
  * @returns New messages
83
83
  */
84
- export declare function newMessages(before: SeenMessage[], after: SeenMessage[], newDocument: boolean): NewMessage[];
84
+ export declare function newMessages(before: SeenMessage[], after: SeenMessage[], newDocument: boolean): {
85
+ messages: NewMessage[];
86
+ more: number;
87
+ };
85
88
  /**
86
89
  * Elements to report as shown: those whose text is not already reported as
87
90
  * a new message, at most {@link MAX_SHOWN_ELEMENTS}, texts cut to
@@ -81,8 +81,6 @@ export function newMessages(before, after, newDocument) {
81
81
  const reported = new Set();
82
82
  const result = [];
83
83
  for (const message of after) {
84
- if (result.length >= MAX_NEW_MESSAGES)
85
- break;
86
84
  if (reported.has(message.text) || TICKING_TEXT.test(message.text))
87
85
  continue;
88
86
  if (!isNew(message))
@@ -90,7 +88,10 @@ export function newMessages(before, after, newDocument) {
90
88
  reported.add(message.text);
91
89
  result.push({ text: cutText(message.text), element: message.element });
92
90
  }
93
- return result;
91
+ return {
92
+ messages: result.slice(0, MAX_NEW_MESSAGES),
93
+ more: Math.max(0, result.length - MAX_NEW_MESSAGES),
94
+ };
94
95
  }
95
96
  /**
96
97
  * Cut a text to {@link MAX_MESSAGE_LENGTH} characters, marking the cut.
@@ -333,11 +334,14 @@ function pageWork(watch, snapshot, domChanging = false) {
333
334
  */
334
335
  function effectsOf(start, snapshot, events) {
335
336
  const navigation = pageNavigation(start?.href, snapshot, events);
336
- const messages = start && snapshot ? newMessages(start.messages, snapshot.messages, snapshot.fresh) : [];
337
+ const { messages, more } = start && snapshot
338
+ ? newMessages(start.messages, snapshot.messages, snapshot.fresh)
339
+ : { messages: [], more: 0 };
337
340
  const shown = shownElements(snapshot?.shown ?? [], messages);
338
341
  return {
339
342
  ...(navigation && { navigation }),
340
343
  ...(messages.length > 0 && { messages }),
344
+ ...(more > 0 && { moreMessages: more }),
341
345
  ...(shown.length > 0 && { shown }),
342
346
  };
343
347
  }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * `bdg dom audit`: page-wide checks in one page walk
3
+ * ({@link AUDIT_PAGE_JS}), turned into findings by {@link buildAudit}.
4
+ */
5
+ import type { CDPConnection } from '../../connection/cdp.js';
6
+ import type { AuditResult } from '../../ipc/protocol/auditTypes.js';
7
+ import type { DomAuditCommand } from '../../ipc/protocol/commands.js';
8
+ /** Findings listed per check without `--limit` */
9
+ export declare const DEFAULT_AUDIT_LIMIT = 20;
10
+ /**
11
+ * Run the page-wide checks.
12
+ *
13
+ * @param cdp - CDP connection
14
+ * @param params - Checks, WCAG level and limit
15
+ * @returns Findings
16
+ * @throws CommandError (91) when the page script fails
17
+ */
18
+ export declare function auditPage(cdp: CDPConnection, params: DomAuditCommand): Promise<AuditResult>;
19
+ //# sourceMappingURL=audit.d.ts.map