browser-debugger-cli 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/README.md +4 -1
  2. package/dist/commands/dom/a11y.js +2 -1
  3. package/dist/commands/dom/formInteraction.js +56 -25
  4. package/dist/commands/dom/helpers/keyAttributes.d.ts +20 -0
  5. package/dist/commands/dom/helpers/keyAttributes.js +54 -0
  6. package/dist/commands/dom/helpers/query.d.ts +1 -1
  7. package/dist/commands/dom/helpers/query.js +66 -19
  8. package/dist/commands/dom/helpers/runElementCommand.js +4 -3
  9. package/dist/commands/dom/helpers/screenshot.js +85 -12
  10. package/dist/commands/dom/index.d.ts +1 -0
  11. package/dist/commands/dom/index.js +8 -3
  12. package/dist/commands/dom/inspect.d.ts +15 -0
  13. package/dist/commands/dom/inspect.js +82 -0
  14. package/dist/commands/dom/layout.js +2 -2
  15. package/dist/commands/dom/listeners.js +2 -2
  16. package/dist/commands/dom/semanticUtils.d.ts +14 -1
  17. package/dist/commands/dom/semanticUtils.js +44 -3
  18. package/dist/commands/network/list.js +13 -2
  19. package/dist/commands/optionBehaviors.js +48 -6
  20. package/dist/commands/page.d.ts +1 -1
  21. package/dist/commands/page.js +62 -3
  22. package/dist/commands/shared/commonOptions.d.ts +4 -0
  23. package/dist/commands/shared/commonOptions.js +9 -0
  24. package/dist/commands/shared/optionTypes.d.ts +21 -0
  25. package/dist/commands/shared/startHelpers.d.ts +66 -0
  26. package/dist/commands/shared/startHelpers.js +91 -10
  27. package/dist/commands/shared/validation.d.ts +11 -0
  28. package/dist/commands/shared/validation.js +16 -0
  29. package/dist/daemon/launcher.d.ts +8 -1
  30. package/dist/daemon/launcher.js +3 -1
  31. package/dist/daemon/session/Session.d.ts +7 -0
  32. package/dist/daemon/session/Session.js +23 -1
  33. package/dist/daemon/session/commandRegistry.d.ts +14 -1
  34. package/dist/daemon/session/commandRegistry.js +65 -9
  35. package/dist/daemon/session/interactions.d.ts +18 -5
  36. package/dist/daemon/session/interactions.js +22 -12
  37. package/dist/daemon.js +3565 -329
  38. package/dist/errors/messages.d.ts +69 -0
  39. package/dist/errors/messages.js +102 -1
  40. package/dist/index.js +2416 -1320
  41. package/dist/ipc/client.d.ts +9 -0
  42. package/dist/ipc/client.js +13 -0
  43. package/dist/ipc/protocol/commands.d.ts +56 -1
  44. package/dist/ipc/protocol/commands.js +2 -0
  45. package/dist/ipc/protocol/domTypes.d.ts +35 -2
  46. package/dist/ipc/protocol/inspectTypes.d.ts +388 -0
  47. package/dist/ipc/protocol/inspectTypes.js +10 -0
  48. package/dist/runtime/dom/actionEffects.d.ts +94 -15
  49. package/dist/runtime/dom/actionEffects.js +173 -27
  50. package/dist/runtime/dom/actionEffectsScripts.d.ts +52 -14
  51. package/dist/runtime/dom/actionEffectsScripts.js +224 -32
  52. package/dist/runtime/dom/elementInfo.d.ts +26 -0
  53. package/dist/runtime/dom/elementInfo.js +65 -0
  54. package/dist/runtime/dom/eventListeners.js +14 -4
  55. package/dist/runtime/dom/formFillHelpers/fill.d.ts +3 -4
  56. package/dist/runtime/dom/formFillHelpers/fill.js +77 -28
  57. package/dist/runtime/dom/frameSelection.d.ts +11 -0
  58. package/dist/runtime/dom/frameSelection.js +20 -1
  59. package/dist/runtime/dom/frames.d.ts +38 -5
  60. package/dist/runtime/dom/frames.js +136 -21
  61. package/dist/runtime/dom/inspect.d.ts +28 -0
  62. package/dist/runtime/dom/inspect.js +557 -0
  63. package/dist/runtime/dom/inspectAllStyles.d.ts +62 -0
  64. package/dist/runtime/dom/inspectAllStyles.js +385 -0
  65. package/dist/runtime/dom/inspectCascade.d.ts +94 -0
  66. package/dist/runtime/dom/inspectCascade.js +371 -0
  67. package/dist/runtime/dom/inspectCascadeModel.d.ts +39 -0
  68. package/dist/runtime/dom/inspectCascadeModel.js +232 -0
  69. package/dist/runtime/dom/inspectHints.d.ts +62 -0
  70. package/dist/runtime/dom/inspectHints.js +305 -0
  71. package/dist/runtime/dom/inspectLayoutModel.d.ts +87 -0
  72. package/dist/runtime/dom/inspectLayoutModel.js +346 -0
  73. package/dist/runtime/dom/inspectModel.d.ts +74 -0
  74. package/dist/runtime/dom/inspectModel.js +184 -0
  75. package/dist/runtime/dom/inspectPaintModel.d.ts +157 -0
  76. package/dist/runtime/dom/inspectPaintModel.js +461 -0
  77. package/dist/runtime/dom/inspectRules.d.ts +37 -0
  78. package/dist/runtime/dom/inspectRules.js +101 -0
  79. package/dist/runtime/dom/inspectScripts.d.ts +132 -0
  80. package/dist/runtime/dom/inspectScripts.js +263 -0
  81. package/dist/runtime/dom/inspectTree.d.ts +40 -0
  82. package/dist/runtime/dom/inspectTree.js +134 -0
  83. package/dist/runtime/dom/inspectVariables.d.ts +33 -0
  84. package/dist/runtime/dom/inspectVariables.js +94 -0
  85. package/dist/runtime/dom/inspectWhyModel.d.ts +20 -0
  86. package/dist/runtime/dom/inspectWhyModel.js +134 -0
  87. package/dist/runtime/dom/layout.d.ts +5 -1
  88. package/dist/runtime/dom/layout.js +10 -3
  89. package/dist/runtime/dom/listenerPageScripts.d.ts +11 -5
  90. package/dist/runtime/dom/listenerPageScripts.js +95 -9
  91. package/dist/runtime/dom/listenerSummary.d.ts +4 -0
  92. package/dist/runtime/dom/listenerSummary.js +26 -9
  93. package/dist/runtime/dom/reactEventHelpers.d.ts +5 -0
  94. package/dist/runtime/dom/reactEventHelpers.js +12 -4
  95. package/dist/runtime/page/emulation.d.ts +20 -0
  96. package/dist/runtime/page/emulation.js +37 -0
  97. package/dist/telemetry/a11y.d.ts +10 -0
  98. package/dist/telemetry/a11y.js +78 -1
  99. package/dist/telemetry/console.d.ts +1 -0
  100. package/dist/telemetry/console.js +100 -5
  101. package/dist/telemetry/network.js +3 -1
  102. package/dist/types.d.ts +32 -0
  103. package/dist/ui/formatters/details.d.ts +8 -0
  104. package/dist/ui/formatters/details.js +59 -3
  105. package/dist/ui/formatters/dom.d.ts +2 -1
  106. package/dist/ui/formatters/dom.js +25 -9
  107. package/dist/ui/formatters/inspect.d.ts +39 -0
  108. package/dist/ui/formatters/inspect.js +596 -0
  109. package/dist/ui/formatters/keyAttributes.d.ts +19 -0
  110. package/dist/ui/formatters/keyAttributes.js +84 -0
  111. package/dist/ui/formatters/layout.js +2 -2
  112. package/dist/ui/formatters/networkHeaders.d.ts +13 -0
  113. package/dist/ui/formatters/networkHeaders.js +23 -3
  114. package/dist/ui/formatters/networkList.d.ts +29 -1
  115. package/dist/ui/formatters/networkList.js +86 -20
  116. package/dist/ui/formatters/status.js +1 -1
  117. package/dist/ui/formatting.d.ts +9 -0
  118. package/dist/ui/formatting.js +6 -3
  119. package/dist/ui/messages/commands.d.ts +123 -7
  120. package/dist/ui/messages/commands.js +181 -10
  121. package/dist/ui/messages/networkMessages.d.ts +14 -0
  122. package/dist/ui/messages/networkMessages.js +18 -0
  123. package/dist/ui/messages/session.d.ts +14 -0
  124. package/dist/ui/messages/session.js +20 -0
  125. package/dist/utils/async.d.ts +9 -0
  126. package/dist/utils/async.js +17 -0
  127. package/dist/utils/color.d.ts +84 -0
  128. package/dist/utils/color.js +376 -0
  129. package/dist/utils/cssValues.d.ts +109 -0
  130. package/dist/utils/cssValues.js +236 -0
  131. package/dist/utils/selectorFilters.d.ts +12 -0
  132. package/dist/utils/selectorFilters.js +29 -0
  133. package/package.json +1 -1
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Text, fill, border, effect, state and pseudo-element groups of
3
+ * `bdg dom inspect`, from computed styles. No-op defaults (transparent,
4
+ * none, 0, normal, 1) are left out, colors are hex and shadows drop
5
+ * invisible layers. The text group shows the font Chrome rendered the text
6
+ * with and the WCAG contrast against the background behind the text (the
7
+ * backgrounds of the element and its ancestors composited over the page
8
+ * canvas).
9
+ */
10
+ import type { InspectContrast, InspectEffect, InspectFill, InspectFx, InspectPseudo, InspectState, InspectStroke, InspectText, Sides } from '../../ipc/protocol/inspectTypes.js';
11
+ import type { StyleMap } from './inspectLayoutModel.js';
12
+ import type { RawBackground, RawInspect } from './inspectScripts.js';
13
+ import { type Rgba } from '../../utils/color.js';
14
+ /** A font Chrome used for the text (`CSS.getPlatformFontsForNode`) */
15
+ export interface PlatformFont {
16
+ familyName: string;
17
+ isCustomFont: boolean;
18
+ glyphCount: number;
19
+ }
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.
26
+ *
27
+ * @param family - First family of `font-family`
28
+ * @param fonts - Platform fonts of the text
29
+ * @returns `rendered` and `webfont` fields
30
+ */
31
+ export declare function renderedFont(family: string, fonts: readonly PlatformFont[]): Pick<InspectText, 'rendered' | 'webfont'>;
32
+ /**
33
+ * The background behind the element's text: its own and its ancestors'
34
+ * backgrounds composited, from the nearest opaque one (or the page canvas).
35
+ *
36
+ * @param backgrounds - Backgrounds, the element's own first
37
+ * @param canvasDark - The page canvas is dark
38
+ * @returns Opaque background, whether it came from an ancestor and whether an image was in the way
39
+ */
40
+ export declare function effectiveBackground(backgrounds: readonly RawBackground[], canvasDark: boolean): {
41
+ color: Rgba;
42
+ inherited: boolean;
43
+ overImage: boolean;
44
+ };
45
+ /**
46
+ * Contrast of the text color with the background behind it.
47
+ *
48
+ * @param style - Computed styles (color, font size and weight)
49
+ * @param raw - Backgrounds and the page canvas
50
+ * @returns Ratio (rounded down to 2 decimals), level and background
51
+ */
52
+ export declare function textContrast(style: StyleMap, raw: Pick<RawInspect, 'backgrounds' | 'canvasDark' | 'opacity'>): InspectContrast | undefined;
53
+ /**
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.
60
+ *
61
+ * @param style - Computed styles
62
+ * @param parentStyle - Computed styles of the layout parent
63
+ * @param raw - Page-side measurements
64
+ * @param fonts - Platform fonts of the text
65
+ * @returns Text group, or undefined when there is nothing to say
66
+ */
67
+ export declare function buildText(style: StyleMap, parentStyle: StyleMap | undefined, raw: Pick<RawInspect, 'textual' | 'backgrounds' | 'canvasDark' | 'opacity'>, fonts: readonly PlatformFont[]): InspectText | undefined;
68
+ /**
69
+ * Background layers: images and gradients (top first), then the color.
70
+ *
71
+ * @param style - Computed styles
72
+ * @returns Fills
73
+ */
74
+ export declare function buildFills(style: StyleMap): InspectFill[];
75
+ /**
76
+ * Border sides that show (a style other than none/hidden and a width): one
77
+ * `all` stroke when the four are equal.
78
+ *
79
+ * @param style - Computed styles
80
+ * @returns Strokes
81
+ */
82
+ export declare function buildStrokes(style: StyleMap): InspectStroke[];
83
+ /**
84
+ * Corner radii, when any corner is rounded.
85
+ *
86
+ * @param style - Computed styles
87
+ * @returns Top-left, top-right, bottom-right, bottom-left
88
+ */
89
+ export declare function buildRadius(style: StyleMap): Sides | undefined;
90
+ /**
91
+ * The outline, when it shows (a style, a width and a visible color).
92
+ *
93
+ * @param style - Computed styles
94
+ * @returns Outline fields
95
+ */
96
+ export declare function buildOutline(style: StyleMap): {
97
+ width: number;
98
+ style: string;
99
+ color: string;
100
+ offset?: number;
101
+ } | undefined;
102
+ /**
103
+ * Box shadows as effects (invisible layers left out).
104
+ *
105
+ * @param style - Computed styles
106
+ * @returns Effects
107
+ */
108
+ export declare function buildEffects(style: StyleMap): InspectEffect[];
109
+ /**
110
+ * Effects other than shadows: transform, filter, backdrop filter, clip
111
+ * path, mask and a running animation (never transitions).
112
+ *
113
+ * @param style - Computed styles
114
+ * @returns Fx fields, or undefined when there are none
115
+ */
116
+ export declare function buildFx(style: StyleMap): InspectFx | undefined;
117
+ /**
118
+ * Interaction state set by CSS: a cursor (other than the default, and the
119
+ * pointer links get anyway), `pointer-events: none`, visibility,
120
+ * `user-select: none` and `appearance: none` on form controls.
121
+ *
122
+ * @param style - Computed styles
123
+ * @param raw - Tag and whether it is a form control
124
+ * @returns State fields, or undefined when there are none
125
+ */
126
+ export declare function buildState(style: StyleMap, raw: Pick<RawInspect, 'tag' | 'formControl'>): InspectState | undefined;
127
+ /** A generated pseudo-element as CDP reads it */
128
+ export interface PseudoSource {
129
+ type: '::before' | '::after';
130
+ style: StyleMap;
131
+ /** Border box size, when it is rendered */
132
+ size?: {
133
+ w: number;
134
+ h: number;
135
+ };
136
+ }
137
+ /**
138
+ * A `::before`/`::after` pseudo-element whose `content` is set: its content,
139
+ * display (unless inline), position, size, color (when not the host's),
140
+ * background, radius, shadow, transform and opacity.
141
+ *
142
+ * @param pseudo - Pseudo-element styles and size
143
+ * @param hostColor - Text color of the element
144
+ * @returns Pseudo fields, or undefined when it generates nothing
145
+ */
146
+ export declare function buildGeneratedPseudo(pseudo: PseudoSource, hostColor: string | undefined): InspectPseudo | undefined;
147
+ /**
148
+ * The pseudo group: generated `::before`/`::after` and the placeholder color.
149
+ *
150
+ * @param generated - Pseudo-elements CDP found
151
+ * @param hostStyle - Computed styles of the element (text color, and the font
152
+ * size and weight the placeholder's contrast level depends on)
153
+ * @param raw - Placeholder color of an empty field, and the backgrounds behind it
154
+ * @returns Pseudo-elements, or undefined when there are none
155
+ */
156
+ export declare function buildPseudo(generated: readonly PseudoSource[], hostStyle: StyleMap, raw: Pick<RawInspect, 'placeholderColor' | 'placeholderFont' | 'backgrounds' | 'canvasDark' | 'opacity'>): InspectPseudo[] | undefined;
157
+ //# sourceMappingURL=inspectPaintModel.d.ts.map
@@ -0,0 +1,461 @@
1
+ /**
2
+ * Text, fill, border, effect, state and pseudo-element groups of
3
+ * `bdg dom inspect`, from computed styles. No-op defaults (transparent,
4
+ * none, 0, normal, 1) are left out, colors are hex and shadows drop
5
+ * invisible layers. The text group shows the font Chrome rendered the text
6
+ * with and the WCAG contrast against the background behind the text (the
7
+ * backgrounds of the element and its ancestors composited over the page
8
+ * canvas).
9
+ */
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';
12
+ /** Background images and gradients are shown at most this long */
13
+ const IMAGE_TEXT_LENGTH = 140;
14
+ /** Page canvas colors (light and dark color-scheme) */
15
+ const CANVAS = {
16
+ light: { r: 1, g: 1, b: 1, a: 1 },
17
+ dark: { r: 0x12 / 255, g: 0x12 / 255, b: 0x12 / 255, a: 1 },
18
+ };
19
+ /**
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.
25
+ *
26
+ * @param family - First family of `font-family`
27
+ * @param fonts - Platform fonts of the text
28
+ * @returns `rendered` and `webfont` fields
29
+ */
30
+ export function renderedFont(family, fonts) {
31
+ const primary = [...fonts].sort((a, b) => b.glyphCount - a.glyphCount)[0];
32
+ if (!primary)
33
+ return {};
34
+ const readable = /^[\p{L}\p{N}.][\p{L}\p{N} ._'-]+$/u.test(primary.familyName);
35
+ const differs = readable && !primary.familyName.toLowerCase().startsWith(family.toLowerCase());
36
+ return {
37
+ ...(differs && { rendered: primary.familyName }),
38
+ ...(primary.isCustomFont && { webfont: true }),
39
+ };
40
+ }
41
+ /**
42
+ * The background behind the element's text: its own and its ancestors'
43
+ * backgrounds composited, from the nearest opaque one (or the page canvas).
44
+ *
45
+ * @param backgrounds - Backgrounds, the element's own first
46
+ * @param canvasDark - The page canvas is dark
47
+ * @returns Opaque background, whether it came from an ancestor and whether an image was in the way
48
+ */
49
+ 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 };
69
+ }
70
+ /**
71
+ * Contrast of the text color with the background behind it.
72
+ *
73
+ * @param style - Computed styles (color, font size and weight)
74
+ * @param raw - Backgrounds and the page canvas
75
+ * @returns Ratio (rounded down to 2 decimals), level and background
76
+ */
77
+ export function textContrast(style, raw) {
78
+ const color = parseColor(style['color'] ?? '');
79
+ if (!color)
80
+ return undefined;
81
+ 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;
85
+ const size = pxNumber(style['font-size']) ?? 16;
86
+ const weight = Number(style['font-weight'] ?? 400);
87
+ return {
88
+ ratio,
89
+ level: contrastLevel(ratio, size, weight),
90
+ background: toHex(background.color),
91
+ ...(background.inherited && { inherited: true }),
92
+ ...(background.overImage && { overImage: true }),
93
+ ...(opacity < 1 && { opacity: Math.round(opacity * 100) / 100 }),
94
+ };
95
+ }
96
+ /**
97
+ * White-space handling as the `white-space` keyword.
98
+ *
99
+ * @param style - Computed styles
100
+ * @returns e.g. `nowrap`, `pre-wrap`; undefined for `normal`
101
+ */
102
+ function whiteSpaceOf(style) {
103
+ const collapse = style['white-space-collapse'] ?? 'collapse';
104
+ const wrap = (style['text-wrap-mode'] ?? 'wrap') === 'wrap';
105
+ if (collapse === 'collapse')
106
+ return wrap ? undefined : 'nowrap';
107
+ if (collapse === 'preserve')
108
+ return wrap ? 'pre-wrap' : 'pre';
109
+ if (collapse === 'preserve-breaks')
110
+ return wrap ? 'pre-line' : collapse;
111
+ return collapse;
112
+ }
113
+ /**
114
+ * Text decoration: the lines, with a style other than solid and a color
115
+ * other than the text color.
116
+ *
117
+ * @param style - Computed styles
118
+ * @returns e.g. `underline dotted #f00`; undefined without lines
119
+ */
120
+ function decorationOf(style) {
121
+ const line = style['text-decoration-line'];
122
+ if (!line || line === 'none')
123
+ return undefined;
124
+ const decorationStyle = style['text-decoration-style'];
125
+ const color = style['text-decoration-color'];
126
+ return [
127
+ line,
128
+ decorationStyle !== 'solid' && decorationStyle,
129
+ color && color !== style['color'] && hexColor(color),
130
+ ]
131
+ .filter(Boolean)
132
+ .join(' ');
133
+ }
134
+ /**
135
+ * Text fields beyond font, size and color, when not their default.
136
+ *
137
+ * @param style - Computed styles
138
+ * @returns Alignment, transform, tracking, decoration, white-space, overflow, clamp, shadow, features
139
+ */
140
+ function textExtras(style) {
141
+ const align = style['text-align'];
142
+ const transform = style['text-transform'];
143
+ const tracking = style['letter-spacing'];
144
+ const overflow = style['text-overflow'];
145
+ const clamp = style['-webkit-line-clamp'];
146
+ const shadow = parseShadows(style['text-shadow'] ?? 'none').map((layer) => shadowText(layer, false));
147
+ const features = style['font-feature-settings'];
148
+ const decoration = decorationOf(style);
149
+ const whiteSpace = whiteSpaceOf(style);
150
+ return {
151
+ ...(align && !['start', 'left', '-webkit-auto'].includes(align) && { align }),
152
+ ...(transform && transform !== 'none' && { transform }),
153
+ ...(tracking && tracking !== 'normal' && { tracking: cssLength(tracking) }),
154
+ ...(decoration && { decoration }),
155
+ ...(whiteSpace && { whiteSpace }),
156
+ ...(overflow && overflow !== 'clip' && { overflow }),
157
+ ...(clamp && clamp !== 'none' && { clamp }),
158
+ ...(shadow.length > 0 && { shadow: shadow.join(', ') }),
159
+ ...(features && features !== 'normal' && { features }),
160
+ };
161
+ }
162
+ /**
163
+ * Horizontal text alignment, the default (`start`, older `-webkit-auto`)
164
+ * included, so "is it centered?" is answered without asking.
165
+ *
166
+ * @param style - Computed styles
167
+ * @returns e.g. `start`, `center`, `right`
168
+ */
169
+ function alignOf(style) {
170
+ const align = style['text-align'] ?? 'start';
171
+ return align === '-webkit-auto' ? 'start' : align;
172
+ }
173
+ /**
174
+ * Font, size and color fields.
175
+ *
176
+ * @param style - Computed styles
177
+ * @returns Family, weight, style, size, line height and color
178
+ */
179
+ function fontFields(style) {
180
+ const fontStyle = style['font-style'];
181
+ return {
182
+ family: firstFontFamily(style['font-family'] ?? ''),
183
+ weight: Number(style['font-weight'] ?? 400),
184
+ ...(fontStyle && fontStyle !== 'normal' && { style: fontStyle }),
185
+ size: pxNumber(style['font-size']) ?? 16,
186
+ lineHeight: cssLength(style['line-height'] ?? 'normal'),
187
+ color: hexColor(style['color'] ?? ''),
188
+ };
189
+ }
190
+ /**
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.
197
+ *
198
+ * @param style - Computed styles
199
+ * @param parentStyle - Computed styles of the layout parent
200
+ * @param raw - Page-side measurements
201
+ * @param fonts - Platform fonts of the text
202
+ * @returns Text group, or undefined when there is nothing to say
203
+ */
204
+ export function buildText(style, parentStyle, raw, fonts) {
205
+ const fields = fontFields(style);
206
+ if (raw.textual) {
207
+ const seen = (raw.opacity ?? 1) > 0 && style['visibility'] !== 'hidden';
208
+ const contrast = seen ? textContrast(style, raw) : undefined;
209
+ return {
210
+ ...fields,
211
+ ...renderedFont(fields.family ?? '', fonts),
212
+ ...(contrast && { contrast }),
213
+ ...textExtras(style),
214
+ align: alignOf(style),
215
+ };
216
+ }
217
+ if (!parentStyle)
218
+ return undefined;
219
+ const parentFields = { ...fontFields(parentStyle), ...textExtras(parentStyle) };
220
+ const own = { ...fields, ...textExtras(style) };
221
+ const differing = Object.fromEntries(Object.entries(own).filter(([key, value]) => parentFields[key] !== value));
222
+ if (Object.keys(differing).length === 0)
223
+ return undefined;
224
+ return differing.family ? { ...differing, ...renderedFont(differing.family, fonts) } : differing;
225
+ }
226
+ /**
227
+ * Cut a long value.
228
+ *
229
+ * @param text - Value
230
+ * @returns At most {@link IMAGE_TEXT_LENGTH} characters
231
+ */
232
+ function cut(text) {
233
+ return text.length > IMAGE_TEXT_LENGTH ? `${text.slice(0, IMAGE_TEXT_LENGTH)}…` : text;
234
+ }
235
+ /**
236
+ * Background layers: images and gradients (top first), then the color.
237
+ *
238
+ * @param style - Computed styles
239
+ * @returns Fills
240
+ */
241
+ export function buildFills(style) {
242
+ const fills = [];
243
+ const image = style['background-image'];
244
+ const size = style['background-size'];
245
+ if (image && image !== 'none') {
246
+ for (const layer of splitTopLevel(image)) {
247
+ 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
+ }
257
+ }
258
+ const color = hexColor(style['background-color'] ?? 'transparent');
259
+ if (color !== 'transparent')
260
+ fills.push({ type: 'solid', color });
261
+ return fills;
262
+ }
263
+ /**
264
+ * Border sides that show (a style other than none/hidden and a width): one
265
+ * `all` stroke when the four are equal.
266
+ *
267
+ * @param style - Computed styles
268
+ * @returns Strokes
269
+ */
270
+ export function buildStrokes(style) {
271
+ const strokes = ['top', 'right', 'bottom', 'left'].map((side) => ({
272
+ side,
273
+ width: pxNumber(style[`border-${side}-width`]) ?? 0,
274
+ style: style[`border-${side}-style`] ?? 'none',
275
+ color: hexColor(style[`border-${side}-color`] ?? ''),
276
+ }));
277
+ const shown = strokes.filter((s) => s.width > 0 && !['none', 'hidden'].includes(s.style));
278
+ const [first] = shown;
279
+ const same = (s) => s.width === first?.width && s.style === first.style && s.color === first.color;
280
+ if (first && shown.length === 4 && shown.every(same))
281
+ return [{ ...first, side: 'all' }];
282
+ return shown;
283
+ }
284
+ /**
285
+ * Corner radii, when any corner is rounded.
286
+ *
287
+ * @param style - Computed styles
288
+ * @returns Top-left, top-right, bottom-right, bottom-left
289
+ */
290
+ export function buildRadius(style) {
291
+ const radius = ['top-left', 'top-right', 'bottom-right', 'bottom-left'].map((corner) => cssLength(style[`border-${corner}-radius`] ?? '0px'));
292
+ return radius.every((value) => value === 0) ? undefined : radius;
293
+ }
294
+ /**
295
+ * The outline, when it shows (a style, a width and a visible color).
296
+ *
297
+ * @param style - Computed styles
298
+ * @returns Outline fields
299
+ */
300
+ export function buildOutline(style) {
301
+ const outlineStyle = style['outline-style'] ?? 'none';
302
+ const width = pxNumber(style['outline-width']) ?? 0;
303
+ const color = hexColor(style['outline-color'] ?? '');
304
+ if (outlineStyle === 'none' || width === 0 || color === 'transparent')
305
+ return undefined;
306
+ const offset = pxNumber(style['outline-offset']) ?? 0;
307
+ return { width, style: outlineStyle, color, ...(offset !== 0 && { offset }) };
308
+ }
309
+ /**
310
+ * Box shadows as effects (invisible layers left out).
311
+ *
312
+ * @param style - Computed styles
313
+ * @returns Effects
314
+ */
315
+ export function buildEffects(style) {
316
+ return parseShadows(style['box-shadow'] ?? 'none').map((layer) => ({
317
+ type: layer.inset ? 'inner-shadow' : 'shadow',
318
+ x: layer.x,
319
+ y: layer.y,
320
+ blur: layer.blur,
321
+ spread: layer.spread,
322
+ color: layer.color,
323
+ }));
324
+ }
325
+ /**
326
+ * The transform: the `transform` matrix in readable form and the
327
+ * individual `translate`, `rotate` and `scale` properties.
328
+ *
329
+ * @param style - Computed styles
330
+ * @returns e.g. `translate(10,20) rotate(45deg)`; undefined without one
331
+ */
332
+ function transformOf(style) {
333
+ const parts = [
334
+ style['transform'] && style['transform'] !== 'none' && readableTransform(style['transform']),
335
+ ...['translate', 'rotate', 'scale'].map((name) => {
336
+ const value = style[name];
337
+ return value && value !== 'none' && `${name} ${normalizeCssValue(value)}`;
338
+ }),
339
+ ].filter((part) => Boolean(part) && part !== 'none');
340
+ return parts.length > 0 ? parts.join(' ') : undefined;
341
+ }
342
+ /**
343
+ * Effects other than shadows: transform, filter, backdrop filter, clip
344
+ * path, mask and a running animation (never transitions).
345
+ *
346
+ * @param style - Computed styles
347
+ * @returns Fx fields, or undefined when there are none
348
+ */
349
+ export function buildFx(style) {
350
+ const set = (name) => {
351
+ const value = style[name];
352
+ return value && value !== 'none' ? cut(normalizeCssValue(shortUrls(value))) : '';
353
+ };
354
+ const filter = set('filter');
355
+ const backdrop = set('backdrop-filter');
356
+ const clip = set('clip-path');
357
+ const mask = set('mask-image');
358
+ const animationName = style['animation-name'];
359
+ const iterations = style['animation-iteration-count'];
360
+ const animation = animationName && animationName !== 'none'
361
+ ? [animationName, style['animation-duration'], iterations !== '1' && iterations]
362
+ .filter(Boolean)
363
+ .join(' ')
364
+ : undefined;
365
+ const transform = transformOf(style);
366
+ const fx = {
367
+ ...(transform && { transform }),
368
+ ...(filter && { filter }),
369
+ ...(backdrop && { backdrop }),
370
+ ...(clip && { clip }),
371
+ ...(mask && { mask }),
372
+ ...(animation && { animation }),
373
+ };
374
+ return Object.keys(fx).length > 0 ? fx : undefined;
375
+ }
376
+ /**
377
+ * Interaction state set by CSS: a cursor (other than the default, and the
378
+ * pointer links get anyway), `pointer-events: none`, visibility,
379
+ * `user-select: none` and `appearance: none` on form controls.
380
+ *
381
+ * @param style - Computed styles
382
+ * @param raw - Tag and whether it is a form control
383
+ * @returns State fields, or undefined when there are none
384
+ */
385
+ export function buildState(style, raw) {
386
+ const cursor = style['cursor'];
387
+ const linkPointer = raw.tag === 'a' && cursor === 'pointer';
388
+ const state = {
389
+ ...(cursor && !['auto', 'default'].includes(cursor) && !linkPointer && { cursor }),
390
+ ...(style['pointer-events'] === 'none' && { pointerEvents: 'none' }),
391
+ ...(style['visibility'] &&
392
+ style['visibility'] !== 'visible' && { visibility: style['visibility'] }),
393
+ ...(style['user-select'] === 'none' && { userSelect: 'none' }),
394
+ ...(raw.formControl && style['appearance'] === 'none' && { appearance: 'none' }),
395
+ };
396
+ return Object.keys(state).length > 0 ? state : undefined;
397
+ }
398
+ /**
399
+ * A `::before`/`::after` pseudo-element whose `content` is set: its content,
400
+ * display (unless inline), position, size, color (when not the host's),
401
+ * background, radius, shadow, transform and opacity.
402
+ *
403
+ * @param pseudo - Pseudo-element styles and size
404
+ * @param hostColor - Text color of the element
405
+ * @returns Pseudo fields, or undefined when it generates nothing
406
+ */
407
+ export function buildGeneratedPseudo(pseudo, hostColor) {
408
+ const { style } = pseudo;
409
+ const content = style['content'];
410
+ if (!content || content === 'none' || content === 'normal')
411
+ return undefined;
412
+ const display = style['display'];
413
+ const position = style['position'];
414
+ const radius = buildRadius(style);
415
+ const effects = buildEffects(style);
416
+ const fills = buildFills(style);
417
+ const opacity = Number(style['opacity'] ?? 1);
418
+ const transform = transformOf(style);
419
+ return {
420
+ type: pseudo.type,
421
+ content: shortUrls(content),
422
+ ...(display && display !== 'inline' && { display }),
423
+ ...(position && position !== 'static' && { position }),
424
+ ...(pseudo.size && { size: pseudo.size }),
425
+ ...(style['color'] && style['color'] !== hostColor && { color: hexColor(style['color']) }),
426
+ ...(fills.length > 0 && { fills }),
427
+ ...(radius && { radius: radius.join(' ') }),
428
+ ...(effects.length > 0 && { effects }),
429
+ ...(transform && { transform }),
430
+ ...(opacity !== 1 && { opacity }),
431
+ };
432
+ }
433
+ /**
434
+ * The pseudo group: generated `::before`/`::after` and the placeholder color.
435
+ *
436
+ * @param generated - Pseudo-elements CDP found
437
+ * @param hostStyle - Computed styles of the element (text color, and the font
438
+ * size and weight the placeholder's contrast level depends on)
439
+ * @param raw - Placeholder color of an empty field, and the backgrounds behind it
440
+ * @returns Pseudo-elements, or undefined when there are none
441
+ */
442
+ export function buildPseudo(generated, hostStyle, raw) {
443
+ const pseudo = generated
444
+ .map((source) => buildGeneratedPseudo(source, hostStyle['color']))
445
+ .filter((entry) => entry !== undefined);
446
+ if (raw.placeholderColor) {
447
+ const font = raw.placeholderFont;
448
+ const weight = font?.weight ?? hostStyle['font-weight'] ?? '400';
449
+ const contrast = textContrast({ ...hostStyle, color: raw.placeholderColor, 'font-weight': weight }, raw);
450
+ pseudo.push({
451
+ type: '::placeholder',
452
+ color: hexColor(raw.placeholderColor),
453
+ ...(font &&
454
+ font.style !== (hostStyle['font-style'] ?? 'normal') && { fontStyle: font.style }),
455
+ ...(weight !== (hostStyle['font-weight'] ?? '400') && { fontWeight: Number(weight) }),
456
+ ...(contrast && { contrast }),
457
+ });
458
+ }
459
+ return pseudo.length > 0 ? pseudo : undefined;
460
+ }
461
+ //# sourceMappingURL=inspectPaintModel.js.map
@@ -0,0 +1,37 @@
1
+ /**
2
+ * CDP side of `dom inspect`'s cascade: the stylesheet headers (where a rule
3
+ * comes from), the matched rules of an element with a time budget, and
4
+ * labels for a declaration's source.
5
+ */
6
+ import type { CDPConnection } from '../../connection/cdp.js';
7
+ import type { Protocol } from '../../connection/typed-cdp.js';
8
+ import type { Declaration } from './inspectCascade.js';
9
+ /**
10
+ * Start collecting stylesheet headers. Call before `CSS.enable`, which
11
+ * reports every existing stylesheet; later ones arrive as they are added,
12
+ * removed ones (navigations) are dropped.
13
+ *
14
+ * @param cdp - CDP connection
15
+ */
16
+ export declare function trackStyleSheets(cdp: CDPConnection): void;
17
+ /**
18
+ * The rules that match an element, with its inline style and what its
19
+ * ancestors pass down, or why they are missing.
20
+ *
21
+ * @param cdp - CDP connection
22
+ * @param nodeId - Node id of the element
23
+ * @param budgetMs - Time allowed
24
+ * @returns Matched styles, `timeout` (longer than the budget) or `failed` (CDP error)
25
+ */
26
+ export declare function matchedStyles(cdp: CDPConnection, nodeId: number, budgetMs: number): Promise<Protocol.CSS.GetMatchedStylesForNodeResponse | 'timeout' | 'failed'>;
27
+ /**
28
+ * Where a declaration comes from, for people: the selector and the file
29
+ * with its line (and column, for single-line minified files), or what kind
30
+ * of style it is.
31
+ *
32
+ * @param declaration - Declaration
33
+ * @param cdp - CDP connection whose stylesheet headers to use
34
+ * @returns e.g. `.btn-primary (bootstrap.min.css:5:52628)`, `style attribute`, `browser default`
35
+ */
36
+ export declare function sourceLabel(declaration: Declaration, cdp: CDPConnection): string;
37
+ //# sourceMappingURL=inspectRules.d.ts.map