browser-debugger-cli 0.11.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.
Files changed (222) hide show
  1. package/.claude/skills/bdg/SKILL.md +4 -4
  2. package/README.md +143 -79
  3. package/dist/commands/cdp.d.ts +22 -1
  4. package/dist/commands/cdp.js +100 -43
  5. package/dist/commands/console.d.ts +12 -0
  6. package/dist/commands/console.js +62 -12
  7. package/dist/commands/css.d.ts +13 -0
  8. package/dist/commands/css.js +53 -0
  9. package/dist/commands/dom/DomElementResolver.d.ts +3 -1
  10. package/dist/commands/dom/DomElementResolver.js +10 -3
  11. package/dist/commands/dom/a11y.js +3 -2
  12. package/dist/commands/dom/audit.d.ts +14 -0
  13. package/dist/commands/dom/audit.js +87 -0
  14. package/dist/commands/dom/eval.d.ts +3 -2
  15. package/dist/commands/dom/eval.js +11 -5
  16. package/dist/commands/dom/form.js +10 -9
  17. package/dist/commands/dom/formInteraction.js +42 -11
  18. package/dist/commands/dom/get.js +8 -8
  19. package/dist/commands/dom/helpers/index.d.ts +1 -1
  20. package/dist/commands/dom/helpers/index.js +1 -1
  21. package/dist/commands/dom/helpers/keyAttributes.d.ts +3 -2
  22. package/dist/commands/dom/helpers/keyAttributes.js +6 -4
  23. package/dist/commands/dom/helpers/query.d.ts +27 -3
  24. package/dist/commands/dom/helpers/query.js +152 -64
  25. package/dist/commands/dom/helpers/screenshot.d.ts +1 -0
  26. package/dist/commands/dom/helpers/screenshot.js +169 -49
  27. package/dist/commands/dom/index.js +7 -2
  28. package/dist/commands/dom/query.d.ts +19 -2
  29. package/dist/commands/dom/query.js +37 -6
  30. package/dist/commands/dom/screenshot.js +12 -7
  31. package/dist/commands/dom/semanticUtils.d.ts +3 -2
  32. package/dist/commands/dom/semanticUtils.js +40 -9
  33. package/dist/commands/dom/wait.js +5 -3
  34. package/dist/commands/helpJson.d.ts +82 -19
  35. package/dist/commands/helpJson.js +112 -41
  36. package/dist/commands/helpTopic.d.ts +16 -1
  37. package/dist/commands/helpTopic.js +59 -1
  38. package/dist/commands/installSkill.d.ts +15 -5
  39. package/dist/commands/installSkill.js +86 -16
  40. package/dist/commands/network/list.js +22 -12
  41. package/dist/commands/optionBehaviors.js +53 -16
  42. package/dist/commands/page.js +7 -4
  43. package/dist/commands/peek.d.ts +7 -0
  44. package/dist/commands/peek.js +65 -23
  45. package/dist/commands/shared/daemonErrorHandler.d.ts +5 -2
  46. package/dist/commands/shared/daemonErrorHandler.js +20 -9
  47. package/dist/commands/shared/dataFetcher.d.ts +12 -4
  48. package/dist/commands/shared/dataFetcher.js +12 -4
  49. package/dist/commands/shared/followMode.d.ts +9 -1
  50. package/dist/commands/shared/followMode.js +22 -4
  51. package/dist/commands/shared/optionTypes.d.ts +9 -2
  52. package/dist/commands/shared/outputFile.js +6 -1
  53. package/dist/commands/start.d.ts +20 -5
  54. package/dist/commands/start.js +84 -23
  55. package/dist/commands/stop.d.ts +11 -0
  56. package/dist/commands/stop.js +24 -1
  57. package/dist/commands/tail.d.ts +7 -1
  58. package/dist/commands/tail.js +13 -62
  59. package/dist/commands.js +2 -0
  60. package/dist/connection/cdp.d.ts +7 -0
  61. package/dist/connection/cdp.js +9 -0
  62. package/dist/connection/launcher.js +3 -2
  63. package/dist/daemon/SessionController.js +6 -1
  64. package/dist/daemon/launcher.d.ts +3 -2
  65. package/dist/daemon/launcher.js +47 -3
  66. package/dist/daemon/session/Session.d.ts +4 -1
  67. package/dist/daemon/session/Session.js +33 -2
  68. package/dist/daemon/session/TelemetryStore.d.ts +8 -1
  69. package/dist/daemon/session/TelemetryStore.js +13 -1
  70. package/dist/daemon/session/commandRegistry.js +36 -14
  71. package/dist/daemon/session/interactions.d.ts +2 -1
  72. package/dist/daemon/session/interactions.js +13 -1
  73. package/dist/daemon/session/plugins.js +19 -53
  74. package/dist/daemon/session/teardown.js +1 -1
  75. package/dist/daemon.js +9234 -7222
  76. package/dist/errors/messages.d.ts +88 -15
  77. package/dist/errors/messages.js +177 -27
  78. package/dist/index.js +19322 -13961
  79. package/dist/ipc/client.d.ts +22 -2
  80. package/dist/ipc/client.js +34 -5
  81. package/dist/ipc/protocol/auditTypes.d.ts +135 -0
  82. package/dist/ipc/protocol/auditTypes.js +6 -0
  83. package/dist/ipc/protocol/commands.d.ts +35 -0
  84. package/dist/ipc/protocol/commands.js +2 -0
  85. package/dist/ipc/protocol/domTypes.d.ts +16 -0
  86. package/dist/ipc/protocol/inspectTypes.d.ts +73 -8
  87. package/dist/ipc/session/types.d.ts +2 -0
  88. package/dist/runtime/css/search.d.ts +39 -0
  89. package/dist/runtime/css/search.js +122 -0
  90. package/dist/runtime/dom/actionEffects.d.ts +9 -2
  91. package/dist/runtime/dom/actionEffects.js +30 -14
  92. package/dist/runtime/dom/audit.d.ts +19 -0
  93. package/dist/runtime/dom/audit.js +37 -0
  94. package/dist/runtime/dom/auditModel.d.ts +45 -0
  95. package/dist/runtime/dom/auditModel.js +220 -0
  96. package/dist/runtime/dom/auditScripts.d.ts +113 -0
  97. package/dist/runtime/dom/auditScripts.js +148 -0
  98. package/dist/runtime/dom/elementGeometry.d.ts +16 -3
  99. package/dist/runtime/dom/elementGeometry.js +49 -10
  100. package/dist/runtime/dom/elementInfo.d.ts +74 -17
  101. package/dist/runtime/dom/elementInfo.js +187 -34
  102. package/dist/runtime/dom/evalHelpers.d.ts +12 -2
  103. package/dist/runtime/dom/evalHelpers.js +67 -7
  104. package/dist/runtime/dom/formDiscovery.d.ts +6 -2
  105. package/dist/runtime/dom/formDiscovery.js +20 -3
  106. package/dist/runtime/dom/formFillHelpers/fill.js +8 -12
  107. package/dist/runtime/dom/formFillHelpers/pressKey.js +2 -2
  108. package/dist/runtime/dom/formFillHelpers/shared.d.ts +16 -10
  109. package/dist/runtime/dom/formFillHelpers/shared.js +19 -52
  110. package/dist/runtime/dom/formSubmitHelpers.js +4 -3
  111. package/dist/runtime/dom/frameLayout.js +1 -0
  112. package/dist/runtime/dom/inspect.d.ts +7 -0
  113. package/dist/runtime/dom/inspect.js +92 -28
  114. package/dist/runtime/dom/inspectAllStyles.d.ts +16 -4
  115. package/dist/runtime/dom/inspectAllStyles.js +90 -7
  116. package/dist/runtime/dom/inspectCascade.d.ts +19 -2
  117. package/dist/runtime/dom/inspectCascade.js +214 -44
  118. package/dist/runtime/dom/inspectCascadeModel.d.ts +8 -0
  119. package/dist/runtime/dom/inspectCascadeModel.js +108 -34
  120. package/dist/runtime/dom/inspectHints.d.ts +26 -3
  121. package/dist/runtime/dom/inspectHints.js +125 -9
  122. package/dist/runtime/dom/inspectModel.d.ts +5 -1
  123. package/dist/runtime/dom/inspectModel.js +37 -10
  124. package/dist/runtime/dom/inspectPaintModel.d.ts +50 -22
  125. package/dist/runtime/dom/inspectPaintModel.js +182 -68
  126. package/dist/runtime/dom/inspectRules.d.ts +19 -0
  127. package/dist/runtime/dom/inspectRules.js +21 -5
  128. package/dist/runtime/dom/inspectScripts.d.ts +112 -12
  129. package/dist/runtime/dom/inspectScripts.js +357 -32
  130. package/dist/runtime/dom/inspectTree.js +10 -2
  131. package/dist/runtime/dom/inspectWhyModel.d.ts +2 -1
  132. package/dist/runtime/dom/inspectWhyModel.js +52 -10
  133. package/dist/runtime/dom/layout.js +40 -16
  134. package/dist/runtime/dom/reactEventHelpers.d.ts +21 -4
  135. package/dist/runtime/dom/reactEventHelpers.js +90 -36
  136. package/dist/runtime/dom/targetNode.d.ts +18 -5
  137. package/dist/runtime/dom/targetNode.js +268 -8
  138. package/dist/runtime/dom/wait.js +2 -1
  139. package/dist/runtime/page/bdgWorld.d.ts +57 -0
  140. package/dist/runtime/page/bdgWorld.js +180 -0
  141. package/dist/runtime/page/emulation.d.ts +13 -4
  142. package/dist/runtime/page/emulation.js +69 -4
  143. package/dist/runtime/page/replacedBuiltins.d.ts +28 -0
  144. package/dist/runtime/page/replacedBuiltins.js +136 -0
  145. package/dist/runtime/page/userAgent.d.ts +17 -0
  146. package/dist/runtime/page/userAgent.js +57 -0
  147. package/dist/session/QueryCacheManager.d.ts +4 -1
  148. package/dist/session/QueryCacheManager.js +5 -2
  149. package/dist/session/chrome.d.ts +4 -1
  150. package/dist/session/chrome.js +7 -1
  151. package/dist/session/cleanup/staleSession.d.ts +21 -4
  152. package/dist/session/cleanup/staleSession.js +79 -9
  153. package/dist/session/cleanup/userCommands.d.ts +4 -1
  154. package/dist/session/cleanup/userCommands.js +10 -5
  155. package/dist/session/daemonSocket.d.ts +10 -0
  156. package/dist/session/daemonSocket.js +22 -0
  157. package/dist/session/lastSession.d.ts +6 -3
  158. package/dist/session/lastSession.js +11 -5
  159. package/dist/session/paths.d.ts +3 -1
  160. package/dist/session/paths.js +5 -5
  161. package/dist/session/portClaims.js +4 -3
  162. package/dist/session/sessionList.d.ts +13 -5
  163. package/dist/session/sessionList.js +31 -7
  164. package/dist/telemetry/a11y.js +2 -2
  165. package/dist/telemetry/console.d.ts +2 -1
  166. package/dist/telemetry/console.js +30 -21
  167. package/dist/telemetry/pageCrash.d.ts +26 -0
  168. package/dist/telemetry/pageCrash.js +53 -0
  169. package/dist/types.d.ts +20 -0
  170. package/dist/ui/formatters/audit.d.ts +19 -0
  171. package/dist/ui/formatters/audit.js +115 -0
  172. package/dist/ui/formatters/cdp.d.ts +138 -0
  173. package/dist/ui/formatters/cdp.js +131 -0
  174. package/dist/ui/formatters/console/chronological.js +3 -1
  175. package/dist/ui/formatters/console/follow.d.ts +2 -1
  176. package/dist/ui/formatters/console/follow.js +2 -2
  177. package/dist/ui/formatters/console/json.d.ts +2 -2
  178. package/dist/ui/formatters/console/json.js +11 -5
  179. package/dist/ui/formatters/console/shared.d.ts +30 -0
  180. package/dist/ui/formatters/console/shared.js +16 -0
  181. package/dist/ui/formatters/console/summarize.d.ts +9 -2
  182. package/dist/ui/formatters/console/summarize.js +40 -9
  183. package/dist/ui/formatters/console.d.ts +2 -1
  184. package/dist/ui/formatters/console.js +7 -5
  185. package/dist/ui/formatters/details.js +3 -1
  186. package/dist/ui/formatters/dom.d.ts +2 -2
  187. package/dist/ui/formatters/dom.js +10 -8
  188. package/dist/ui/formatters/helpFormatters.js +1 -1
  189. package/dist/ui/formatters/inspect.js +50 -17
  190. package/dist/ui/formatters/installSkill.d.ts +9 -1
  191. package/dist/ui/formatters/installSkill.js +32 -6
  192. package/dist/ui/formatters/layout.js +2 -1
  193. package/dist/ui/formatters/networkList.d.ts +1 -1
  194. package/dist/ui/formatters/networkList.js +1 -2
  195. package/dist/ui/formatters/preview.d.ts +2 -0
  196. package/dist/ui/formatters/preview.js +17 -7
  197. package/dist/ui/formatters/sessions.d.ts +2 -2
  198. package/dist/ui/formatters/sessions.js +9 -2
  199. package/dist/ui/formatters/status.js +1 -1
  200. package/dist/ui/logging/logger.d.ts +1 -1
  201. package/dist/ui/messages/commands.d.ts +168 -11
  202. package/dist/ui/messages/commands.js +245 -18
  203. package/dist/ui/messages/consoleMessages.d.ts +24 -0
  204. package/dist/ui/messages/consoleMessages.js +32 -0
  205. package/dist/ui/messages/preview.d.ts +12 -0
  206. package/dist/ui/messages/preview.js +18 -2
  207. package/dist/ui/messages/session.d.ts +13 -2
  208. package/dist/ui/messages/session.js +22 -3
  209. package/dist/utils/cssValues.js +36 -4
  210. package/dist/utils/decisionTrees.js +0 -5
  211. package/dist/utils/directories.d.ts +34 -0
  212. package/dist/utils/directories.js +88 -0
  213. package/dist/utils/display.d.ts +16 -0
  214. package/dist/utils/display.js +42 -0
  215. package/dist/utils/exitCodes.d.ts +1 -0
  216. package/dist/utils/exitCodes.js +6 -0
  217. package/dist/utils/process.d.ts +12 -0
  218. package/dist/utils/process.js +25 -0
  219. package/dist/utils/suggestions.d.ts +4 -2
  220. package/dist/utils/suggestions.js +7 -5
  221. package/dist/utils/taskMappings.js +1 -1
  222. package/package.json +3 -2
@@ -7,7 +7,7 @@
7
7
  * backgrounds of the element and its ancestors composited over the page
8
8
  * canvas).
9
9
  */
10
- import type { InspectContrast, InspectEffect, InspectFill, InspectFx, InspectPseudo, InspectState, InspectStroke, InspectText, Sides } from '../../ipc/protocol/inspectTypes.js';
10
+ import type { InspectContrast, InspectEffect, InspectFill, InspectFx, InspectPseudo, InspectState, InspectStroke, InspectSvgPaint, InspectText, Sides } from '../../ipc/protocol/inspectTypes.js';
11
11
  import type { StyleMap } from './inspectLayoutModel.js';
12
12
  import type { RawBackground, RawInspect } from './inspectScripts.js';
13
13
  import { type Rgba } from '../../utils/color.js';
@@ -18,20 +18,24 @@ export interface PlatformFont {
18
18
  glyphCount: number;
19
19
  }
20
20
  /**
21
- * The font the text was rendered with, when it is not a face of the first
22
- * family (a fallback: `DM Sans 9pt` is a face of `DM Sans`, `Liberation Sans`
23
- * is not one of `Arial`), and whether it is a web font. Names
24
- * that are not readable (sites that scramble a web font's internal name)
25
- * are left out.
21
+ * The font the text was rendered with and whether it is a web font. For a
22
+ * generic first family, the font it resolved to (`resolved`). Otherwise
23
+ * `rendered` only when it is a fallback: not a face of the first family
24
+ * (`DM Sans 9pt` is a face of `DM Sans`, `Liberation Sans` is not one of
25
+ * `Arial`), and the first family is not a web font the page loaded (whose
26
+ * file may give any internal name: `Copyright Klim Type Foundry`, or the
27
+ * local font a `src: local()` points at). Names that are not readable
28
+ * (sites that scramble a web font's internal name) are left out.
26
29
  *
27
30
  * @param family - First family of `font-family`
28
31
  * @param fonts - Platform fonts of the text
29
- * @returns `rendered` and `webfont` fields
32
+ * @param familyLoaded - The first family is a loaded web font
33
+ * @returns `rendered`, `resolved` and `webfont` fields
30
34
  */
31
- export declare function renderedFont(family: string, fonts: readonly PlatformFont[]): Pick<InspectText, 'rendered' | 'webfont'>;
35
+ export declare function renderedFont(family: string, fonts: readonly PlatformFont[], familyLoaded?: boolean): Pick<InspectText, 'rendered' | 'resolved' | 'webfont'>;
32
36
  /**
33
37
  * The background behind the element's text: its own and its ancestors'
34
- * backgrounds composited, from the nearest opaque one (or the page canvas).
38
+ * backgrounds composited with their opacity over the page canvas.
35
39
  *
36
40
  * @param backgrounds - Backgrounds, the element's own first
37
41
  * @param canvasDark - The page canvas is dark
@@ -42,36 +46,60 @@ export declare function effectiveBackground(backgrounds: readonly RawBackground[
42
46
  inherited: boolean;
43
47
  overImage: boolean;
44
48
  };
49
+ /** Why a contrast over a background image or gradient is approximate (its pixels are unknown) */
50
+ export declare const OVER_IMAGE_RISK = "background image or gradient behind";
45
51
  /**
46
- * Contrast of the text color with the background behind it.
52
+ * Contrast of the text color with the background behind it, both painted
53
+ * as the browser composites them ({@link paintOver}), with what makes the
54
+ * number approximate (blend modes, filters, content behind or on top).
47
55
  *
48
56
  * @param style - Computed styles (color, font size and weight)
49
- * @param raw - Backgrounds and the page canvas
57
+ * @param raw - Backgrounds, the page canvas, opacity and paint risks
50
58
  * @returns Ratio (rounded down to 2 decimals), level and background
51
59
  */
52
- export declare function textContrast(style: StyleMap, raw: Pick<RawInspect, 'backgrounds' | 'canvasDark' | 'opacity'>): InspectContrast | undefined;
60
+ export declare function textContrast(style: StyleMap, raw: Pick<RawInspect, 'backgrounds' | 'canvasDark' | 'opacity' | 'paintRisks'>): InspectContrast | undefined;
61
+ /** Computed styles the text group reads */
62
+ export interface TextStyles {
63
+ /** The element's */
64
+ style: StyleMap;
65
+ /** Its layout parent's */
66
+ parentStyle?: StyleMap | undefined;
67
+ /** The descendant's that draws most of its text, when it is not the element */
68
+ holderStyle?: StyleMap | undefined;
69
+ }
53
70
  /**
54
- * The text group. Elements with text (or form controls) get the full group:
55
- * font with the rendered font, size, color, alignment, contrast (not for
56
- * text no one can see: `opacity: 0` on it or an ancestor,
57
- * `visibility: hidden`) and the non-default extras. Containers get only what differs from
58
- * their parent, with the font their text was rendered in when the family
59
- * differs.
71
+ * The text group. Elements with text (or text fields) get the full group,
72
+ * read from whatever draws the text (the element, or the descendant with
73
+ * most of it, named in `holder`): font with the rendered font, size, color,
74
+ * alignment, contrast (not for text no one can see: not rendered,
75
+ * `opacity: 0` on it or an ancestor, `visibility: hidden`) and the
76
+ * non-default extras. Containers get only what differs from their parent,
77
+ * with the font their text was rendered in when that text has the
78
+ * container's family. Nothing for an element without text (an icon button,
79
+ * a checkbox).
60
80
  *
61
- * @param style - Computed styles
62
- * @param parentStyle - Computed styles of the layout parent
81
+ * @param styles - Computed styles of the element, its parent and its text holder
63
82
  * @param raw - Page-side measurements
64
83
  * @param fonts - Platform fonts of the text
65
84
  * @returns Text group, or undefined when there is nothing to say
66
85
  */
67
- export declare function buildText(style: StyleMap, parentStyle: StyleMap | undefined, raw: Pick<RawInspect, 'textual' | 'backgrounds' | 'canvasDark' | 'opacity'>, fonts: readonly PlatformFont[]): InspectText | undefined;
86
+ export declare function buildText(styles: TextStyles, raw: Pick<RawInspect, 'textual' | 'textHolder' | 'rendered' | 'hasText' | 'familyLoaded' | 'truncated' | 'backgrounds' | 'canvasDark' | 'opacity' | 'paintRisks'>, fonts: readonly PlatformFont[]): InspectText | undefined;
68
87
  /**
69
- * Background layers: images and gradients (top first), then the color.
88
+ * Background layers: images and gradients (top first), each with its own
89
+ * size and position when they are not the defaults, then the color.
70
90
  *
71
91
  * @param style - Computed styles
72
92
  * @returns Fills
73
93
  */
74
94
  export declare function buildFills(style: StyleMap): InspectFill[];
95
+ /**
96
+ * How an SVG element is painted: its `fill` and `stroke` (with the width).
97
+ *
98
+ * @param style - Computed styles
99
+ * @param raw - Whether it is an SVG element
100
+ * @returns Paint, or undefined for an HTML element
101
+ */
102
+ export declare function buildSvgPaint(style: StyleMap, raw: Pick<RawInspect, 'svg'>): InspectSvgPaint | undefined;
75
103
  /**
76
104
  * Border sides that show (a style other than none/hidden and a width): one
77
105
  * `all` stroke when the four are equal.
@@ -8,7 +8,7 @@
8
8
  * canvas).
9
9
  */
10
10
  import { composite, contrastLevel, contrastRatio, hexColor, parseColor, toHex, } from '../../utils/color.js';
11
- import { cssLength, firstFontFamily, normalizeCssValue, parseShadows, pxNumber, readableTransform, shadowText, shortUrls, splitTopLevel, } from '../../utils/cssValues.js';
11
+ import { cssLength, firstFontFamily, normalizeCssValue, parseShadows, pxNumber, readableTransform, shadowText, shortUrls, sidesShorthand, splitTopLevel, } from '../../utils/cssValues.js';
12
12
  /** Background images and gradients are shown at most this long */
13
13
  const IMAGE_TEXT_LENGTH = 140;
14
14
  /** Page canvas colors (light and dark color-scheme) */
@@ -16,62 +16,127 @@ const CANVAS = {
16
16
  light: { r: 1, g: 1, b: 1, a: 1 },
17
17
  dark: { r: 0x12 / 255, g: 0x12 / 255, b: 0x12 / 255, a: 1 },
18
18
  };
19
+ /** CSS generic font families: the browser picks a font for them */
20
+ const GENERIC_FAMILIES = new Set([
21
+ 'serif',
22
+ 'sans-serif',
23
+ 'monospace',
24
+ 'cursive',
25
+ 'fantasy',
26
+ 'system-ui',
27
+ 'ui-serif',
28
+ 'ui-sans-serif',
29
+ 'ui-monospace',
30
+ 'ui-rounded',
31
+ 'math',
32
+ 'emoji',
33
+ 'fangsong',
34
+ '-apple-system',
35
+ 'blinkmacsystemfont',
36
+ ]);
19
37
  /**
20
- * The font the text was rendered with, when it is not a face of the first
21
- * family (a fallback: `DM Sans 9pt` is a face of `DM Sans`, `Liberation Sans`
22
- * is not one of `Arial`), and whether it is a web font. Names
23
- * that are not readable (sites that scramble a web font's internal name)
24
- * are left out.
38
+ * The font the text was rendered with and whether it is a web font. For a
39
+ * generic first family, the font it resolved to (`resolved`). Otherwise
40
+ * `rendered` only when it is a fallback: not a face of the first family
41
+ * (`DM Sans 9pt` is a face of `DM Sans`, `Liberation Sans` is not one of
42
+ * `Arial`), and the first family is not a web font the page loaded (whose
43
+ * file may give any internal name: `Copyright Klim Type Foundry`, or the
44
+ * local font a `src: local()` points at). Names that are not readable
45
+ * (sites that scramble a web font's internal name) are left out.
25
46
  *
26
47
  * @param family - First family of `font-family`
27
48
  * @param fonts - Platform fonts of the text
28
- * @returns `rendered` and `webfont` fields
49
+ * @param familyLoaded - The first family is a loaded web font
50
+ * @returns `rendered`, `resolved` and `webfont` fields
29
51
  */
30
- export function renderedFont(family, fonts) {
52
+ export function renderedFont(family, fonts, familyLoaded = false) {
31
53
  const primary = [...fonts].sort((a, b) => b.glyphCount - a.glyphCount)[0];
32
54
  if (!primary)
33
55
  return {};
56
+ const webfont = primary.isCustomFont ? { webfont: true } : {};
34
57
  const readable = /^[\p{L}\p{N}.][\p{L}\p{N} ._'-]+$/u.test(primary.familyName);
35
- const differs = readable && !primary.familyName.toLowerCase().startsWith(family.toLowerCase());
58
+ if (!readable || familyLoaded)
59
+ return webfont;
60
+ if (GENERIC_FAMILIES.has(family.toLowerCase())) {
61
+ return { resolved: primary.familyName, ...webfont };
62
+ }
63
+ const differs = !primary.familyName.toLowerCase().startsWith(family.toLowerCase());
64
+ return { ...(differs && { rendered: primary.familyName }), ...webfont };
65
+ }
66
+ /** Fully transparent */
67
+ const CLEAR = { r: 0, g: 0, b: 0, a: 0 };
68
+ /**
69
+ * Paint the text color and the background behind it the way the browser
70
+ * composites them: from the element up to the root, each element's content
71
+ * over its own background, the result faded by its opacity (a translucent
72
+ * ancestor fades its background along with the text), then over the page
73
+ * canvas.
74
+ *
75
+ * @param text - Text color (undefined: the background alone)
76
+ * @param backgrounds - Backgrounds with their opacity, the element's own first
77
+ * @param canvasDark - The page canvas is dark
78
+ * @returns The text pixel, the background pixel and whether a background
79
+ * image or gradient was in the way
80
+ */
81
+ function paintOver(text, backgrounds, canvasDark) {
82
+ let withText = text ?? CLEAR;
83
+ let without = CLEAR;
84
+ let overImage = false;
85
+ for (const layer of backgrounds) {
86
+ if (layer.image && without.a < 0.999)
87
+ overImage = true;
88
+ const color = parseColor(layer.color);
89
+ if (color && color.a > 0) {
90
+ withText = composite(withText, color);
91
+ without = composite(without, color);
92
+ }
93
+ const opacity = layer.opacity ?? 1;
94
+ withText = { ...withText, a: withText.a * opacity };
95
+ without = { ...without, a: without.a * opacity };
96
+ }
97
+ const canvas = CANVAS[canvasDark ? 'dark' : 'light'];
36
98
  return {
37
- ...(differs && { rendered: primary.familyName }),
38
- ...(primary.isCustomFont && { webfont: true }),
99
+ text: composite(withText, canvas),
100
+ background: composite(without, canvas),
101
+ overImage,
39
102
  };
40
103
  }
104
+ /**
105
+ * Whether the element's own background lets what is behind it through (the
106
+ * background behind its text comes from an ancestor or the canvas).
107
+ *
108
+ * @param backgrounds - Backgrounds, the element's own first
109
+ * @returns True when its own background is not opaque
110
+ */
111
+ function ownBackgroundTranslucent(backgrounds) {
112
+ const own = parseColor(backgrounds[0]?.color ?? '');
113
+ return !own || own.a < 0.999;
114
+ }
41
115
  /**
42
116
  * The background behind the element's text: its own and its ancestors'
43
- * backgrounds composited, from the nearest opaque one (or the page canvas).
117
+ * backgrounds composited with their opacity over the page canvas.
44
118
  *
45
119
  * @param backgrounds - Backgrounds, the element's own first
46
120
  * @param canvasDark - The page canvas is dark
47
121
  * @returns Opaque background, whether it came from an ancestor and whether an image was in the way
48
122
  */
49
123
  export function effectiveBackground(backgrounds, canvasDark) {
50
- const layers = [];
51
- let overImage = false;
52
- for (const background of backgrounds) {
53
- if (background.image)
54
- overImage = true;
55
- const color = parseColor(background.color);
56
- if (!color || color.a === 0)
57
- continue;
58
- layers.push(color);
59
- if (color.a >= 0.999)
60
- break;
61
- }
62
- const base = layers[layers.length - 1];
63
- let color = base && base.a >= 0.999 ? base : CANVAS[canvasDark ? 'dark' : 'light'];
64
- const translucent = base && base.a >= 0.999 ? layers.slice(0, -1) : layers;
65
- for (const layer of [...translucent].reverse())
66
- color = composite(layer, color);
67
- const own = parseColor(backgrounds[0]?.color ?? '');
68
- return { color, inherited: !own || own.a < 0.999, overImage };
124
+ const painted = paintOver(undefined, backgrounds, canvasDark);
125
+ return {
126
+ color: painted.background,
127
+ inherited: ownBackgroundTranslucent(backgrounds),
128
+ overImage: painted.overImage,
129
+ };
69
130
  }
131
+ /** Why a contrast over a background image or gradient is approximate (its pixels are unknown) */
132
+ export const OVER_IMAGE_RISK = 'background image or gradient behind';
70
133
  /**
71
- * Contrast of the text color with the background behind it.
134
+ * Contrast of the text color with the background behind it, both painted
135
+ * as the browser composites them ({@link paintOver}), with what makes the
136
+ * number approximate (blend modes, filters, content behind or on top).
72
137
  *
73
138
  * @param style - Computed styles (color, font size and weight)
74
- * @param raw - Backgrounds and the page canvas
139
+ * @param raw - Backgrounds, the page canvas, opacity and paint risks
75
140
  * @returns Ratio (rounded down to 2 decimals), level and background
76
141
  */
77
142
  export function textContrast(style, raw) {
@@ -79,18 +144,19 @@ export function textContrast(style, raw) {
79
144
  if (!color)
80
145
  return undefined;
81
146
  const opacity = raw.opacity ?? 1;
82
- const text = opacity < 1 ? { ...color, a: color.a * opacity } : color;
83
- const background = effectiveBackground(raw.backgrounds, raw.canvasDark);
84
- const ratio = Math.floor(contrastRatio(text, background.color) * 100) / 100;
147
+ const painted = paintOver(color, raw.backgrounds, raw.canvasDark);
148
+ const ratio = Math.floor(contrastRatio(painted.text, painted.background) * 100) / 100;
85
149
  const size = pxNumber(style['font-size']) ?? 16;
86
150
  const weight = Number(style['font-weight'] ?? 400);
151
+ const approximate = [...(raw.paintRisks ?? []), ...(painted.overImage ? [OVER_IMAGE_RISK] : [])];
87
152
  return {
88
153
  ratio,
89
154
  level: contrastLevel(ratio, size, weight),
90
- background: toHex(background.color),
91
- ...(background.inherited && { inherited: true }),
92
- ...(background.overImage && { overImage: true }),
155
+ background: toHex(painted.background),
156
+ ...(ownBackgroundTranslucent(raw.backgrounds) && { inherited: true }),
157
+ ...(painted.overImage && { overImage: true }),
93
158
  ...(opacity < 1 && { opacity: Math.round(opacity * 100) / 100 }),
159
+ ...(approximate.length > 0 && { approximate }),
94
160
  };
95
161
  }
96
162
  /**
@@ -188,40 +254,56 @@ function fontFields(style) {
188
254
  };
189
255
  }
190
256
  /**
191
- * The text group. Elements with text (or form controls) get the full group:
192
- * font with the rendered font, size, color, alignment, contrast (not for
193
- * text no one can see: `opacity: 0` on it or an ancestor,
194
- * `visibility: hidden`) and the non-default extras. Containers get only what differs from
195
- * their parent, with the font their text was rendered in when the family
196
- * differs.
257
+ * The text group. Elements with text (or text fields) get the full group,
258
+ * read from whatever draws the text (the element, or the descendant with
259
+ * most of it, named in `holder`): font with the rendered font, size, color,
260
+ * alignment, contrast (not for text no one can see: not rendered,
261
+ * `opacity: 0` on it or an ancestor, `visibility: hidden`) and the
262
+ * non-default extras. Containers get only what differs from their parent,
263
+ * with the font their text was rendered in when that text has the
264
+ * container's family. Nothing for an element without text (an icon button,
265
+ * a checkbox).
197
266
  *
198
- * @param style - Computed styles
199
- * @param parentStyle - Computed styles of the layout parent
267
+ * @param styles - Computed styles of the element, its parent and its text holder
200
268
  * @param raw - Page-side measurements
201
269
  * @param fonts - Platform fonts of the text
202
270
  * @returns Text group, or undefined when there is nothing to say
203
271
  */
204
- export function buildText(style, parentStyle, raw, fonts) {
205
- const fields = fontFields(style);
272
+ export function buildText(styles, raw, fonts) {
273
+ const { style, parentStyle, holderStyle } = styles;
206
274
  if (raw.textual) {
207
- const seen = (raw.opacity ?? 1) > 0 && style['visibility'] !== 'hidden';
208
- const contrast = seen ? textContrast(style, raw) : undefined;
275
+ const textStyle = raw.textHolder && holderStyle ? holderStyle : style;
276
+ const fields = fontFields(textStyle);
277
+ const gradientFill = (textStyle['background-clip'] ?? '').includes('text') &&
278
+ (parseColor(textStyle['-webkit-text-fill-color'] ?? '')?.a ?? 1) === 0;
279
+ const seen = raw.rendered !== false &&
280
+ (raw.opacity ?? 1) > 0 &&
281
+ textStyle['visibility'] !== 'hidden' &&
282
+ !gradientFill;
283
+ const contrast = seen ? textContrast(textStyle, raw) : undefined;
209
284
  return {
285
+ ...(textStyle !== style && raw.textHolder && { holder: raw.textHolder }),
210
286
  ...fields,
211
- ...renderedFont(fields.family ?? '', fonts),
287
+ ...renderedFont(fields.family ?? '', fonts, raw.familyLoaded),
212
288
  ...(contrast && { contrast }),
213
- ...textExtras(style),
214
- align: alignOf(style),
289
+ ...textExtras(textStyle),
290
+ align: alignOf(textStyle),
291
+ ...(raw.truncated && { truncated: true }),
292
+ ...(gradientFill && { gradientFill: true }),
215
293
  };
216
294
  }
217
- if (!parentStyle)
295
+ if (!parentStyle || raw.hasText === false)
218
296
  return undefined;
297
+ const fields = fontFields(style);
219
298
  const parentFields = { ...fontFields(parentStyle), ...textExtras(parentStyle) };
220
299
  const own = { ...fields, ...textExtras(style) };
221
300
  const differing = Object.fromEntries(Object.entries(own).filter(([key, value]) => parentFields[key] !== value));
222
301
  if (Object.keys(differing).length === 0)
223
302
  return undefined;
224
- return differing.family ? { ...differing, ...renderedFont(differing.family, fonts) } : differing;
303
+ const sameFamily = !holderStyle || holderStyle['font-family'] === style['font-family'];
304
+ return differing.family && sameFamily
305
+ ? { ...differing, ...renderedFont(differing.family, fonts, raw.familyLoaded) }
306
+ : differing;
225
307
  }
226
308
  /**
227
309
  * Cut a long value.
@@ -233,7 +315,8 @@ function cut(text) {
233
315
  return text.length > IMAGE_TEXT_LENGTH ? `${text.slice(0, IMAGE_TEXT_LENGTH)}…` : text;
234
316
  }
235
317
  /**
236
- * Background layers: images and gradients (top first), then the color.
318
+ * Background layers: images and gradients (top first), each with its own
319
+ * size and position when they are not the defaults, then the color.
237
320
  *
238
321
  * @param style - Computed styles
239
322
  * @returns Fills
@@ -241,25 +324,46 @@ function cut(text) {
241
324
  export function buildFills(style) {
242
325
  const fills = [];
243
326
  const image = style['background-image'];
244
- const size = style['background-size'];
245
327
  if (image && image !== 'none') {
246
- for (const layer of splitTopLevel(image)) {
328
+ const sizes = splitTopLevel(style['background-size'] ?? 'auto');
329
+ const xs = splitTopLevel(style['background-position-x'] ?? '0%');
330
+ const ys = splitTopLevel(style['background-position-y'] ?? '0%');
331
+ const positions = xs.map((x, i) => `${x} ${ys[i % ys.length] ?? '0%'}`);
332
+ splitTopLevel(image).forEach((layer, i) => {
333
+ const size = sizes[i % sizes.length] ?? 'auto';
334
+ const position = positions[i % positions.length] ?? '0% 0%';
247
335
  const value = cut(normalizeCssValue(shortUrls(layer)));
248
- if (layer.includes('gradient('))
249
- fills.push({ type: 'gradient', value });
250
- else
251
- fills.push({
252
- type: 'image',
253
- value,
254
- ...(size && size !== 'auto' && { size: normalizeCssValue(size) }),
255
- });
256
- }
336
+ fills.push({
337
+ type: layer.includes('gradient(') ? 'gradient' : 'image',
338
+ value,
339
+ ...(size !== 'auto' && size !== 'auto auto' && { size: normalizeCssValue(size) }),
340
+ ...(position !== '0% 0%' && { position: normalizeCssValue(position) }),
341
+ });
342
+ });
257
343
  }
258
344
  const color = hexColor(style['background-color'] ?? 'transparent');
259
345
  if (color !== 'transparent')
260
346
  fills.push({ type: 'solid', color });
261
347
  return fills;
262
348
  }
349
+ /**
350
+ * How an SVG element is painted: its `fill` and `stroke` (with the width).
351
+ *
352
+ * @param style - Computed styles
353
+ * @param raw - Whether it is an SVG element
354
+ * @returns Paint, or undefined for an HTML element
355
+ */
356
+ export function buildSvgPaint(style, raw) {
357
+ if (!raw.svg)
358
+ return undefined;
359
+ const paint = (value) => normalizeCssValue(shortUrls(hexColor(value ?? 'none')));
360
+ const stroke = paint(style['stroke']);
361
+ return {
362
+ fill: paint(style['fill']),
363
+ stroke,
364
+ ...(stroke !== 'none' && { strokeWidth: cssLength(style['stroke-width'] ?? '1px') }),
365
+ };
366
+ }
263
367
  /**
264
368
  * Border sides that show (a style other than none/hidden and a width): one
265
369
  * `all` stroke when the four are equal.
@@ -421,6 +525,7 @@ export function buildGeneratedPseudo(pseudo, hostColor) {
421
525
  content: shortUrls(content),
422
526
  ...(display && display !== 'inline' && { display }),
423
527
  ...(position && position !== 'static' && { position }),
528
+ ...(position && position !== 'static' && { inset: insetOf(style) }),
424
529
  ...(pseudo.size && { size: pseudo.size }),
425
530
  ...(style['color'] && style['color'] !== hostColor && { color: hexColor(style['color']) }),
426
531
  ...(fills.length > 0 && { fills }),
@@ -430,6 +535,15 @@ export function buildGeneratedPseudo(pseudo, hostColor) {
430
535
  ...(opacity !== 1 && { opacity }),
431
536
  };
432
537
  }
538
+ /**
539
+ * Offsets of a positioned box, as one 1-4 value shorthand.
540
+ *
541
+ * @param style - Computed styles
542
+ * @returns e.g. `auto 50% -9 0`
543
+ */
544
+ function insetOf(style) {
545
+ return sidesShorthand(['top', 'right', 'bottom', 'left'].map((side) => normalizeCssValue(style[side] ?? 'auto')));
546
+ }
433
547
  /**
434
548
  * The pseudo group: generated `::before`/`::after` and the placeholder color.
435
549
  *
@@ -14,6 +14,14 @@ import type { Declaration } from './inspectCascade.js';
14
14
  * @param cdp - CDP connection
15
15
  */
16
16
  export declare function trackStyleSheets(cdp: CDPConnection): void;
17
+ /**
18
+ * The stylesheet headers seen on a connection (after {@link trackStyleSheets}
19
+ * and `CSS.enable`).
20
+ *
21
+ * @param cdp - CDP connection
22
+ * @returns Headers
23
+ */
24
+ export declare function styleSheetHeaders(cdp: CDPConnection): Iterable<Protocol.CSS.CSSStyleSheetHeader>;
17
25
  /**
18
26
  * The rules that match an element, with its inline style and what its
19
27
  * ancestors pass down, or why they are missing.
@@ -34,4 +42,15 @@ export declare function matchedStyles(cdp: CDPConnection, nodeId: number, budget
34
42
  * @returns e.g. `.btn-primary (bootstrap.min.css:5:52628)`, `style attribute`, `browser default`
35
43
  */
36
44
  export declare function sourceLabel(declaration: Declaration, cdp: CDPConnection): string;
45
+ /**
46
+ * A stylesheet position as `file:line[:column]`.
47
+ *
48
+ * @param header - Stylesheet header (unknown: just the line)
49
+ * @param line - 0-based line within the stylesheet
50
+ * @param column - 0-based column
51
+ * @returns e.g. `app.css:12`, `bootstrap.min.css:5:52628`, `<style> in index.html:40`,
52
+ * `constructed stylesheet`, `<style> added by a script` (a sheet a script created and
53
+ * filled with `insertRule`: its lines are not in any file)
54
+ */
55
+ export declare function stylesheetPositionLabel(header: Protocol.CSS.CSSStyleSheetHeader | undefined, line: number | undefined, column: number | undefined): string;
37
56
  //# sourceMappingURL=inspectRules.d.ts.map
@@ -27,6 +27,16 @@ export function trackStyleSheets(cdp) {
27
27
  headers.delete(styleSheetId);
28
28
  });
29
29
  }
30
+ /**
31
+ * The stylesheet headers seen on a connection (after {@link trackStyleSheets}
32
+ * and `CSS.enable`).
33
+ *
34
+ * @param cdp - CDP connection
35
+ * @returns Headers
36
+ */
37
+ export function styleSheetHeaders(cdp) {
38
+ return headersByConnection.get(cdp)?.values() ?? [];
39
+ }
30
40
  /**
31
41
  * The rules that match an element, with its inline style and what its
32
42
  * ancestors pass down, or why they are missing.
@@ -77,7 +87,7 @@ export function sourceLabel(declaration, cdp) {
77
87
  const header = source.styleSheetId
78
88
  ? headersByConnection.get(cdp)?.get(source.styleSheetId)
79
89
  : undefined;
80
- return `${source.selector ?? ''} (${fileLabel(header, source.line, source.column)})`;
90
+ return `${source.selector ?? ''} (${stylesheetPositionLabel(header, source.line, source.column)})`;
81
91
  }
82
92
  /**
83
93
  * A stylesheet position as `file:line[:column]`.
@@ -85,17 +95,23 @@ export function sourceLabel(declaration, cdp) {
85
95
  * @param header - Stylesheet header (unknown: just the line)
86
96
  * @param line - 0-based line within the stylesheet
87
97
  * @param column - 0-based column
88
- * @returns e.g. `app.css:12`, `bootstrap.min.css:5:52628`, `<style> in index.html:40`, `constructed stylesheet`
98
+ * @returns e.g. `app.css:12`, `bootstrap.min.css:5:52628`, `<style> in index.html:40`,
99
+ * `constructed stylesheet`, `<style> added by a script` (a sheet a script created and
100
+ * filled with `insertRule`: its lines are not in any file)
89
101
  */
90
- function fileLabel(header, line, column) {
102
+ export function stylesheetPositionLabel(header, line, column) {
91
103
  if (!header)
92
104
  return line === undefined ? 'stylesheet' : `stylesheet:${line + 1}`;
93
105
  if (header.isConstructed)
94
106
  return 'constructed stylesheet';
95
107
  const file = fileName(header.sourceURL) || 'page';
96
- const absoluteLine = (line ?? 0) + (header.isInline ? header.startLine : 0) + 1;
97
108
  const where = header.isInline ? `<style> in ${file}` : file;
109
+ if (header.isMutable && !header.sourceURL)
110
+ return `<style> added by a script`;
111
+ const absoluteLine = (line ?? 0) + (header.isInline ? header.startLine : 0) + 1;
112
+ const firstLine = header.isInline && (line ?? 0) === 0;
113
+ const absoluteColumn = (column ?? 0) + (firstLine ? header.startColumn : 0) + 1;
98
114
  const minified = header.endLine - header.startLine < 10 && (column ?? 0) > 200;
99
- return minified ? `${where}:${absoluteLine}:${(column ?? 0) + 1}` : `${where}:${absoluteLine}`;
115
+ return minified ? `${where}:${absoluteLine}:${absoluteColumn}` : `${where}:${absoluteLine}`;
100
116
  }
101
117
  //# sourceMappingURL=inspectRules.js.map