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.
- package/README.md +4 -1
- package/dist/commands/dom/a11y.js +2 -1
- package/dist/commands/dom/formInteraction.js +56 -25
- package/dist/commands/dom/helpers/keyAttributes.d.ts +20 -0
- package/dist/commands/dom/helpers/keyAttributes.js +54 -0
- package/dist/commands/dom/helpers/query.d.ts +1 -1
- package/dist/commands/dom/helpers/query.js +66 -19
- package/dist/commands/dom/helpers/runElementCommand.js +4 -3
- package/dist/commands/dom/helpers/screenshot.js +85 -12
- package/dist/commands/dom/index.d.ts +1 -0
- package/dist/commands/dom/index.js +8 -3
- package/dist/commands/dom/inspect.d.ts +15 -0
- package/dist/commands/dom/inspect.js +82 -0
- package/dist/commands/dom/layout.js +2 -2
- package/dist/commands/dom/listeners.js +2 -2
- package/dist/commands/dom/semanticUtils.d.ts +14 -1
- package/dist/commands/dom/semanticUtils.js +44 -3
- package/dist/commands/network/list.js +13 -2
- package/dist/commands/optionBehaviors.js +48 -6
- package/dist/commands/page.d.ts +1 -1
- package/dist/commands/page.js +62 -3
- package/dist/commands/shared/commonOptions.d.ts +4 -0
- package/dist/commands/shared/commonOptions.js +9 -0
- package/dist/commands/shared/optionTypes.d.ts +21 -0
- package/dist/commands/shared/startHelpers.d.ts +66 -0
- package/dist/commands/shared/startHelpers.js +91 -10
- package/dist/commands/shared/validation.d.ts +11 -0
- package/dist/commands/shared/validation.js +16 -0
- package/dist/daemon/launcher.d.ts +8 -1
- package/dist/daemon/launcher.js +3 -1
- package/dist/daemon/session/Session.d.ts +7 -0
- package/dist/daemon/session/Session.js +23 -1
- package/dist/daemon/session/commandRegistry.d.ts +14 -1
- package/dist/daemon/session/commandRegistry.js +65 -9
- package/dist/daemon/session/interactions.d.ts +18 -5
- package/dist/daemon/session/interactions.js +22 -12
- package/dist/daemon.js +3565 -329
- package/dist/errors/messages.d.ts +69 -0
- package/dist/errors/messages.js +102 -1
- package/dist/index.js +2416 -1320
- package/dist/ipc/client.d.ts +9 -0
- package/dist/ipc/client.js +13 -0
- package/dist/ipc/protocol/commands.d.ts +56 -1
- package/dist/ipc/protocol/commands.js +2 -0
- package/dist/ipc/protocol/domTypes.d.ts +35 -2
- package/dist/ipc/protocol/inspectTypes.d.ts +388 -0
- package/dist/ipc/protocol/inspectTypes.js +10 -0
- package/dist/runtime/dom/actionEffects.d.ts +94 -15
- package/dist/runtime/dom/actionEffects.js +173 -27
- package/dist/runtime/dom/actionEffectsScripts.d.ts +52 -14
- package/dist/runtime/dom/actionEffectsScripts.js +224 -32
- package/dist/runtime/dom/elementInfo.d.ts +26 -0
- package/dist/runtime/dom/elementInfo.js +65 -0
- package/dist/runtime/dom/eventListeners.js +14 -4
- package/dist/runtime/dom/formFillHelpers/fill.d.ts +3 -4
- package/dist/runtime/dom/formFillHelpers/fill.js +77 -28
- package/dist/runtime/dom/frameSelection.d.ts +11 -0
- package/dist/runtime/dom/frameSelection.js +20 -1
- package/dist/runtime/dom/frames.d.ts +38 -5
- package/dist/runtime/dom/frames.js +136 -21
- package/dist/runtime/dom/inspect.d.ts +28 -0
- package/dist/runtime/dom/inspect.js +557 -0
- package/dist/runtime/dom/inspectAllStyles.d.ts +62 -0
- package/dist/runtime/dom/inspectAllStyles.js +385 -0
- package/dist/runtime/dom/inspectCascade.d.ts +94 -0
- package/dist/runtime/dom/inspectCascade.js +371 -0
- package/dist/runtime/dom/inspectCascadeModel.d.ts +39 -0
- package/dist/runtime/dom/inspectCascadeModel.js +232 -0
- package/dist/runtime/dom/inspectHints.d.ts +62 -0
- package/dist/runtime/dom/inspectHints.js +305 -0
- package/dist/runtime/dom/inspectLayoutModel.d.ts +87 -0
- package/dist/runtime/dom/inspectLayoutModel.js +346 -0
- package/dist/runtime/dom/inspectModel.d.ts +74 -0
- package/dist/runtime/dom/inspectModel.js +184 -0
- package/dist/runtime/dom/inspectPaintModel.d.ts +157 -0
- package/dist/runtime/dom/inspectPaintModel.js +461 -0
- package/dist/runtime/dom/inspectRules.d.ts +37 -0
- package/dist/runtime/dom/inspectRules.js +101 -0
- package/dist/runtime/dom/inspectScripts.d.ts +132 -0
- package/dist/runtime/dom/inspectScripts.js +263 -0
- package/dist/runtime/dom/inspectTree.d.ts +40 -0
- package/dist/runtime/dom/inspectTree.js +134 -0
- package/dist/runtime/dom/inspectVariables.d.ts +33 -0
- package/dist/runtime/dom/inspectVariables.js +94 -0
- package/dist/runtime/dom/inspectWhyModel.d.ts +20 -0
- package/dist/runtime/dom/inspectWhyModel.js +134 -0
- package/dist/runtime/dom/layout.d.ts +5 -1
- package/dist/runtime/dom/layout.js +10 -3
- package/dist/runtime/dom/listenerPageScripts.d.ts +11 -5
- package/dist/runtime/dom/listenerPageScripts.js +95 -9
- package/dist/runtime/dom/listenerSummary.d.ts +4 -0
- package/dist/runtime/dom/listenerSummary.js +26 -9
- package/dist/runtime/dom/reactEventHelpers.d.ts +5 -0
- package/dist/runtime/dom/reactEventHelpers.js +12 -4
- package/dist/runtime/page/emulation.d.ts +20 -0
- package/dist/runtime/page/emulation.js +37 -0
- package/dist/telemetry/a11y.d.ts +10 -0
- package/dist/telemetry/a11y.js +78 -1
- package/dist/telemetry/console.d.ts +1 -0
- package/dist/telemetry/console.js +100 -5
- package/dist/telemetry/network.js +3 -1
- package/dist/types.d.ts +32 -0
- package/dist/ui/formatters/details.d.ts +8 -0
- package/dist/ui/formatters/details.js +59 -3
- package/dist/ui/formatters/dom.d.ts +2 -1
- package/dist/ui/formatters/dom.js +25 -9
- package/dist/ui/formatters/inspect.d.ts +39 -0
- package/dist/ui/formatters/inspect.js +596 -0
- package/dist/ui/formatters/keyAttributes.d.ts +19 -0
- package/dist/ui/formatters/keyAttributes.js +84 -0
- package/dist/ui/formatters/layout.js +2 -2
- package/dist/ui/formatters/networkHeaders.d.ts +13 -0
- package/dist/ui/formatters/networkHeaders.js +23 -3
- package/dist/ui/formatters/networkList.d.ts +29 -1
- package/dist/ui/formatters/networkList.js +86 -20
- package/dist/ui/formatters/status.js +1 -1
- package/dist/ui/formatting.d.ts +9 -0
- package/dist/ui/formatting.js +6 -3
- package/dist/ui/messages/commands.d.ts +123 -7
- package/dist/ui/messages/commands.js +181 -10
- package/dist/ui/messages/networkMessages.d.ts +14 -0
- package/dist/ui/messages/networkMessages.js +18 -0
- package/dist/ui/messages/session.d.ts +14 -0
- package/dist/ui/messages/session.js +20 -0
- package/dist/utils/async.d.ts +9 -0
- package/dist/utils/async.js +17 -0
- package/dist/utils/color.d.ts +84 -0
- package/dist/utils/color.js +376 -0
- package/dist/utils/cssValues.d.ts +109 -0
- package/dist/utils/cssValues.js +236 -0
- package/dist/utils/selectorFilters.d.ts +12 -0
- package/dist/utils/selectorFilters.js +29 -0
- package/package.json +1 -1
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `bdg dom inspect`: what one element looks like, read in the daemon.
|
|
3
|
+
*
|
|
4
|
+
* The element is found like the other element commands find it (selector
|
|
5
|
+
* with filters through open shadow roots and same-origin iframes, or the
|
|
6
|
+
* exact cached node). Then, in parallel: one page-side walk on the element
|
|
7
|
+
* ({@link INSPECT_PAGE_JS}: text, placement in the parent, backgrounds, child
|
|
8
|
+
* tree), `dom layout`'s measurement (page position, hidden, covered,
|
|
9
|
+
* offscreen) and, once the nodes are pushed to CDP, `CSS.getComputedStyleForNode`
|
|
10
|
+
* for the element, its layout parent and its `::before`/`::after`,
|
|
11
|
+
* `CSS.getPlatformFontsForNode` for its text and `DOM.getBoxModel`. DOM and
|
|
12
|
+
* CSS are enabled on the first inspect and kept on. Matched rules are not
|
|
13
|
+
* read (no cascade).
|
|
14
|
+
*/
|
|
15
|
+
import { CommandError } from '../../errors/index.js';
|
|
16
|
+
import { operationFailedError, unknownCssPropertyError } from '../../errors/messages.js';
|
|
17
|
+
import { throwIfInvalidSelector } from './formFillHelpers/shared.js';
|
|
18
|
+
import { selectedProps } from './inspectAllStyles.js';
|
|
19
|
+
import { buildCascadeFields } from './inspectCascadeModel.js';
|
|
20
|
+
import { buildInspectResult } from './inspectModel.js';
|
|
21
|
+
import { matchedStyles, sourceLabel, trackStyleSheets } from './inspectRules.js';
|
|
22
|
+
import { INSPECT_PAGE_JS, RELATED_NODE_JS } from './inspectScripts.js';
|
|
23
|
+
import { DEFAULT_TREE_DEPTH, DEFAULT_TREE_LIMIT } from './inspectTree.js';
|
|
24
|
+
import { inspectLayout } from './layout.js';
|
|
25
|
+
import { DEEP_QUERY_JS, missingElementError, selectorArgsJS } from './targetNode.js';
|
|
26
|
+
import { createLogger } from '../../ui/logging/index.js';
|
|
27
|
+
import { getErrorMessage } from '../../utils/errors.js';
|
|
28
|
+
import { EXIT_CODES } from '../../utils/exitCodes.js';
|
|
29
|
+
import { findSimilar } from '../../utils/suggestions.js';
|
|
30
|
+
const log = createLogger('dom');
|
|
31
|
+
/** Connections DOM and CSS were enabled on */
|
|
32
|
+
const stylesEnabled = new WeakSet();
|
|
33
|
+
/** Time allowed for the matched rules behind the default hints (large stylesheets take longer) */
|
|
34
|
+
const HINTS_BUDGET_MS = 1000;
|
|
35
|
+
/** Time allowed for them with --rules or --why */
|
|
36
|
+
const RULES_BUDGET_MS = 5000;
|
|
37
|
+
/** Distinguishes the object groups of concurrent calls */
|
|
38
|
+
let groupCounter = 0;
|
|
39
|
+
/** Page-side choice of a match: the index asked for, else the first rendered one (the first when none is) */
|
|
40
|
+
const PICK_MATCH_JS = `function (i) {
|
|
41
|
+
if (i !== null) return i;
|
|
42
|
+
const shown = (el) => (el.checkVisibility ? el.checkVisibility() : el.getClientRects().length > 0);
|
|
43
|
+
const first = Array.prototype.findIndex.call(this, shown);
|
|
44
|
+
return first < 0 ? 0 : first;
|
|
45
|
+
}`;
|
|
46
|
+
/**
|
|
47
|
+
* Inspect one element.
|
|
48
|
+
*
|
|
49
|
+
* @param cdp - CDP connection
|
|
50
|
+
* @param params - Selector (and index) or backend node id, and options
|
|
51
|
+
* @returns Inspect result
|
|
52
|
+
* @throws CommandError (83) no match, (81) index out of range, invalid
|
|
53
|
+
* selector or unknown property, (87) the cached element left the page
|
|
54
|
+
*/
|
|
55
|
+
export async function inspectElement(cdp, params) {
|
|
56
|
+
const started = Date.now();
|
|
57
|
+
const objectGroup = `bdg-inspect-${++groupCounter}`;
|
|
58
|
+
try {
|
|
59
|
+
const found = await findElement(cdp, params, objectGroup);
|
|
60
|
+
const sources = await readSources(cdp, found.objectId, params, objectGroup);
|
|
61
|
+
const propValues = sources.props ? checkedProps(sources.props, sources) : undefined;
|
|
62
|
+
const built = buildInspectResult(sources, {
|
|
63
|
+
selector: params.selector,
|
|
64
|
+
index: found.index,
|
|
65
|
+
count: found.count,
|
|
66
|
+
treeLimit: params.treeLimit ?? DEFAULT_TREE_LIMIT,
|
|
67
|
+
...(params.all && { all: true }),
|
|
68
|
+
...(propValues && { propValues }),
|
|
69
|
+
});
|
|
70
|
+
const withCascade = { ...built, ...cascadeFields(cdp, sources) };
|
|
71
|
+
const result = found.picked ? { ...withCascade, picked: found.picked } : withCascade;
|
|
72
|
+
log.debug(`Inspected ${result.element} in ${Date.now() - started} ms`);
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
void cdp
|
|
77
|
+
.send('Runtime.releaseObjectGroup', { objectGroup })
|
|
78
|
+
.catch((error) => log.debug(`Object group not released: ${getErrorMessage(error)}`));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The properties asked for with `--props`.
|
|
83
|
+
*
|
|
84
|
+
* @param names - Property names (custom property patterns expanded)
|
|
85
|
+
* @param sources - What was read
|
|
86
|
+
* @returns Values by name
|
|
87
|
+
* @throws CommandError (81) for a name no value was found for
|
|
88
|
+
*/
|
|
89
|
+
function checkedProps(names, sources) {
|
|
90
|
+
const { props, unknown } = selectedProps(names, sources.style, sources.raw.props, sources.raw.unknownProps);
|
|
91
|
+
if (unknown.length === 0)
|
|
92
|
+
return props;
|
|
93
|
+
const suggestions = unknown.flatMap((name) => findSimilar(name, Object.keys(sources.style), { maxSuggestions: 1 }));
|
|
94
|
+
const err = unknownCssPropertyError(unknown, suggestions);
|
|
95
|
+
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* `--props` names with custom property patterns expanded: `--*` gives every
|
|
99
|
+
* custom property the element has (its own and inherited), `--bs-btn-*`
|
|
100
|
+
* those with that prefix, sorted.
|
|
101
|
+
*
|
|
102
|
+
* @param names - Names asked for
|
|
103
|
+
* @param style - Computed styles (custom properties included)
|
|
104
|
+
* @returns Names
|
|
105
|
+
*/
|
|
106
|
+
function expandCustomPropertyPatterns(names, style) {
|
|
107
|
+
return names.flatMap((name) => {
|
|
108
|
+
if (!name.startsWith('--') || !name.endsWith('*'))
|
|
109
|
+
return [name];
|
|
110
|
+
const prefix = name.slice(0, -1);
|
|
111
|
+
return Object.keys(style)
|
|
112
|
+
.filter((key) => key.startsWith('--') && key.startsWith(prefix))
|
|
113
|
+
.sort();
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Hints, `--rules` and `--why` from the matched rules.
|
|
118
|
+
*
|
|
119
|
+
* @param cdp - CDP connection (stylesheet headers for the source labels)
|
|
120
|
+
* @param sources - What was read
|
|
121
|
+
* @returns Cascade fields, or `cascade: 'timeout' | 'failed'` when the rules were not read
|
|
122
|
+
*/
|
|
123
|
+
function cascadeFields(cdp, sources) {
|
|
124
|
+
if (!sources.matched)
|
|
125
|
+
return {};
|
|
126
|
+
if (typeof sources.matched === 'string')
|
|
127
|
+
return { cascade: sources.matched };
|
|
128
|
+
return buildCascadeFields({
|
|
129
|
+
matched: sources.matched,
|
|
130
|
+
style: sources.style,
|
|
131
|
+
parentStyle: sources.parentStyle,
|
|
132
|
+
replaced: sources.raw.replaced === true,
|
|
133
|
+
formControl: sources.raw.formControl,
|
|
134
|
+
...(sources.hints === false && { hints: false }),
|
|
135
|
+
label: (declaration) => sourceLabel(declaration, cdp),
|
|
136
|
+
...(sources.rules && { rules: true }),
|
|
137
|
+
...(sources.why && { why: sources.why }),
|
|
138
|
+
...(sources.props && { props: sources.props }),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Find the element: the cached node, or the match at the index (default 0)
|
|
143
|
+
* among the selector's matches.
|
|
144
|
+
*
|
|
145
|
+
* @param cdp - CDP connection
|
|
146
|
+
* @param params - Selector (and index) or backend node id
|
|
147
|
+
* @param objectGroup - Object group for the handles
|
|
148
|
+
* @returns The element's remote object and the match count
|
|
149
|
+
* @throws CommandError when there is no such element
|
|
150
|
+
*/
|
|
151
|
+
async function findElement(cdp, params, objectGroup) {
|
|
152
|
+
if (params.backendNodeId !== undefined) {
|
|
153
|
+
const objectId = await resolveCachedNode(cdp, params.backendNodeId, objectGroup);
|
|
154
|
+
if (!objectId)
|
|
155
|
+
throw missingElementError(params, 0);
|
|
156
|
+
return { objectId, count: 1, index: 0 };
|
|
157
|
+
}
|
|
158
|
+
const matches = await querySelector(cdp, params.selector, objectGroup);
|
|
159
|
+
const [count, index] = await Promise.all([
|
|
160
|
+
callOn(cdp, matches, 'function () { return this.length; }', []),
|
|
161
|
+
callOn(cdp, matches, PICK_MATCH_JS, [params.index ?? null]),
|
|
162
|
+
]);
|
|
163
|
+
const element = (await cdp.send('Runtime.callFunctionOn', {
|
|
164
|
+
objectId: matches,
|
|
165
|
+
functionDeclaration: 'function (i) { return this[i] || null; }',
|
|
166
|
+
arguments: [{ value: index ?? 0 }],
|
|
167
|
+
objectGroup,
|
|
168
|
+
}));
|
|
169
|
+
const objectId = element.result.objectId;
|
|
170
|
+
if (!objectId)
|
|
171
|
+
throw missingElementError(params, count ?? 0);
|
|
172
|
+
return { objectId, count: count ?? 0, index: index ?? 0, ...pickedHow(params, count, index) };
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* How a match was chosen, for the note on several matches.
|
|
176
|
+
*
|
|
177
|
+
* @param params - Command parameters (an explicit --index needs no note)
|
|
178
|
+
* @param count - Number of matches
|
|
179
|
+
* @param index - Index chosen
|
|
180
|
+
* @returns `picked` when no index was given and several elements matched
|
|
181
|
+
*/
|
|
182
|
+
function pickedHow(params, count, index) {
|
|
183
|
+
if (params.index !== undefined || (count ?? 0) < 2)
|
|
184
|
+
return {};
|
|
185
|
+
return { picked: (index ?? 0) > 0 ? 'first-visible' : 'first' };
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Resolve a cached node that is still in the page.
|
|
189
|
+
*
|
|
190
|
+
* @param cdp - CDP connection
|
|
191
|
+
* @param backendNodeId - Backend node id from the query cache
|
|
192
|
+
* @param objectGroup - Object group for the handle
|
|
193
|
+
* @returns Remote object id, or undefined when the node left the page
|
|
194
|
+
*/
|
|
195
|
+
async function resolveCachedNode(cdp, backendNodeId, objectGroup) {
|
|
196
|
+
const resolved = (await cdp
|
|
197
|
+
.send('DOM.resolveNode', { backendNodeId, objectGroup })
|
|
198
|
+
.catch((error) => {
|
|
199
|
+
log.debug(`Node ${backendNodeId} not resolved: ${getErrorMessage(error)}`);
|
|
200
|
+
return {};
|
|
201
|
+
}));
|
|
202
|
+
const objectId = resolved.object?.objectId;
|
|
203
|
+
if (!objectId)
|
|
204
|
+
return undefined;
|
|
205
|
+
const connected = await callOn(cdp, objectId, 'function () { return this.isConnected; }', []);
|
|
206
|
+
return connected ? objectId : undefined;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Run the selector search ({@link DEEP_QUERY_JS}) and keep the array of matches.
|
|
210
|
+
*
|
|
211
|
+
* @param cdp - CDP connection
|
|
212
|
+
* @param selector - Selector (filters allowed)
|
|
213
|
+
* @param objectGroup - Object group for the array
|
|
214
|
+
* @returns Remote object id of the array
|
|
215
|
+
* @throws CommandError (81) invalid selector, (91) page script failure
|
|
216
|
+
*/
|
|
217
|
+
async function querySelector(cdp, selector, objectGroup) {
|
|
218
|
+
const response = (await cdp.send('Runtime.evaluate', {
|
|
219
|
+
expression: `(${DEEP_QUERY_JS})(${selectorArgsJS(selector)})`,
|
|
220
|
+
objectGroup,
|
|
221
|
+
}));
|
|
222
|
+
if (response.exceptionDetails || !response.result.objectId) {
|
|
223
|
+
if (response.exceptionDetails)
|
|
224
|
+
throwIfInvalidSelector(response.exceptionDetails, selector);
|
|
225
|
+
const err = operationFailedError('find the element', response.exceptionDetails?.text ?? 'no result');
|
|
226
|
+
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.SCRIPT_ERROR);
|
|
227
|
+
}
|
|
228
|
+
return response.result.objectId;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Call a function on a remote object and return its value.
|
|
232
|
+
*
|
|
233
|
+
* @param cdp - CDP connection
|
|
234
|
+
* @param objectId - Remote object (`this`)
|
|
235
|
+
* @param functionDeclaration - Function source
|
|
236
|
+
* @param args - Arguments (JSON values)
|
|
237
|
+
* @returns The value, or undefined when the call threw
|
|
238
|
+
*/
|
|
239
|
+
async function callOn(cdp, objectId, functionDeclaration, args) {
|
|
240
|
+
const response = (await cdp.send('Runtime.callFunctionOn', {
|
|
241
|
+
objectId,
|
|
242
|
+
functionDeclaration,
|
|
243
|
+
arguments: args.map((value) => ({ value })),
|
|
244
|
+
returnByValue: true,
|
|
245
|
+
}));
|
|
246
|
+
if (response.exceptionDetails) {
|
|
247
|
+
log.debug(`Page function failed: ${response.exceptionDetails.exception?.description ?? response.exceptionDetails.text}`);
|
|
248
|
+
return undefined;
|
|
249
|
+
}
|
|
250
|
+
return response.result.value;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Read everything about the element: the page-side walk, `dom layout`'s
|
|
254
|
+
* measurement and the CDP styles, fonts and box.
|
|
255
|
+
*
|
|
256
|
+
* @param cdp - CDP connection
|
|
257
|
+
* @param objectId - The element
|
|
258
|
+
* @param params - Request
|
|
259
|
+
* @param objectGroup - Object group for handles
|
|
260
|
+
* @returns Inputs of {@link buildInspectResult}
|
|
261
|
+
*/
|
|
262
|
+
async function readSources(cdp, objectId, params, objectGroup) {
|
|
263
|
+
const related = await relatedNodes(cdp, objectId, objectGroup);
|
|
264
|
+
const [raw, measured, styles] = await Promise.all([
|
|
265
|
+
readPage(cdp, objectId, params),
|
|
266
|
+
measure(cdp, params.selector, related.node),
|
|
267
|
+
readStyles(cdp, related, params),
|
|
268
|
+
]);
|
|
269
|
+
return {
|
|
270
|
+
raw,
|
|
271
|
+
...styles,
|
|
272
|
+
fonts: raw.ownText || raw.formControl ? styles.fonts.node : styles.fonts.textHolder,
|
|
273
|
+
...measured,
|
|
274
|
+
...(params.rules && { rules: true }),
|
|
275
|
+
...(params.why && { why: params.why }),
|
|
276
|
+
...(params.props && { props: expandCustomPropertyPatterns(params.props, styles.style) }),
|
|
277
|
+
...(params.hints === false && { hints: false }),
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* The page-side walk on the element.
|
|
282
|
+
*
|
|
283
|
+
* @param cdp - CDP connection
|
|
284
|
+
* @param objectId - The element
|
|
285
|
+
* @param params - Tree depth and `--props`
|
|
286
|
+
* @returns Page-side measurements
|
|
287
|
+
* @throws CommandError (91) when the walk fails
|
|
288
|
+
*/
|
|
289
|
+
async function readPage(cdp, objectId, params) {
|
|
290
|
+
const raw = await callOn(cdp, objectId, INSPECT_PAGE_JS, [
|
|
291
|
+
params.props || params.all ? 0 : (params.tree ?? DEFAULT_TREE_DEPTH),
|
|
292
|
+
params.props ?? null,
|
|
293
|
+
]);
|
|
294
|
+
if (raw)
|
|
295
|
+
return raw;
|
|
296
|
+
const err = operationFailedError('inspect the element', 'the page script failed');
|
|
297
|
+
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.SCRIPT_ERROR);
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* The backend node id of the element's layout parent or first text holder.
|
|
301
|
+
*
|
|
302
|
+
* @param cdp - CDP connection
|
|
303
|
+
* @param objectId - The element
|
|
304
|
+
* @param which - `parent` or `textHolder`
|
|
305
|
+
* @param objectGroup - Object group for handles
|
|
306
|
+
* @returns Backend node id, or undefined when there is none
|
|
307
|
+
*/
|
|
308
|
+
async function relatedNode(cdp, objectId, which, objectGroup) {
|
|
309
|
+
const response = (await cdp.send('Runtime.callFunctionOn', {
|
|
310
|
+
objectId,
|
|
311
|
+
functionDeclaration: RELATED_NODE_JS,
|
|
312
|
+
arguments: [{ value: which }],
|
|
313
|
+
objectGroup,
|
|
314
|
+
}));
|
|
315
|
+
const id = response.result.objectId;
|
|
316
|
+
return id ? (await describe(cdp, id)).backendNodeId : undefined;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* The backend node ids of the element, its layout parent, its first text
|
|
320
|
+
* holder and its generated pseudo-elements.
|
|
321
|
+
*
|
|
322
|
+
* @param cdp - CDP connection
|
|
323
|
+
* @param objectId - The element
|
|
324
|
+
* @param objectGroup - Object group for handles
|
|
325
|
+
* @returns Backend node ids
|
|
326
|
+
*/
|
|
327
|
+
async function relatedNodes(cdp, objectId, objectGroup) {
|
|
328
|
+
const [node, parent, textHolder] = await Promise.all([
|
|
329
|
+
describe(cdp, objectId),
|
|
330
|
+
relatedNode(cdp, objectId, 'parent', objectGroup),
|
|
331
|
+
relatedNode(cdp, objectId, 'textHolder', objectGroup),
|
|
332
|
+
]);
|
|
333
|
+
const pseudo = (node.pseudoElements ?? [])
|
|
334
|
+
.filter((p) => p.pseudoType === 'before' || p.pseudoType === 'after')
|
|
335
|
+
.map((p) => ({
|
|
336
|
+
type: `::${p.pseudoType}`,
|
|
337
|
+
backendNodeId: p.backendNodeId,
|
|
338
|
+
}));
|
|
339
|
+
return {
|
|
340
|
+
node: node.backendNodeId,
|
|
341
|
+
...(parent && { parent }),
|
|
342
|
+
...(textHolder && { textHolder }),
|
|
343
|
+
pseudo,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Describe a node (no tracking needed).
|
|
348
|
+
*
|
|
349
|
+
* @param cdp - CDP connection
|
|
350
|
+
* @param objectId - Remote object of the node
|
|
351
|
+
* @returns CDP node description
|
|
352
|
+
*/
|
|
353
|
+
async function describe(cdp, objectId) {
|
|
354
|
+
const { node } = (await cdp.send('DOM.describeNode', {
|
|
355
|
+
objectId,
|
|
356
|
+
}));
|
|
357
|
+
return node;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* The element as `dom layout` measures it (page position, visibility, cover)
|
|
361
|
+
* and the color scheme the page sees. Not fatal: without it the header has
|
|
362
|
+
* no position.
|
|
363
|
+
*
|
|
364
|
+
* @param cdp - CDP connection
|
|
365
|
+
* @param selector - Selector of the request
|
|
366
|
+
* @param backendNodeId - The element
|
|
367
|
+
* @returns Layout and color scheme
|
|
368
|
+
*/
|
|
369
|
+
async function measure(cdp, selector, backendNodeId) {
|
|
370
|
+
try {
|
|
371
|
+
const result = await inspectLayout(cdp, { selector, backendNodeId });
|
|
372
|
+
const [layout] = result.elements;
|
|
373
|
+
return {
|
|
374
|
+
...(layout && { layout }),
|
|
375
|
+
...(result.page.colorScheme && { colorScheme: result.page.colorScheme }),
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
catch (error) {
|
|
379
|
+
log.debug(`Layout not measured: ${getErrorMessage(error)}`);
|
|
380
|
+
return {};
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Node ids of the related nodes, for the CSS methods.
|
|
385
|
+
*
|
|
386
|
+
* @param cdp - CDP connection
|
|
387
|
+
* @param related - Backend node ids
|
|
388
|
+
* @returns Node id for a backend node id (undefined when it cannot be tracked)
|
|
389
|
+
*/
|
|
390
|
+
async function nodeIdLookup(cdp, related) {
|
|
391
|
+
const order = [
|
|
392
|
+
related.node,
|
|
393
|
+
related.parent,
|
|
394
|
+
related.textHolder,
|
|
395
|
+
...related.pseudo.map((p) => p.backendNodeId),
|
|
396
|
+
];
|
|
397
|
+
const ids = await pushNodes(cdp, order.filter((id) => id !== undefined));
|
|
398
|
+
return (backendNodeId) => (backendNodeId === undefined ? undefined : ids.get(backendNodeId));
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Computed styles of the element, its parent and pseudo-elements, the
|
|
402
|
+
* platform fonts of its text and its border box size.
|
|
403
|
+
*
|
|
404
|
+
* @param cdp - CDP connection
|
|
405
|
+
* @param related - Backend node ids
|
|
406
|
+
* @returns CDP styles
|
|
407
|
+
*/
|
|
408
|
+
async function readStyles(cdp, related, params) {
|
|
409
|
+
await enableStyleDomains(cdp);
|
|
410
|
+
const nodeIdOf = await nodeIdLookup(cdp, related);
|
|
411
|
+
const [matched, style, parentStyle, nodeFonts, holderFonts, size, pseudo] = await Promise.all([
|
|
412
|
+
readMatched(cdp, nodeIdOf(related.node), params),
|
|
413
|
+
computedStyle(cdp, nodeIdOf(related.node)),
|
|
414
|
+
related.parent === undefined ? undefined : computedStyle(cdp, nodeIdOf(related.parent)),
|
|
415
|
+
platformFonts(cdp, nodeIdOf(related.node)),
|
|
416
|
+
platformFonts(cdp, nodeIdOf(related.textHolder)),
|
|
417
|
+
borderBoxSize(cdp, related.node),
|
|
418
|
+
Promise.all(related.pseudo.map((p) => pseudoSource(cdp, p.type, p.backendNodeId, nodeIdOf(p.backendNodeId)))),
|
|
419
|
+
]);
|
|
420
|
+
return {
|
|
421
|
+
style,
|
|
422
|
+
...(parentStyle && { parentStyle }),
|
|
423
|
+
pseudo,
|
|
424
|
+
fonts: { node: nodeFonts, textHolder: holderFonts },
|
|
425
|
+
...(size && { size }),
|
|
426
|
+
...(matched && { matched }),
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* The element's matched rules, when hints, `--rules` or `--why` need them:
|
|
431
|
+
* within {@link HINTS_BUDGET_MS} for the default hints (skipped on very
|
|
432
|
+
* large stylesheets), {@link RULES_BUDGET_MS} when asked for explicitly.
|
|
433
|
+
*
|
|
434
|
+
* @param cdp - CDP connection
|
|
435
|
+
* @param nodeId - Node id of the element
|
|
436
|
+
* @param params - Request
|
|
437
|
+
* @returns Matched styles, `timeout`, or undefined when not needed (or no node id)
|
|
438
|
+
*/
|
|
439
|
+
async function readMatched(cdp, nodeId, params) {
|
|
440
|
+
const explicit = params.rules === true || params.why !== undefined;
|
|
441
|
+
const skipped = !explicit && (params.hints === false || params.props !== undefined || params.all === true);
|
|
442
|
+
if (nodeId === undefined || skipped) {
|
|
443
|
+
return undefined;
|
|
444
|
+
}
|
|
445
|
+
return matchedStyles(cdp, nodeId, explicit ? RULES_BUDGET_MS : HINTS_BUDGET_MS);
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* A pseudo-element's computed styles and size.
|
|
449
|
+
*
|
|
450
|
+
* @param cdp - CDP connection
|
|
451
|
+
* @param type - `::before` or `::after`
|
|
452
|
+
* @param backendNodeId - Its backend node id
|
|
453
|
+
* @param nodeId - Its node id
|
|
454
|
+
* @returns Pseudo source
|
|
455
|
+
*/
|
|
456
|
+
async function pseudoSource(cdp, type, backendNodeId, nodeId) {
|
|
457
|
+
const [style, size] = await Promise.all([
|
|
458
|
+
computedStyle(cdp, nodeId),
|
|
459
|
+
borderBoxSize(cdp, backendNodeId),
|
|
460
|
+
]);
|
|
461
|
+
return { type, style, ...(size && { size: { w: Math.round(size.w), h: Math.round(size.h) } }) };
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Enable DOM and CSS once per connection (kept on: CSS.enable replays every
|
|
465
|
+
* stylesheet, which costs up to a few hundred ms on large sites the first time).
|
|
466
|
+
*
|
|
467
|
+
* @param cdp - CDP connection
|
|
468
|
+
*/
|
|
469
|
+
async function enableStyleDomains(cdp) {
|
|
470
|
+
if (stylesEnabled.has(cdp))
|
|
471
|
+
return;
|
|
472
|
+
trackStyleSheets(cdp);
|
|
473
|
+
await cdp.send('DOM.enable', {});
|
|
474
|
+
await cdp.send('CSS.enable', {});
|
|
475
|
+
stylesEnabled.add(cdp);
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Node ids for backend node ids (CSS methods take node ids). When none can
|
|
479
|
+
* be tracked, the document was never requested on this connection (or was
|
|
480
|
+
* replaced by a navigation): it is requested, shallowly, and the push tried
|
|
481
|
+
* again. A node that left the page meanwhile is simply missing.
|
|
482
|
+
*
|
|
483
|
+
* @param cdp - CDP connection
|
|
484
|
+
* @param backendNodeIds - Backend node ids
|
|
485
|
+
* @returns Node id per backend node id (missing when CDP cannot track it)
|
|
486
|
+
*/
|
|
487
|
+
async function pushNodes(cdp, backendNodeIds) {
|
|
488
|
+
const push = async () => {
|
|
489
|
+
const response = (await cdp.send('DOM.pushNodesByBackendIdsToFrontend', {
|
|
490
|
+
backendNodeIds,
|
|
491
|
+
}));
|
|
492
|
+
return response.nodeIds;
|
|
493
|
+
};
|
|
494
|
+
let nodeIds = await push().catch(() => []);
|
|
495
|
+
if (nodeIds.every((id) => id === 0)) {
|
|
496
|
+
await cdp.send('DOM.getDocument', { depth: 0 });
|
|
497
|
+
nodeIds = await push().catch(() => []);
|
|
498
|
+
}
|
|
499
|
+
return new Map(backendNodeIds.flatMap((backendNodeId, i) => nodeIds[i] ? [[backendNodeId, nodeIds[i]]] : []));
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Computed styles of a node.
|
|
503
|
+
*
|
|
504
|
+
* @param cdp - CDP connection
|
|
505
|
+
* @param nodeId - Node id (none: empty)
|
|
506
|
+
* @returns Styles by property name
|
|
507
|
+
*/
|
|
508
|
+
async function computedStyle(cdp, nodeId) {
|
|
509
|
+
if (!nodeId)
|
|
510
|
+
return {};
|
|
511
|
+
const response = (await cdp
|
|
512
|
+
.send('CSS.getComputedStyleForNode', { nodeId })
|
|
513
|
+
.catch((error) => {
|
|
514
|
+
log.debug(`Computed style not read: ${getErrorMessage(error)}`);
|
|
515
|
+
return { computedStyle: [] };
|
|
516
|
+
}));
|
|
517
|
+
return Object.fromEntries(response.computedStyle.map((entry) => [entry.name, entry.value]));
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Fonts Chrome rendered a node's own text with.
|
|
521
|
+
*
|
|
522
|
+
* @param cdp - CDP connection
|
|
523
|
+
* @param nodeId - Node id (none: no fonts)
|
|
524
|
+
* @returns Platform fonts
|
|
525
|
+
*/
|
|
526
|
+
async function platformFonts(cdp, nodeId) {
|
|
527
|
+
if (!nodeId)
|
|
528
|
+
return [];
|
|
529
|
+
const response = (await cdp
|
|
530
|
+
.send('CSS.getPlatformFontsForNode', { nodeId })
|
|
531
|
+
.catch((error) => {
|
|
532
|
+
log.debug(`Platform fonts not read: ${getErrorMessage(error)}`);
|
|
533
|
+
return { fonts: [] };
|
|
534
|
+
}));
|
|
535
|
+
return response.fonts;
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Border box size of a node.
|
|
539
|
+
*
|
|
540
|
+
* @param cdp - CDP connection
|
|
541
|
+
* @param backendNodeId - Backend node id
|
|
542
|
+
* @returns Width and height, or undefined when it has no box (not rendered)
|
|
543
|
+
*/
|
|
544
|
+
async function borderBoxSize(cdp, backendNodeId) {
|
|
545
|
+
try {
|
|
546
|
+
const { model } = (await cdp.send('DOM.getBoxModel', {
|
|
547
|
+
backendNodeId,
|
|
548
|
+
}));
|
|
549
|
+
const [x1 = 0, y1 = 0, x2 = 0, y2 = 0, , , x4 = 0, y4 = 0] = model.border;
|
|
550
|
+
return { w: Math.hypot(x2 - x1, y2 - y1), h: Math.hypot(x4 - x1, y4 - y1) };
|
|
551
|
+
}
|
|
552
|
+
catch (error) {
|
|
553
|
+
log.debug(`No box model: ${getErrorMessage(error)}`);
|
|
554
|
+
return undefined;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
//# sourceMappingURL=inspect.js.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `bdg dom inspect --all` and `--props`.
|
|
3
|
+
*
|
|
4
|
+
* `--all` lists every computed longhand that is not noise and not its
|
|
5
|
+
* default, collapsed into shorthands (margin, padding, inset, border,
|
|
6
|
+
* radius, overflow, gap, flex, grid area, outline, animation). Defaults are
|
|
7
|
+
* the CSS initial values as Chrome computes them; the UA stylesheet's
|
|
8
|
+
* per-tag values (a `<p>`'s margins, a button's padding) are real values and
|
|
9
|
+
* are listed. Display, font, size, line height and color are always listed.
|
|
10
|
+
*
|
|
11
|
+
* Noise: custom properties, logical duplicates of physical properties,
|
|
12
|
+
* width/height (in the header), transitions, origins without a transform,
|
|
13
|
+
* colors that only repeat `color` (currentColor), properties of borders,
|
|
14
|
+
* outlines and rules that are not drawn, SVG paint properties on HTML
|
|
15
|
+
* elements, and vendor-prefixed internals.
|
|
16
|
+
*/
|
|
17
|
+
import type { InspectProp } from '../../ipc/protocol/inspectTypes.js';
|
|
18
|
+
import type { StyleMap } from './inspectLayoutModel.js';
|
|
19
|
+
/**
|
|
20
|
+
* Whether a computed value is its property's default.
|
|
21
|
+
*
|
|
22
|
+
* @param name - Property name
|
|
23
|
+
* @param value - Computed value
|
|
24
|
+
* @returns True for the initial value (or an equivalent), an empty value, and
|
|
25
|
+
* a generic default keyword for a property the table does not know
|
|
26
|
+
*/
|
|
27
|
+
export declare function isDefaultValue(name: string, value: string): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* A computed value normalized: px as numbers, colors as hex, URLs as file
|
|
30
|
+
* names, matrices as transforms.
|
|
31
|
+
*
|
|
32
|
+
* @param name - Property name
|
|
33
|
+
* @param value - Computed value
|
|
34
|
+
* @returns Normalized value
|
|
35
|
+
*/
|
|
36
|
+
export declare function normalizeProperty(name: string, value: string): string;
|
|
37
|
+
/**
|
|
38
|
+
* Every computed longhand that is not noise and not a default, normalized
|
|
39
|
+
* and collapsed into shorthands.
|
|
40
|
+
*
|
|
41
|
+
* @param style - Computed styles
|
|
42
|
+
* @param svg - The element is an SVG element (SVG properties are listed)
|
|
43
|
+
* @returns Properties and values
|
|
44
|
+
*/
|
|
45
|
+
export declare function allStyles(style: StyleMap, svg: boolean): Record<string, string>;
|
|
46
|
+
/**
|
|
47
|
+
* The properties asked for with `--props`, raw and normalized. CDP's
|
|
48
|
+
* computed longhands come first; shorthands and custom properties come from
|
|
49
|
+
* the page (`getComputedStyle`).
|
|
50
|
+
*
|
|
51
|
+
* @param names - Property names, lowercase
|
|
52
|
+
* @param style - Computed longhands (CDP)
|
|
53
|
+
* @param pageValues - Values the page read
|
|
54
|
+
* @param pageUnknown - Names the page does not know as CSS properties
|
|
55
|
+
* @returns Values by name (an unset custom property is empty), and the
|
|
56
|
+
* names that are not properties
|
|
57
|
+
*/
|
|
58
|
+
export declare function selectedProps(names: readonly string[], style: StyleMap, pageValues: Record<string, string> | undefined, pageUnknown?: readonly string[]): {
|
|
59
|
+
props: Record<string, InspectProp>;
|
|
60
|
+
unknown: string[];
|
|
61
|
+
};
|
|
62
|
+
//# sourceMappingURL=inspectAllStyles.d.ts.map
|