blob: 70cf81405ac7003be673148c4d2f9cf39673fe58 [file] [log] [blame]
Paul Lewis8cddf9932019-09-27 16:40:071// Copyright 2019 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Tim van der Lippe76961572021-04-06 10:48:075import * as Common from '../../core/common/common.js';
Tim van der Lipped8caac42021-03-31 14:40:446import * as Root from '../../core/root/root.js';
Tim van der Lippee00b92f2021-03-31 16:52:177import * as SDK from '../../core/sdk/sdk.js';
Tim van der Lippe51ae9e12021-04-13 12:41:068import * as ColorPicker from '../../ui/legacy/components/color_picker/color_picker.js';
Tim van der Lippe03ca5952021-05-12 13:16:259import type * as ProtocolProxyApi from '../../generated/protocol-proxy-api.js';
Tim van der Lippe229a54f2021-05-14 16:59:0510import * as Protocol from '../../generated/protocol.js';
Tim van der Lippe7946bbc2020-02-13 13:58:4211
Jack Franklina75ae7c2021-05-11 13:22:5412import type {ContrastIssue} from './CSSOverviewCompletedView.js';
13import type {UnusedDeclaration} from './CSSOverviewUnusedDeclarations.js';
14import {CSSOverviewUnusedDeclarations} from './CSSOverviewUnusedDeclarations.js';
Jan Scheffler830467c2021-02-25 13:44:4715
16interface NodeStyleStats {
17 elementCount: number;
Philip Pfaffe8071e532021-09-20 08:27:1518 backgroundColors: Map<string, Set<Protocol.DOM.BackendNodeId>>;
19 textColors: Map<string, Set<Protocol.DOM.BackendNodeId>>;
Jan Scheffler830467c2021-02-25 13:44:4720 textColorContrastIssues: Map<string, ContrastIssue[]>;
Philip Pfaffe8071e532021-09-20 08:27:1521 fillColors: Map<string, Set<Protocol.DOM.BackendNodeId>>;
22 borderColors: Map<string, Set<Protocol.DOM.BackendNodeId>>;
23 fontInfo: Map<string, Map<string, Map<string, Protocol.DOM.BackendNodeId[]>>>;
Jan Scheffler830467c2021-02-25 13:44:4724 unusedDeclarations: Map<string, UnusedDeclaration[]>;
25}
26
27export interface GlobalStyleStats {
28 styleRules: number;
29 inlineStyles: number;
30 externalSheets: number;
31 stats: {
32 // Simple.
33 type: number,
34 class: number,
35 id: number,
36 universal: number,
37 attribute: number,
38
39 // Non-simple.
40 nonSimple: number,
41 };
42}
43
Simon Zünddbb8de42021-09-22 07:56:4844export class CSSOverviewModel extends SDK.SDKModel.SDKModel<void> {
Tim van der Lippe1e4e83c2021-10-12 16:16:5845 readonly #runtimeAgent: ProtocolProxyApi.RuntimeApi;
46 readonly #cssAgent: ProtocolProxyApi.CSSApi;
47 readonly #domAgent: ProtocolProxyApi.DOMApi;
48 readonly #domSnapshotAgent: ProtocolProxyApi.DOMSnapshotApi;
49 readonly #overlayAgent: ProtocolProxyApi.OverlayApi;
Jan Scheffler830467c2021-02-25 13:44:4750
Sigurd Schneider79e812e2021-06-04 06:48:2351 constructor(target: SDK.Target.Target) {
Paul Lewis8cddf9932019-09-27 16:40:0752 super(target);
53
Tim van der Lippe1e4e83c2021-10-12 16:16:5854 this.#runtimeAgent = target.runtimeAgent();
55 this.#cssAgent = target.cssAgent();
56 this.#domAgent = target.domAgent();
57 this.#domSnapshotAgent = target.domsnapshotAgent();
58 this.#overlayAgent = target.overlayAgent();
Paul Lewis8cddf9932019-09-27 16:40:0759 }
60
Sigurd Schneidere18ce8e2021-07-06 08:57:0761 highlightNode(node: Protocol.DOM.BackendNodeId): void {
Alex Rudenkoc88c76b2020-11-20 15:08:0762 const highlightConfig = {
63 contentColor: Common.Color.PageHighlight.Content.toProtocolRGBA(),
64 showInfo: true,
65 contrastAlgorithm: Root.Runtime.experiments.isEnabled('APCA') ? Protocol.Overlay.ContrastAlgorithm.Apca :
66 Protocol.Overlay.ContrastAlgorithm.Aa,
67 };
Paul Lewisde0224c2019-10-22 16:23:1568
Tim van der Lippe1e4e83c2021-10-12 16:16:5869 this.#overlayAgent.invoke_hideHighlight();
70 this.#overlayAgent.invoke_highlightNode({backendNodeId: node, highlightConfig});
Paul Lewisde0224c2019-10-22 16:23:1571 }
72
Jan Scheffler830467c2021-02-25 13:44:4773 async getNodeStyleStats(): Promise<NodeStyleStats> {
Paul Lewisde0224c2019-10-22 16:23:1574 const backgroundColors = new Map();
75 const textColors = new Map();
Alex Rudenkod55e18c2020-09-23 11:37:0676 const textColorContrastIssues = new Map();
Paul Lewis12bc63e2019-10-23 10:53:2977 const fillColors = new Map();
78 const borderColors = new Map();
Paul Lewis7d10a732019-11-06 16:11:5179 const fontInfo = new Map();
Paul Lewised808ce2019-11-06 14:21:5480 const unusedDeclarations = new Map();
Paul Lewis12bc63e2019-10-23 10:53:2981 const snapshotConfig = {
82 computedStyles: [
Paul Lewis8aef9012019-10-29 14:15:1783 'background-color',
84 'color',
85 'fill',
86 'border-top-width',
87 'border-top-color',
88 'border-bottom-width',
89 'border-bottom-color',
90 'border-left-width',
91 'border-left-color',
92 'border-right-width',
93 'border-right-color',
Paul Lewis7d10a732019-11-06 16:11:5194 'font-family',
Paul Lewis8aef9012019-10-29 14:15:1795 'font-size',
96 'font-weight',
Paul Lewis7d10a732019-11-06 16:11:5197 'line-height',
Paul Lewis8aef9012019-10-29 14:15:1798 'position',
99 'top',
100 'right',
101 'bottom',
102 'left',
103 'display',
104 'width',
Paul Lewisaae7b102019-10-30 14:18:21105 'height',
Jan Scheffler830467c2021-02-25 13:44:47106 'vertical-align',
107 ],
Alex Rudenko1c7ae0e2021-04-15 09:37:58108 includeTextColorOpacities: true,
109 includeBlendedBackgroundColors: true,
Paul Lewis12bc63e2019-10-23 10:53:29110 };
Paul Lewisde0224c2019-10-22 16:23:15111
Jan Scheffler830467c2021-02-25 13:44:47112 const formatColor = (color: Common.Color.Color): string|null => {
Alex Rudenkod55e18c2020-09-23 11:37:06113 return color.hasAlpha() ? color.asString(Common.Color.Format.HEXA) : color.asString(Common.Color.Format.HEX);
114 };
115
Jan Scheffler830467c2021-02-25 13:44:47116 const storeColor = (id: number, nodeId: number, target: Map<string, Set<number>>): Common.Color.Color|undefined => {
Paul Lewisde0224c2019-10-22 16:23:15117 if (id === -1) {
118 return;
119 }
120
121 // Parse the color, discard transparent ones.
122 const colorText = strings[id];
Paul Lewisbe87fed2020-07-07 12:44:19123 if (!colorText) {
124 return;
125 }
126
Tim van der Lippe7946bbc2020-02-13 13:58:42127 const color = Common.Color.Color.parse(colorText);
Paul Lewis4ab492e2019-10-28 10:28:54128 if (!color || color.rgba()[3] === 0) {
Paul Lewisde0224c2019-10-22 16:23:15129 return;
130 }
131
132 // Format the color and use as the key.
Alex Rudenkod55e18c2020-09-23 11:37:06133 const colorFormatted = formatColor(color);
Andres Olivares1e6d9c62020-09-03 22:47:38134 if (!colorFormatted) {
135 return;
136 }
Paul Lewisde0224c2019-10-22 16:23:15137
138 // Get the existing set of nodes with the color, or create a new set.
139 const colorValues = target.get(colorFormatted) || new Set();
140 colorValues.add(nodeId);
141
142 // Store.
143 target.set(colorFormatted, colorValues);
Alex Rudenkod55e18c2020-09-23 11:37:06144
145 return color;
Paul Lewisde0224c2019-10-22 16:23:15146 };
147
Jan Scheffler830467c2021-02-25 13:44:47148 const isSVGNode = (nodeName: string): boolean => {
Paul Lewis69fb9b62019-10-29 16:22:33149 const validNodes = new Set([
Jan Scheffler830467c2021-02-25 13:44:47150 'altglyph',
151 'circle',
152 'ellipse',
153 'path',
154 'polygon',
155 'polyline',
156 'rect',
157 'svg',
158 'text',
159 'textpath',
160 'tref',
161 'tspan',
Paul Lewis69fb9b62019-10-29 16:22:33162 ]);
163 return validNodes.has(nodeName.toLowerCase());
164 };
165
Jan Scheffler830467c2021-02-25 13:44:47166 const isReplacedContent = (nodeName: string): boolean => {
Paul Lewis69fb9b62019-10-29 16:22:33167 const validNodes = new Set(['iframe', 'video', 'embed', 'img']);
168 return validNodes.has(nodeName.toLowerCase());
Paul Lewis12bc63e2019-10-23 10:53:29169 };
170
Jan Scheffler830467c2021-02-25 13:44:47171 const isTableElementWithDefaultStyles = (nodeName: string, display: string): boolean => {
Paul Lewisaae7b102019-10-30 14:18:21172 const validNodes = new Set(['tr', 'td', 'thead', 'tbody']);
173 return validNodes.has(nodeName.toLowerCase()) && display.startsWith('table');
174 };
175
Paul Lewis8aef9012019-10-29 14:15:17176 let elementCount = 0;
Andres Olivares1e6d9c62020-09-03 22:47:38177
Tim van der Lippe1e4e83c2021-10-12 16:16:58178 const {documents, strings} = await this.#domSnapshotAgent.invoke_captureSnapshot(snapshotConfig);
Paul Lewisde0224c2019-10-22 16:23:15179 for (const {nodes, layout} of documents) {
Paul Lewis8aef9012019-10-29 14:15:17180 // Track the number of elements in the documents.
181 elementCount += layout.nodeIndex.length;
182
Paul Lewisde0224c2019-10-22 16:23:15183 for (let idx = 0; idx < layout.styles.length; idx++) {
184 const styles = layout.styles[idx];
185 const nodeIdx = layout.nodeIndex[idx];
Andres Olivares1e6d9c62020-09-03 22:47:38186 if (!nodes.backendNodeId || !nodes.nodeName) {
187 continue;
188 }
Paul Lewisde0224c2019-10-22 16:23:15189 const nodeId = nodes.backendNodeId[nodeIdx];
Paul Lewis12bc63e2019-10-23 10:53:29190 const nodeName = nodes.nodeName[nodeIdx];
Paul Lewisde0224c2019-10-22 16:23:15191
Paul Lewis7d10a732019-11-06 16:11:51192 const [backgroundColorIdx, textColorIdx, fillIdx, borderTopWidthIdx, borderTopColorIdx, borderBottomWidthIdx, borderBottomColorIdx, borderLeftWidthIdx, borderLeftColorIdx, borderRightWidthIdx, borderRightColorIdx, fontFamilyIdx, fontSizeIdx, fontWeightIdx, lineHeightIdx, positionIdx, topIdx, rightIdx, bottomIdx, leftIdx, displayIdx, widthIdx, heightIdx, verticalAlignIdx] =
Paul Lewisaae7b102019-10-30 14:18:21193 styles;
Paul Lewisde0224c2019-10-22 16:23:15194
Alex Rudenko1c7ae0e2021-04-15 09:37:58195 storeColor(backgroundColorIdx, nodeId, backgroundColors);
Alex Rudenkod55e18c2020-09-23 11:37:06196 const textColor = storeColor(textColorIdx, nodeId, textColors);
Paul Lewisde0224c2019-10-22 16:23:15197
Paul Lewis12bc63e2019-10-23 10:53:29198 if (isSVGNode(strings[nodeName])) {
199 storeColor(fillIdx, nodeId, fillColors);
200 }
201
202 if (strings[borderTopWidthIdx] !== '0px') {
203 storeColor(borderTopColorIdx, nodeId, borderColors);
204 }
205
206 if (strings[borderBottomWidthIdx] !== '0px') {
207 storeColor(borderBottomColorIdx, nodeId, borderColors);
208 }
209
210 if (strings[borderLeftWidthIdx] !== '0px') {
211 storeColor(borderLeftColorIdx, nodeId, borderColors);
212 }
213
214 if (strings[borderRightWidthIdx] !== '0px') {
215 storeColor(borderRightColorIdx, nodeId, borderColors);
216 }
217
Paul Lewis7d10a732019-11-06 16:11:51218 /**
219 * Create a structure like this for font info:
220 *
221 * / size (Map) -- nodes (Array)
222 * /
223 * Font family (Map) ----- weight (Map) -- nodes (Array)
224 * \
225 * \ line-height (Map) -- nodes (Array)
226 */
Paul Lewisbe87fed2020-07-07 12:44:19227 if (fontFamilyIdx && fontFamilyIdx !== -1) {
Paul Lewis7d10a732019-11-06 16:11:51228 const fontFamily = strings[fontFamilyIdx];
229 const fontFamilyInfo = fontInfo.get(fontFamily) || new Map();
Paul Lewisde0224c2019-10-22 16:23:15230
Paul Lewis7d10a732019-11-06 16:11:51231 const sizeLabel = 'font-size';
232 const weightLabel = 'font-weight';
233 const lineHeightLabel = 'line-height';
234
235 const size = fontFamilyInfo.get(sizeLabel) || new Map();
236 const weight = fontFamilyInfo.get(weightLabel) || new Map();
237 const lineHeight = fontFamilyInfo.get(lineHeightLabel) || new Map();
238
239 if (fontSizeIdx !== -1) {
240 const fontSizeValue = strings[fontSizeIdx];
241 const nodes = size.get(fontSizeValue) || [];
242 nodes.push(nodeId);
243 size.set(fontSizeValue, nodes);
244 }
245
246 if (fontWeightIdx !== -1) {
247 const fontWeightValue = strings[fontWeightIdx];
248 const nodes = weight.get(fontWeightValue) || [];
249 nodes.push(nodeId);
250 weight.set(fontWeightValue, nodes);
251 }
252
253 if (lineHeightIdx !== -1) {
254 const lineHeightValue = strings[lineHeightIdx];
255 const nodes = lineHeight.get(lineHeightValue) || [];
256 nodes.push(nodeId);
257 lineHeight.set(lineHeightValue, nodes);
258 }
259
260 // Set the data back.
261 fontFamilyInfo.set(sizeLabel, size);
262 fontFamilyInfo.set(weightLabel, weight);
263 fontFamilyInfo.set(lineHeightLabel, lineHeight);
264 fontInfo.set(fontFamily, fontFamilyInfo);
Paul Lewisde0224c2019-10-22 16:23:15265 }
Paul Lewis8aef9012019-10-29 14:15:17266
Alex Rudenko1c7ae0e2021-04-15 09:37:58267 const blendedBackgroundColor =
268 textColor && layout.blendedBackgroundColors && layout.blendedBackgroundColors[idx] !== -1 ?
269 Common.Color.Color.parse(strings[layout.blendedBackgroundColors[idx]]) :
270 null;
271 if (textColor && blendedBackgroundColor) {
Alex Rudenkod55e18c2020-09-23 11:37:06272 const contrastInfo = new ColorPicker.ContrastInfo.ContrastInfo({
Alex Rudenko1c7ae0e2021-04-15 09:37:58273 backgroundColors: [blendedBackgroundColor.asString(Common.Color.Format.HEXA) as string],
Alex Rudenkod55e18c2020-09-23 11:37:06274 computedFontSize: fontSizeIdx !== -1 ? strings[fontSizeIdx] : '',
275 computedFontWeight: fontWeightIdx !== -1 ? strings[fontWeightIdx] : '',
276 });
Alex Rudenko1c7ae0e2021-04-15 09:37:58277 const blendedTextColor =
278 textColor.blendWithAlpha(layout.textColorOpacities ? layout.textColorOpacities[idx] : 1);
279 contrastInfo.setColor(blendedTextColor);
280 const formattedTextColor = formatColor(blendedTextColor);
281 const formattedBackgroundColor = formatColor(blendedBackgroundColor);
Alex Rudenko69c377e2020-11-27 07:40:35282 const key = `${formattedTextColor}_${formattedBackgroundColor}`;
283 if (Root.Runtime.experiments.isEnabled('APCA')) {
284 const contrastRatio = contrastInfo.contrastRatioAPCA();
285 const threshold = contrastInfo.contrastRatioAPCAThreshold();
286 const passes = contrastRatio && threshold ? Math.abs(contrastRatio) >= threshold : false;
287 if (!passes) {
288 const issue = {
289 nodeId,
290 contrastRatio,
Alex Rudenko1c7ae0e2021-04-15 09:37:58291 textColor: blendedTextColor,
292 backgroundColor: blendedBackgroundColor,
Alex Rudenko69c377e2020-11-27 07:40:35293 thresholdsViolated: {
294 aa: false,
295 aaa: false,
296 apca: true,
297 },
298 };
299 if (textColorContrastIssues.has(key)) {
300 textColorContrastIssues.get(key).push(issue);
301 } else {
302 textColorContrastIssues.set(key, [issue]);
303 }
304 }
305 } else {
306 const aaThreshold = contrastInfo.contrastRatioThreshold('aa') || 0;
307 const aaaThreshold = contrastInfo.contrastRatioThreshold('aaa') || 0;
308 const contrastRatio = contrastInfo.contrastRatio() || 0;
309 if (aaThreshold > contrastRatio || aaaThreshold > contrastRatio) {
310 const issue = {
311 nodeId,
312 contrastRatio,
Alex Rudenko1c7ae0e2021-04-15 09:37:58313 textColor: blendedTextColor,
314 backgroundColor: blendedBackgroundColor,
Alex Rudenko69c377e2020-11-27 07:40:35315 thresholdsViolated: {
316 aa: aaThreshold > contrastRatio,
317 aaa: aaaThreshold > contrastRatio,
318 apca: false,
319 },
320 };
321 if (textColorContrastIssues.has(key)) {
322 textColorContrastIssues.get(key).push(issue);
323 } else {
324 textColorContrastIssues.set(key, [issue]);
325 }
Alex Rudenkod55e18c2020-09-23 11:37:06326 }
327 }
328 }
329
Tim van der Lippe9d0cb5f2020-01-09 14:10:38330 CSSOverviewUnusedDeclarations.checkForUnusedPositionValues(
Paul Lewised808ce2019-11-06 14:21:54331 unusedDeclarations, nodeId, strings, positionIdx, topIdx, leftIdx, rightIdx, bottomIdx);
Paul Lewisbcaccf12019-10-29 14:54:59332
Paul Lewis69fb9b62019-10-29 16:22:33333 // Ignore SVG elements as, despite being inline by default, they can have width & height specified.
334 // Also ignore replaced content, for similar reasons.
335 if (!isSVGNode(strings[nodeName]) && !isReplacedContent(strings[nodeName])) {
Tim van der Lippe9d0cb5f2020-01-09 14:10:38336 CSSOverviewUnusedDeclarations.checkForUnusedWidthAndHeightValues(
Paul Lewised808ce2019-11-06 14:21:54337 unusedDeclarations, nodeId, strings, displayIdx, widthIdx, heightIdx);
Paul Lewisbcaccf12019-10-29 14:54:59338 }
Paul Lewisaae7b102019-10-30 14:18:21339
340 if (verticalAlignIdx !== -1 && !isTableElementWithDefaultStyles(strings[nodeName], strings[displayIdx])) {
Tim van der Lippe9d0cb5f2020-01-09 14:10:38341 CSSOverviewUnusedDeclarations.checkForInvalidVerticalAlignment(
Paul Lewised808ce2019-11-06 14:21:54342 unusedDeclarations, nodeId, strings, displayIdx, verticalAlignIdx);
Paul Lewisaae7b102019-10-30 14:18:21343 }
Paul Lewisde0224c2019-10-22 16:23:15344 }
345 }
346
Alex Rudenkod55e18c2020-09-23 11:37:06347 return {
348 backgroundColors,
349 textColors,
350 textColorContrastIssues,
351 fillColors,
352 borderColors,
353 fontInfo,
354 unusedDeclarations,
Jan Scheffler830467c2021-02-25 13:44:47355 elementCount,
Alex Rudenkod55e18c2020-09-23 11:37:06356 };
Paul Lewisde0224c2019-10-22 16:23:15357 }
358
Jan Scheffler830467c2021-02-25 13:44:47359 getComputedStyleForNode(nodeId: Protocol.DOM.NodeId): Promise<Protocol.CSS.GetComputedStyleForNodeResponse> {
Tim van der Lippe1e4e83c2021-10-12 16:16:58360 return this.#cssAgent.invoke_getComputedStyleForNode({nodeId});
Paul Lewis8cddf9932019-09-27 16:40:07361 }
362
Jan Scheffler830467c2021-02-25 13:44:47363 async getMediaQueries(): Promise<Map<string, Protocol.CSS.CSSMedia[]>> {
Tim van der Lippe1e4e83c2021-10-12 16:16:58364 const queries = await this.#cssAgent.invoke_getMediaQueries();
Jan Scheffler830467c2021-02-25 13:44:47365 const queryMap = new Map<string, Protocol.CSS.CSSMedia[]>();
Paul Lewised808ce2019-11-06 14:21:54366
367 if (!queries) {
368 return queryMap;
369 }
370
Andres Olivares1e6d9c62020-09-03 22:47:38371 for (const query of queries.medias) {
Paul Lewised808ce2019-11-06 14:21:54372 // Ignore media queries applied to stylesheets; instead only use declared media rules.
373 if (query.source === 'linkedSheet') {
374 continue;
375 }
376
Jan Scheffler830467c2021-02-25 13:44:47377 const entries = queryMap.get(query.text) || ([] as Protocol.CSS.CSSMedia[]);
Paul Lewised808ce2019-11-06 14:21:54378 entries.push(query);
379 queryMap.set(query.text, entries);
380 }
381
382 return queryMap;
Paul Lewisebc47192019-10-09 15:06:41383 }
384
Jan Scheffler830467c2021-02-25 13:44:47385 async getGlobalStylesheetStats(): Promise<GlobalStyleStats|void> {
Paul Lewis8cddf9932019-09-27 16:40:07386 // There are no ways to pull CSSOM values directly today, due to its unserializable format,
387 // so instead we execute some JS within the page that extracts the relevant data and send that instead.
388 const expression = `(function() {
389 let styleRules = 0;
Paul Lewis8cddf9932019-09-27 16:40:07390 let inlineStyles = 0;
391 let externalSheets = 0;
Paul Lewisde0224c2019-10-22 16:23:15392 const stats = {
393 // Simple.
394 type: new Set(),
395 class: new Set(),
396 id: new Set(),
397 universal: new Set(),
398 attribute: new Set(),
399
400 // Non-simple.
401 nonSimple: new Set()
402 };
403
404 for (const styleSheet of document.styleSheets) {
405 if (styleSheet.href) {
Paul Lewis8cddf9932019-09-27 16:40:07406 externalSheets++;
407 } else {
408 inlineStyles++;
409 }
410
Paul Lewisde0224c2019-10-22 16:23:15411 // Attempting to grab rules can trigger a DOMException.
412 // Try it and if it fails skip to the next stylesheet.
413 let rules;
414 try {
415 rules = styleSheet.rules;
416 } catch (err) {
417 continue;
418 }
419
Paul Lewis8cddf9932019-09-27 16:40:07420 for (const rule of rules) {
421 if ('selectorText' in rule) {
422 styleRules++;
Paul Lewisde0224c2019-10-22 16:23:15423
424 // Each group that was used.
425 for (const selectorGroup of rule.selectorText.split(',')) {
426 // Each selector in the group.
427 for (const selector of selectorGroup.split(\/[\\t\\n\\f\\r ]+\/g)) {
428 if (selector.startsWith('.')) {
429 // Class.
430 stats.class.add(selector);
431 } else if (selector.startsWith('#')) {
432 // Id.
433 stats.id.add(selector);
434 } else if (selector.startsWith('*')) {
435 // Universal.
436 stats.universal.add(selector);
437 } else if (selector.startsWith('[')) {
438 // Attribute.
439 stats.attribute.add(selector);
440 } else {
441 // Type or non-simple selector.
442 const specialChars = \/[#\.:\\[\\]|\\+>~]\/;
443 if (specialChars.test(selector)) {
444 stats.nonSimple.add(selector);
445 } else {
446 stats.type.add(selector);
447 }
448 }
449 }
450 }
Paul Lewis8cddf9932019-09-27 16:40:07451 }
452 }
453 }
454
455 return {
456 styleRules,
Paul Lewis8cddf9932019-09-27 16:40:07457 inlineStyles,
Paul Lewisde0224c2019-10-22 16:23:15458 externalSheets,
459 stats: {
460 // Simple.
461 type: stats.type.size,
462 class: stats.class.size,
463 id: stats.id.size,
464 universal: stats.universal.size,
465 attribute: stats.attribute.size,
466
467 // Non-simple.
468 nonSimple: stats.nonSimple.size
469 }
Paul Lewis8cddf9932019-09-27 16:40:07470 }
471 })()`;
Tim van der Lippe1e4e83c2021-10-12 16:16:58472 const {result} = await this.#runtimeAgent.invoke_evaluate({expression, returnByValue: true});
Paul Lewis8cddf9932019-09-27 16:40:07473
474 // TODO(paullewis): Handle errors properly.
Tim van der Lippe1d6e57a2019-09-30 11:55:34475 if (result.type !== 'object') {
Paul Lewis8cddf9932019-09-27 16:40:07476 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34477 }
Paul Lewis8cddf9932019-09-27 16:40:07478
479 return result.value;
480 }
Paul Lewis4da3b302019-11-21 14:23:47481}
Paul Lewis8cddf9932019-09-27 16:40:07482
Sigurd Schneider79e812e2021-06-04 06:48:23483SDK.SDKModel.SDKModel.register(CSSOverviewModel, {capabilities: SDK.Target.Capability.DOM, autostart: false});