blob: e4ac5a544ef91efc5bb4ca28fb703ebbaffcba7c [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 Lippebb352e62021-04-01 17:57:286import * as i18n from '../../core/i18n/i18n.js';
Tim van der Lippeaa1ed7a2021-03-31 14:38:277import * as Platform from '../../core/platform/platform.js';
Tim van der Lipped8caac42021-03-31 14:40:448import * as Root from '../../core/root/root.js';
Tim van der Lippee00b92f2021-03-31 16:52:179import * as SDK from '../../core/sdk/sdk.js';
Tim van der Lippefca98ed2021-04-08 14:10:1410import * as TextUtils from '../../models/text_utils/text_utils.js';
Tim van der Lippe8499fe22021-04-12 16:42:4711import * as DataGrid from '../../ui/legacy/components/data_grid/data_grid.js';
Tim van der Lippe339ad262021-04-21 12:23:3612import * as Components from '../../ui/legacy/components/utils/utils.js';
Tim van der Lippeaa61faf2021-04-07 15:32:0713import * as UI from '../../ui/legacy/legacy.js';
Tim van der Lippe229a54f2021-05-14 16:59:0514import type * as Protocol from '../../generated/protocol.js';
Tim van der Lippe7946bbc2020-02-13 13:58:4215
Tim van der Lippef8c26e22021-11-03 11:46:5816import cssOverviewCompletedViewStyles from './cssOverviewCompletedView.css.js';
Jack Frankline839c0c2022-05-03 08:47:4417import type {
18 OverviewController, PopulateNodesEvent, PopulateNodesEventNodes, PopulateNodesEventNodeTypes} from
19 './CSSOverviewController.js';
Kateryna Prokopenko4a802532021-09-16 08:57:0620import {Events as CSSOverViewControllerEvents} from './CSSOverviewController.js';
Tim van der Lippe9d0cb5f2020-01-09 14:10:3821import {CSSOverviewSidebarPanel, SidebarEvents} from './CSSOverviewSidebarPanel.js';
Tim van der Lippebfbb58f2021-02-25 17:34:1922import type {UnusedDeclaration} from './CSSOverviewUnusedDeclarations.js';
Andres Olivares3dcefe42020-09-14 16:15:4223
Simon Zündfbfd1072021-03-01 07:38:5324const UIStrings = {
Christy Chenb6e28b62021-01-22 08:18:0025 /**
26 *@description Label for the summary in the CSS Overview report
27 */
28 overviewSummary: 'Overview summary',
29 /**
30 *@description Title of colors subsection in the CSS Overview Panel
31 */
32 colors: 'Colors',
33 /**
34 *@description Title of font info subsection in the CSS Overview Panel
35 */
36 fontInfo: 'Font info',
37 /**
38 *@description Label to denote unused declarations in the target page
39 */
40 unusedDeclarations: 'Unused declarations',
41 /**
42 *@description Label for the number of media queries in the CSS Overview report
43 */
44 mediaQueries: 'Media queries',
45 /**
46 *@description Title of the Elements Panel
47 */
48 elements: 'Elements',
49 /**
50 *@description Label for the number of External stylesheets in the CSS Overview report
51 */
52 externalStylesheets: 'External stylesheets',
53 /**
54 *@description Label for the number of inline style elements in the CSS Overview report
55 */
56 inlineStyleElements: 'Inline style elements',
57 /**
58 *@description Label for the number of style rules in CSS Overview report
59 */
60 styleRules: 'Style rules',
61 /**
62 *@description Label for the number of type selectors in the CSS Overview report
63 */
64 typeSelectors: 'Type selectors',
65 /**
66 *@description Label for the number of ID selectors in the CSS Overview report
67 */
68 idSelectors: 'ID selectors',
69 /**
70 *@description Label for the number of class selectors in the CSS Overview report
71 */
72 classSelectors: 'Class selectors',
73 /**
74 *@description Label for the number of universal selectors in the CSS Overview report
75 */
76 universalSelectors: 'Universal selectors',
77 /**
78 *@description Label for the number of Attribute selectors in the CSS Overview report
79 */
80 attributeSelectors: 'Attribute selectors',
81 /**
82 *@description Label for the number of non-simple selectors in the CSS Overview report
83 */
84 nonsimpleSelectors: 'Non-simple selectors',
85 /**
86 *@description Label for unique background colors in the CSS Overview Panel
87 *@example {32} PH1
88 */
89 backgroundColorsS: 'Background colors: {PH1}',
90 /**
91 *@description Label for unique text colors in the CSS Overview Panel
92 *@example {32} PH1
93 */
94 textColorsS: 'Text colors: {PH1}',
95 /**
96 *@description Label for unique fill colors in the CSS Overview Panel
97 *@example {32} PH1
98 */
99 fillColorsS: 'Fill colors: {PH1}',
100 /**
101 *@description Label for unique border colors in the CSS Overview Panel
102 *@example {32} PH1
103 */
104 borderColorsS: 'Border colors: {PH1}',
105 /**
106 *@description Label to indicate that there are no fonts in use
107 */
108 thereAreNoFonts: 'There are no fonts.',
109 /**
110 *@description Message to show when no unused declarations in the target page
111 */
112 thereAreNoUnusedDeclarations: 'There are no unused declarations.',
113 /**
114 *@description Message to show when no media queries are found in the target page
115 */
116 thereAreNoMediaQueries: 'There are no media queries.',
117 /**
118 *@description Title of the Drawer for contrast issues in the CSS Overview Panel
119 */
120 contrastIssues: 'Contrast issues',
121 /**
Peter Marshall645f7bf2021-03-04 09:56:20122 * @description Text to indicate how many times this CSS rule showed up.
Christy Chenb6e28b62021-01-22 08:18:00123 */
Peter Marshall5166fad2021-03-04 10:06:49124 nOccurrences: '{n, plural, =1 {# occurrence} other {# occurrences}}',
Christy Chenb6e28b62021-01-22 08:18:00125 /**
126 *@description Section header for contrast issues in the CSS Overview Panel
127 *@example {1} PH1
128 */
129 contrastIssuesS: 'Contrast issues: {PH1}',
130 /**
131 *@description Title of the button for a contrast issue in the CSS Overview Panel
132 *@example {#333333} PH1
133 *@example {#333333} PH2
134 *@example {2} PH3
135 */
136 textColorSOverSBackgroundResults: 'Text color {PH1} over {PH2} background results in low contrast for {PH3} elements',
137 /**
138 *@description Label aa text content in Contrast Details of the Color Picker
139 */
140 aa: 'AA',
141 /**
142 *@description Label aaa text content in Contrast Details of the Color Picker
143 */
144 aaa: 'AAA',
145 /**
146 *@description Label for the APCA contrast in Color Picker
147 */
148 apca: 'APCA',
149 /**
150 *@description Label for the column in the element list in the CSS Overview report
151 */
152 element: 'Element',
153 /**
154 *@description Column header title denoting which declaration is unused
155 */
156 declaration: 'Declaration',
157 /**
158 *@description Text for the source of something
159 */
160 source: 'Source',
161 /**
162 *@description Text of a DOM element in Contrast Details of the Color Picker
163 */
164 contrastRatio: 'Contrast ratio',
165 /**
Peter Marshall3d967382021-02-25 06:56:18166 *@description Accessible title of a table in the CSS Overview Elements.
Christy Chenb6e28b62021-01-22 08:18:00167 */
168 cssOverviewElements: 'CSS Overview Elements',
169 /**
170 *@description Title of the button to show the element in the CSS Overview panel
171 */
172 showElement: 'Show element',
173};
Tim van der Lippe1d7474a2021-03-19 15:41:06174const str_ = i18n.i18n.registerUIStrings('panels/css_overview/CSSOverviewCompletedView.ts', UIStrings);
Christy Chenb6e28b62021-01-22 08:18:00175const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
Alex Rudenko69c377e2020-11-27 07:40:35176
Jan Scheffler830467c2021-02-25 13:44:47177export type NodeStyleStats = Map<string, Set<number>>;
Andres Olivares3dcefe42020-09-14 16:15:42178
Jan Scheffler830467c2021-02-25 13:44:47179export interface ContrastIssue {
Philip Pfaffe8071e532021-09-20 08:27:15180 nodeId: Protocol.DOM.BackendNodeId;
Jan Scheffler830467c2021-02-25 13:44:47181 contrastRatio: number;
182 textColor: Common.Color.Color;
183 backgroundColor: Common.Color.Color;
184 thresholdsViolated: {
185 aa: boolean,
186 aaa: boolean,
187 apca: boolean,
188 };
189}
190export interface OverviewData {
Philip Pfaffe8071e532021-09-20 08:27:15191 backgroundColors: Map<string, Set<Protocol.DOM.BackendNodeId>>;
192 textColors: Map<string, Set<Protocol.DOM.BackendNodeId>>;
Jan Scheffler830467c2021-02-25 13:44:47193 textColorContrastIssues: Map<string, ContrastIssue[]>;
Philip Pfaffe8071e532021-09-20 08:27:15194 fillColors: Map<string, Set<Protocol.DOM.BackendNodeId>>;
195 borderColors: Map<string, Set<Protocol.DOM.BackendNodeId>>;
Jan Scheffler830467c2021-02-25 13:44:47196 globalStyleStats: {
197 styleRules: number,
198 inlineStyles: number,
199 externalSheets: number,
200 stats: {type: number, class: number, id: number, universal: number, attribute: number, nonSimple: number},
201 };
Philip Pfaffe8071e532021-09-20 08:27:15202 fontInfo: Map<string, Map<string, Map<string, Protocol.DOM.BackendNodeId[]>>>;
Jan Scheffler830467c2021-02-25 13:44:47203 elementCount: number;
204 mediaQueries: Map<string, Protocol.CSS.CSSMedia[]>;
205 unusedDeclarations: Map<string, UnusedDeclaration[]>;
206}
Alex Rudenkod55e18c2020-09-23 11:37:06207
Jan Scheffler830467c2021-02-25 13:44:47208export type FontInfo = Map<string, Map<string, Map<string, number[]>>>;
Andres Olivares3dcefe42020-09-14 16:15:42209
Jan Scheffler830467c2021-02-25 13:44:47210function getBorderString(color: Common.Color.Color): string {
Alex Rudenkod3ba0ee2020-09-25 14:01:21211 let [h, s, l] = color.hsla();
212 h = Math.round(h * 360);
213 s = Math.round(s * 100);
214 l = Math.round(l * 100);
215
216 // Reduce the lightness of the border to make sure that there's always a visible outline.
217 l = Math.max(0, l - 15);
218
219 return `1px solid hsl(${h}deg ${s}% ${l}%)`;
220}
221
Tim van der Lippe7946bbc2020-02-13 13:58:42222export class CSSOverviewCompletedView extends UI.Panel.PanelWithSidebar {
Tim van der Lippe1e4e83c2021-10-12 16:16:58223 #controller: OverviewController;
224 #formatter: Intl.NumberFormat;
225 readonly #mainContainer: UI.SplitWidget.SplitWidget;
226 readonly #resultsContainer: UI.Widget.VBox;
227 readonly #elementContainer: DetailsView;
228 readonly #sideBar: CSSOverviewSidebarPanel;
Tim van der Lippef8c26e22021-11-03 11:46:58229 #cssModel?: SDK.CSSModel.CSSModel;
230 #domModel?: SDK.DOMModel.DOMModel;
Tim van der Lippe1e4e83c2021-10-12 16:16:58231 #linkifier: Components.Linkifier.Linkifier;
232 #viewMap: Map<string, ElementDetailsView>;
233 #data: OverviewData|null;
234 #fragment?: UI.Fragment.Fragment;
Jan Scheffler830467c2021-02-25 13:44:47235
Tim van der Lippef8c26e22021-11-03 11:46:58236 constructor(controller: OverviewController) {
Paul Lewis8cddf9932019-09-27 16:40:07237 super('css_overview_completed_view');
Paul Lewis8cddf9932019-09-27 16:40:07238
Tim van der Lippe1e4e83c2021-10-12 16:16:58239 this.#controller = controller;
240 this.#formatter = new Intl.NumberFormat('en-US');
Paul Lewisde0224c2019-10-22 16:23:15241
Tim van der Lippe1e4e83c2021-10-12 16:16:58242 this.#mainContainer = new UI.SplitWidget.SplitWidget(true, true);
243 this.#resultsContainer = new UI.Widget.VBox();
244 this.#elementContainer = new DetailsView();
Paul Lewised808ce2019-11-06 14:21:54245
246 // If closing the last tab, collapse the sidebar.
Tim van der Lippe1e4e83c2021-10-12 16:16:58247 this.#elementContainer.addEventListener(Events.TabClosed, evt => {
Paul Lewised808ce2019-11-06 14:21:54248 if (evt.data === 0) {
Tim van der Lippe1e4e83c2021-10-12 16:16:58249 this.#mainContainer.setSidebarMinimized(true);
Paul Lewised808ce2019-11-06 14:21:54250 }
251 });
Paul Lewisde0224c2019-10-22 16:23:15252
253 // Dupe the styles into the main container because of the shadow root will prevent outer styles.
Paul Lewisde0224c2019-10-22 16:23:15254
Tim van der Lippe1e4e83c2021-10-12 16:16:58255 this.#mainContainer.setMainWidget(this.#resultsContainer);
256 this.#mainContainer.setSidebarWidget(this.#elementContainer);
257 this.#mainContainer.setVertical(false);
258 this.#mainContainer.setSecondIsSidebar(true);
259 this.#mainContainer.setSidebarMinimized(true);
Paul Lewis8cddf9932019-09-27 16:40:07260
Tim van der Lippe1e4e83c2021-10-12 16:16:58261 this.#sideBar = new CSSOverviewSidebarPanel();
Jacky Hu6e564632022-04-14 09:30:00262 this.#sideBar.setMinimumSize(100, 25);
Tim van der Lippe1e4e83c2021-10-12 16:16:58263 this.splitWidget().setSidebarWidget(this.#sideBar);
264 this.splitWidget().setMainWidget(this.#mainContainer);
Paul Lewis8cddf9932019-09-27 16:40:07265
Tim van der Lippe1e4e83c2021-10-12 16:16:58266 this.#linkifier = new Components.Linkifier.Linkifier(/* maxLinkLength */ 20, /* useLinkDecorator */ true);
Paul Lewis8aef9012019-10-29 14:15:17267
Tim van der Lippe1e4e83c2021-10-12 16:16:58268 this.#viewMap = new Map();
Paul Lewisebc47192019-10-09 15:06:41269
Tim van der Lippe1e4e83c2021-10-12 16:16:58270 this.#sideBar.addItem(i18nString(UIStrings.overviewSummary), 'summary');
271 this.#sideBar.addItem(i18nString(UIStrings.colors), 'colors');
272 this.#sideBar.addItem(i18nString(UIStrings.fontInfo), 'font-info');
273 this.#sideBar.addItem(i18nString(UIStrings.unusedDeclarations), 'unused-declarations');
274 this.#sideBar.addItem(i18nString(UIStrings.mediaQueries), 'media-queries');
275 this.#sideBar.select('summary');
Paul Lewis8cddf9932019-09-27 16:40:07276
Tim van der Lippe01601572021-12-15 15:36:29277 this.#sideBar.addEventListener(SidebarEvents.ItemSelected, this.#sideBarItemSelected, this);
278 this.#sideBar.addEventListener(SidebarEvents.Reset, this.#sideBarReset, this);
279 this.#controller.addEventListener(CSSOverViewControllerEvents.Reset, this.#reset, this);
280 this.#controller.addEventListener(CSSOverViewControllerEvents.PopulateNodes, this.#createElementsView, this);
281 this.#resultsContainer.element.addEventListener('click', this.#onClick.bind(this));
Paul Lewisde0224c2019-10-22 16:23:15282
Tim van der Lippe1e4e83c2021-10-12 16:16:58283 this.#data = null;
Paul Lewis8cddf9932019-09-27 16:40:07284 }
285
Jan Scheffler830467c2021-02-25 13:44:47286 wasShown(): void {
Paul Lewised808ce2019-11-06 14:21:54287 super.wasShown();
Tim van der Lippe1e4e83c2021-10-12 16:16:58288 this.#mainContainer.registerCSSFiles([cssOverviewCompletedViewStyles]);
Kriti Sapraf09e2e62021-08-02 09:19:33289 this.registerCSSFiles([cssOverviewCompletedViewStyles]);
Paul Lewisebc47192019-10-09 15:06:41290
Paul Lewised808ce2019-11-06 14:21:54291 // TODO(paullewis): update the links in the panels in case source has been .
Paul Lewis8aef9012019-10-29 14:15:17292 }
293
Tim van der Lippef8c26e22021-11-03 11:46:58294 initializeModels(target: SDK.Target.Target): void {
295 const cssModel = target.model(SDK.CSSModel.CSSModel);
296 const domModel = target.model(SDK.DOMModel.DOMModel);
297 if (!cssModel || !domModel) {
298 throw new Error('Target must provide CSS and DOM models');
299 }
300 this.#cssModel = cssModel;
301 this.#domModel = domModel;
302 }
303
Tim van der Lippe01601572021-12-15 15:36:29304 #sideBarItemSelected(event: Common.EventTarget.EventTargetEvent<string>): void {
Kateryna Prokopenkod79121e2021-09-15 11:08:38305 const {data} = event;
Tim van der Lippe1e4e83c2021-10-12 16:16:58306 const section = (this.#fragment as UI.Fragment.Fragment).$(data);
Tim van der Lippe1d6e57a2019-09-30 11:55:34307 if (!section) {
Paul Lewis8cddf9932019-09-27 16:40:07308 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34309 }
Paul Lewis8cddf9932019-09-27 16:40:07310
311 section.scrollIntoView();
312 }
313
Tim van der Lippe01601572021-12-15 15:36:29314 #sideBarReset(): void {
Tim van der Lippe1e4e83c2021-10-12 16:16:58315 this.#controller.dispatchEventToListeners(CSSOverViewControllerEvents.Reset);
Paul Lewis8cddf9932019-09-27 16:40:07316 }
317
Tim van der Lippe01601572021-12-15 15:36:29318 #reset(): void {
Tim van der Lippe1e4e83c2021-10-12 16:16:58319 this.#resultsContainer.element.removeChildren();
320 this.#mainContainer.setSidebarMinimized(true);
321 this.#elementContainer.closeTabs();
322 this.#viewMap = new Map();
Alex Rudenkoaf4d9902020-11-25 09:18:07323 CSSOverviewCompletedView.pushedNodes.clear();
Tim van der Lippe1e4e83c2021-10-12 16:16:58324 this.#sideBar.select('summary');
Paul Lewis8cddf9932019-09-27 16:40:07325 }
326
Tim van der Lippe01601572021-12-15 15:36:29327 #onClick(evt: Event): void {
Andres Olivares3dcefe42020-09-14 16:15:42328 if (!evt.target) {
329 return;
330 }
Jan Scheffler830467c2021-02-25 13:44:47331 const target = (evt.target as HTMLElement);
Andres Olivares3dcefe42020-09-14 16:15:42332 const dataset = target.dataset;
333
334 const type = dataset.type;
Tim van der Lippe1e4e83c2021-10-12 16:16:58335 if (!type || !this.#data) {
Paul Lewis8cddf9932019-09-27 16:40:07336 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34337 }
Paul Lewis8cddf9932019-09-27 16:40:07338
Philip Pfaffecc7d9382021-09-17 11:55:10339 let payload: PopulateNodesEvent;
Paul Lewised808ce2019-11-06 14:21:54340 switch (type) {
Alex Rudenkod55e18c2020-09-23 11:37:06341 case 'contrast': {
342 const section = dataset.section;
343 const key = dataset.key;
344
345 if (!key) {
346 return;
347 }
348
349 // Remap the Set to an object that is the same shape as the unused declarations.
Tim van der Lippe1e4e83c2021-10-12 16:16:58350 const nodes = this.#data.textColorContrastIssues.get(key) || [];
Alex Rudenkod55e18c2020-09-23 11:37:06351 payload = {type, key, nodes, section};
352 break;
353 }
Paul Lewised808ce2019-11-06 14:21:54354 case 'color': {
Andres Olivares3dcefe42020-09-14 16:15:42355 const color = dataset.color;
356 const section = dataset.section;
Paul Lewised808ce2019-11-06 14:21:54357 if (!color) {
358 return;
359 }
Paul Lewis12bc63e2019-10-23 10:53:29360
Paul Lewised808ce2019-11-06 14:21:54361 let nodes;
362 switch (section) {
363 case 'text':
Tim van der Lippe1e4e83c2021-10-12 16:16:58364 nodes = this.#data.textColors.get(color);
Paul Lewised808ce2019-11-06 14:21:54365 break;
Paul Lewis12bc63e2019-10-23 10:53:29366
Paul Lewised808ce2019-11-06 14:21:54367 case 'background':
Tim van der Lippe1e4e83c2021-10-12 16:16:58368 nodes = this.#data.backgroundColors.get(color);
Paul Lewised808ce2019-11-06 14:21:54369 break;
Paul Lewis12bc63e2019-10-23 10:53:29370
Paul Lewised808ce2019-11-06 14:21:54371 case 'fill':
Tim van der Lippe1e4e83c2021-10-12 16:16:58372 nodes = this.#data.fillColors.get(color);
Paul Lewised808ce2019-11-06 14:21:54373 break;
Paul Lewis12bc63e2019-10-23 10:53:29374
Paul Lewised808ce2019-11-06 14:21:54375 case 'border':
Tim van der Lippe1e4e83c2021-10-12 16:16:58376 nodes = this.#data.borderColors.get(color);
Paul Lewised808ce2019-11-06 14:21:54377 break;
378 }
379
380 if (!nodes) {
381 return;
382 }
383
384 // Remap the Set to an object that is the same shape as the unused declarations.
385 nodes = Array.from(nodes).map(nodeId => ({nodeId}));
386 payload = {type, color, nodes, section};
387 break;
388 }
389
390 case 'unused-declarations': {
Andres Olivares3dcefe42020-09-14 16:15:42391 const declaration = dataset.declaration;
392 if (!declaration) {
393 return;
394 }
Tim van der Lippe1e4e83c2021-10-12 16:16:58395 const nodes = this.#data.unusedDeclarations.get(declaration);
Paul Lewised808ce2019-11-06 14:21:54396 if (!nodes) {
397 return;
398 }
399
400 payload = {type, declaration, nodes};
401 break;
402 }
403
404 case 'media-queries': {
Andres Olivares3dcefe42020-09-14 16:15:42405 const text = dataset.text;
406 if (!text) {
407 return;
408 }
Tim van der Lippe1e4e83c2021-10-12 16:16:58409 const nodes = this.#data.mediaQueries.get(text);
Paul Lewised808ce2019-11-06 14:21:54410 if (!nodes) {
411 return;
412 }
413
414 payload = {type, text, nodes};
415 break;
416 }
417
Paul Lewis7d10a732019-11-06 16:11:51418 case 'font-info': {
Andres Olivares3dcefe42020-09-14 16:15:42419 const value = dataset.value;
420 if (!dataset.path) {
421 return;
422 }
423
424 const [fontFamily, fontMetric] = dataset.path.split('/');
425 if (!value) {
426 return;
427 }
428
Tim van der Lippe1e4e83c2021-10-12 16:16:58429 const fontFamilyInfo = this.#data.fontInfo.get(fontFamily);
Andres Olivares3dcefe42020-09-14 16:15:42430 if (!fontFamilyInfo) {
431 return;
432 }
433
434 const fontMetricInfo = fontFamilyInfo.get(fontMetric);
435 if (!fontMetricInfo) {
436 return;
437 }
438
Philip Pfaffe8071e532021-09-20 08:27:15439 const nodesIds = fontMetricInfo.get(value);
Paul Lewis7d10a732019-11-06 16:11:51440 if (!nodesIds) {
441 return;
442 }
443
444 const nodes = nodesIds.map(nodeId => ({nodeId}));
445 const name = `${value} (${fontFamily}, ${fontMetric})`;
446 payload = {type, name, nodes};
447 break;
448 }
449
Paul Lewised808ce2019-11-06 14:21:54450 default:
451 return;
Paul Lewisde0224c2019-10-22 16:23:15452 }
453
454 evt.consume();
Tim van der Lippe1e4e83c2021-10-12 16:16:58455 this.#controller.dispatchEventToListeners(CSSOverViewControllerEvents.PopulateNodes, {payload});
456 this.#mainContainer.setSidebarMinimized(false);
Paul Lewisde0224c2019-10-22 16:23:15457 }
458
Tim van der Lippe01601572021-12-15 15:36:29459 async #render(data: OverviewData): Promise<void> {
Paul Lewisde0224c2019-10-22 16:23:15460 if (!data || !('backgroundColors' in data) || !('textColors' in data)) {
461 return;
462 }
463
Tim van der Lippe1e4e83c2021-10-12 16:16:58464 this.#data = data;
Paul Lewis8aef9012019-10-29 14:15:17465 const {
466 elementCount,
467 backgroundColors,
468 textColors,
Alex Rudenkod55e18c2020-09-23 11:37:06469 textColorContrastIssues,
Paul Lewis8aef9012019-10-29 14:15:17470 fillColors,
471 borderColors,
472 globalStyleStats,
473 mediaQueries,
Paul Lewis7d10a732019-11-06 16:11:51474 unusedDeclarations,
Jan Scheffler830467c2021-02-25 13:44:47475 fontInfo,
Tim van der Lippe1e4e83c2021-10-12 16:16:58476 } = this.#data;
Paul Lewis8cddf9932019-09-27 16:40:07477
478 // Convert rgb values from the computed styles to either undefined or HEX(A) strings.
Tim van der Lippe01601572021-12-15 15:36:29479 const sortedBackgroundColors = this.#sortColorsByLuminance(backgroundColors);
480 const sortedTextColors = this.#sortColorsByLuminance(textColors);
481 const sortedFillColors = this.#sortColorsByLuminance(fillColors);
482 const sortedBorderColors = this.#sortColorsByLuminance(borderColors);
Paul Lewis8cddf9932019-09-27 16:40:07483
Tim van der Lippe1e4e83c2021-10-12 16:16:58484 this.#fragment = UI.Fragment.Fragment.build`
Paul Lewis8cddf9932019-09-27 16:40:07485 <div class="vbox overview-completed-view">
Paul Lewis8aef9012019-10-29 14:15:17486 <div $="summary" class="results-section horizontally-padded summary">
Christy Chenb6e28b62021-01-22 08:18:00487 <h1>${i18nString(UIStrings.overviewSummary)}</h1>
Paul Lewis8cddf9932019-09-27 16:40:07488
489 <ul>
490 <li>
Christy Chenb6e28b62021-01-22 08:18:00491 <div class="label">${i18nString(UIStrings.elements)}</div>
Tim van der Lippe1e4e83c2021-10-12 16:16:58492 <div class="value">${this.#formatter.format(elementCount)}</div>
Paul Lewis8cddf9932019-09-27 16:40:07493 </li>
494 <li>
Christy Chenb6e28b62021-01-22 08:18:00495 <div class="label">${i18nString(UIStrings.externalStylesheets)}</div>
Tim van der Lippe1e4e83c2021-10-12 16:16:58496 <div class="value">${this.#formatter.format(globalStyleStats.externalSheets)}</div>
Paul Lewis8cddf9932019-09-27 16:40:07497 </li>
498 <li>
Christy Chenb6e28b62021-01-22 08:18:00499 <div class="label">${i18nString(UIStrings.inlineStyleElements)}</div>
Tim van der Lippe1e4e83c2021-10-12 16:16:58500 <div class="value">${this.#formatter.format(globalStyleStats.inlineStyles)}</div>
Paul Lewis8cddf9932019-09-27 16:40:07501 </li>
502 <li>
Christy Chenb6e28b62021-01-22 08:18:00503 <div class="label">${i18nString(UIStrings.styleRules)}</div>
Tim van der Lippe1e4e83c2021-10-12 16:16:58504 <div class="value">${this.#formatter.format(globalStyleStats.styleRules)}</div>
Paul Lewis8cddf9932019-09-27 16:40:07505 </li>
506 <li>
Christy Chenb6e28b62021-01-22 08:18:00507 <div class="label">${i18nString(UIStrings.mediaQueries)}</div>
Tim van der Lippe1e4e83c2021-10-12 16:16:58508 <div class="value">${this.#formatter.format(mediaQueries.size)}</div>
Paul Lewis8cddf9932019-09-27 16:40:07509 </li>
510 <li>
Christy Chenb6e28b62021-01-22 08:18:00511 <div class="label">${i18nString(UIStrings.typeSelectors)}</div>
Tim van der Lippe1e4e83c2021-10-12 16:16:58512 <div class="value">${this.#formatter.format(globalStyleStats.stats.type)}</div>
Paul Lewis8cddf9932019-09-27 16:40:07513 </li>
514 <li>
Christy Chenb6e28b62021-01-22 08:18:00515 <div class="label">${i18nString(UIStrings.idSelectors)}</div>
Tim van der Lippe1e4e83c2021-10-12 16:16:58516 <div class="value">${this.#formatter.format(globalStyleStats.stats.id)}</div>
Paul Lewis8cddf9932019-09-27 16:40:07517 </li>
518 <li>
Christy Chenb6e28b62021-01-22 08:18:00519 <div class="label">${i18nString(UIStrings.classSelectors)}</div>
Tim van der Lippe1e4e83c2021-10-12 16:16:58520 <div class="value">${this.#formatter.format(globalStyleStats.stats.class)}</div>
Paul Lewis8cddf9932019-09-27 16:40:07521 </li>
522 <li>
Christy Chenb6e28b62021-01-22 08:18:00523 <div class="label">${i18nString(UIStrings.universalSelectors)}</div>
Tim van der Lippe1e4e83c2021-10-12 16:16:58524 <div class="value">${this.#formatter.format(globalStyleStats.stats.universal)}</div>
Paul Lewis8cddf9932019-09-27 16:40:07525 </li>
526 <li>
Christy Chenb6e28b62021-01-22 08:18:00527 <div class="label">${i18nString(UIStrings.attributeSelectors)}</div>
Tim van der Lippe1e4e83c2021-10-12 16:16:58528 <div class="value">${this.#formatter.format(globalStyleStats.stats.attribute)}</div>
Paul Lewis8cddf9932019-09-27 16:40:07529 </li>
530 <li>
Christy Chenb6e28b62021-01-22 08:18:00531 <div class="label">${i18nString(UIStrings.nonsimpleSelectors)}</div>
Tim van der Lippe1e4e83c2021-10-12 16:16:58532 <div class="value">${this.#formatter.format(globalStyleStats.stats.nonSimple)}</div>
Paul Lewis8cddf9932019-09-27 16:40:07533 </li>
534 </ul>
535 </div>
536
Paul Lewis8aef9012019-10-29 14:15:17537 <div $="colors" class="results-section horizontally-padded colors">
Christy Chenb6e28b62021-01-22 08:18:00538 <h1>${i18nString(UIStrings.colors)}</h1>
539 <h2>${i18nString(UIStrings.backgroundColorsS, {
Jan Scheffler830467c2021-02-25 13:44:47540 PH1: sortedBackgroundColors.length,
Christy Chenb6e28b62021-01-22 08:18:00541 })}</h2>
Paul Lewis8cddf9932019-09-27 16:40:07542 <ul>
Tim van der Lippe01601572021-12-15 15:36:29543 ${sortedBackgroundColors.map(this.#colorsToFragment.bind(this, 'background'))}
Paul Lewis8cddf9932019-09-27 16:40:07544 </ul>
545
Christy Chenb6e28b62021-01-22 08:18:00546 <h2>${i18nString(UIStrings.textColorsS, {
Jan Scheffler830467c2021-02-25 13:44:47547 PH1: sortedTextColors.length,
Christy Chenb6e28b62021-01-22 08:18:00548 })}</h2>
Paul Lewis8cddf9932019-09-27 16:40:07549 <ul>
Tim van der Lippe01601572021-12-15 15:36:29550 ${sortedTextColors.map(this.#colorsToFragment.bind(this, 'text'))}
Paul Lewis8cddf9932019-09-27 16:40:07551 </ul>
Paul Lewis12bc63e2019-10-23 10:53:29552
Tim van der Lippe01601572021-12-15 15:36:29553 ${textColorContrastIssues.size > 0 ? this.#contrastIssuesToFragment(textColorContrastIssues) : ''}
Alex Rudenkod55e18c2020-09-23 11:37:06554
Christy Chenb6e28b62021-01-22 08:18:00555 <h2>${i18nString(UIStrings.fillColorsS, {
Jan Scheffler830467c2021-02-25 13:44:47556 PH1: sortedFillColors.length,
Christy Chenb6e28b62021-01-22 08:18:00557 })}</h2>
Paul Lewis12bc63e2019-10-23 10:53:29558 <ul>
Tim van der Lippe01601572021-12-15 15:36:29559 ${sortedFillColors.map(this.#colorsToFragment.bind(this, 'fill'))}
Paul Lewis12bc63e2019-10-23 10:53:29560 </ul>
561
Christy Chenb6e28b62021-01-22 08:18:00562 <h2>${i18nString(UIStrings.borderColorsS, {
Jan Scheffler830467c2021-02-25 13:44:47563 PH1: sortedBorderColors.length,
Christy Chenb6e28b62021-01-22 08:18:00564 })}</h2>
Paul Lewis12bc63e2019-10-23 10:53:29565 <ul>
Tim van der Lippe01601572021-12-15 15:36:29566 ${sortedBorderColors.map(this.#colorsToFragment.bind(this, 'border'))}
Paul Lewis12bc63e2019-10-23 10:53:29567 </ul>
Paul Lewis8cddf9932019-09-27 16:40:07568 </div>
Paul Lewisebc47192019-10-09 15:06:41569
Paul Lewis66a15fc2019-11-07 11:17:11570 <div $="font-info" class="results-section font-info">
Christy Chenb6e28b62021-01-22 08:18:00571 <h1>${i18nString(UIStrings.fontInfo)}</h1>
Paul Lewis7d10a732019-11-06 16:11:51572 ${
Tim van der Lippe01601572021-12-15 15:36:29573 fontInfo.size > 0 ? this.#fontInfoToFragment(fontInfo) :
Christy Chenb6e28b62021-01-22 08:18:00574 UI.Fragment.Fragment.build`<div>${i18nString(UIStrings.thereAreNoFonts)}</div>`}
Paul Lewis7d10a732019-11-06 16:11:51575 </div>
576
Paul Lewised808ce2019-11-06 14:21:54577 <div $="unused-declarations" class="results-section unused-declarations">
Christy Chenb6e28b62021-01-22 08:18:00578 <h1>${i18nString(UIStrings.unusedDeclarations)}</h1>
Paul Lewisa3dcbf32019-10-29 14:41:51579 ${
Tim van der Lippe01601572021-12-15 15:36:29580 unusedDeclarations.size > 0 ? this.#groupToFragment(unusedDeclarations, 'unused-declarations', 'declaration') :
Christy Chenb6e28b62021-01-22 08:18:00581 UI.Fragment.Fragment.build`<div class="horizontally-padded">${
582 i18nString(UIStrings.thereAreNoUnusedDeclarations)}</div>`}
Paul Lewis8aef9012019-10-29 14:15:17583 </div>
584
Paul Lewisebc47192019-10-09 15:06:41585 <div $="media-queries" class="results-section media-queries">
Christy Chenb6e28b62021-01-22 08:18:00586 <h1>${i18nString(UIStrings.mediaQueries)}</h1>
Paul Lewisa3dcbf32019-10-29 14:41:51587 ${
Tim van der Lippe01601572021-12-15 15:36:29588 mediaQueries.size > 0 ? this.#groupToFragment(mediaQueries, 'media-queries', 'text') :
Christy Chenb6e28b62021-01-22 08:18:00589 UI.Fragment.Fragment.build`<div class="horizontally-padded">${
590 i18nString(UIStrings.thereAreNoMediaQueries)}</div>`}
Paul Lewisebc47192019-10-09 15:06:41591 </div>
Paul Lewis8cddf9932019-09-27 16:40:07592 </div>`;
593
Tim van der Lippe1e4e83c2021-10-12 16:16:58594 this.#resultsContainer.element.appendChild(this.#fragment.element());
Paul Lewis8cddf9932019-09-27 16:40:07595 }
596
Tim van der Lippe01601572021-12-15 15:36:29597 #createElementsView(evt: Common.EventTarget.EventTargetEvent<{payload: PopulateNodesEvent}>): void {
Philip Pfaffecc7d9382021-09-17 11:55:10598 const {payload} = evt.data;
Paul Lewisde0224c2019-10-22 16:23:15599
Paul Lewised808ce2019-11-06 14:21:54600 let id = '';
601 let tabTitle = '';
602
Philip Pfaffecc7d9382021-09-17 11:55:10603 switch (payload.type) {
Alex Rudenkod55e18c2020-09-23 11:37:06604 case 'contrast': {
Philip Pfaffecc7d9382021-09-17 11:55:10605 const {section, key} = payload;
Alex Rudenkod55e18c2020-09-23 11:37:06606 id = `${section}-${key}`;
Christy Chenb6e28b62021-01-22 08:18:00607 tabTitle = i18nString(UIStrings.contrastIssues);
Alex Rudenkod55e18c2020-09-23 11:37:06608 break;
609 }
610
Mathias Bynens88e8f152020-03-25 14:33:12611 case 'color': {
Philip Pfaffecc7d9382021-09-17 11:55:10612 const {section, color} = payload;
Paul Lewised808ce2019-11-06 14:21:54613 id = `${section}-${color}`;
614 tabTitle = `${color.toUpperCase()} (${section})`;
615 break;
Mathias Bynens88e8f152020-03-25 14:33:12616 }
Paul Lewised808ce2019-11-06 14:21:54617
Mathias Bynens88e8f152020-03-25 14:33:12618 case 'unused-declarations': {
Philip Pfaffecc7d9382021-09-17 11:55:10619 const {declaration} = payload;
Paul Lewised808ce2019-11-06 14:21:54620 id = `${declaration}`;
621 tabTitle = `${declaration}`;
622 break;
Mathias Bynens88e8f152020-03-25 14:33:12623 }
Paul Lewised808ce2019-11-06 14:21:54624
Mathias Bynens88e8f152020-03-25 14:33:12625 case 'media-queries': {
Philip Pfaffecc7d9382021-09-17 11:55:10626 const {text} = payload;
Paul Lewised808ce2019-11-06 14:21:54627 id = `${text}`;
628 tabTitle = `${text}`;
629 break;
Mathias Bynens88e8f152020-03-25 14:33:12630 }
Paul Lewis7d10a732019-11-06 16:11:51631
Mathias Bynens88e8f152020-03-25 14:33:12632 case 'font-info': {
Philip Pfaffecc7d9382021-09-17 11:55:10633 const {name} = payload;
Paul Lewis7d10a732019-11-06 16:11:51634 id = `${name}`;
635 tabTitle = `${name}`;
636 break;
Mathias Bynens88e8f152020-03-25 14:33:12637 }
Paul Lewisde0224c2019-10-22 16:23:15638 }
639
Tim van der Lippe1e4e83c2021-10-12 16:16:58640 let view = this.#viewMap.get(id);
Paul Lewised808ce2019-11-06 14:21:54641 if (!view) {
Tim van der Lippef8c26e22021-11-03 11:46:58642 if (!this.#domModel || !this.#cssModel) {
643 throw new Error('Unable to initialize CSS Overview, missing models');
644 }
Tim van der Lippe1e4e83c2021-10-12 16:16:58645 view = new ElementDetailsView(this.#controller, this.#domModel, this.#cssModel, this.#linkifier);
Tim van der Lippe2d9a95c2022-01-04 15:18:03646 void view.populateNodes(payload.nodes);
Tim van der Lippe1e4e83c2021-10-12 16:16:58647 this.#viewMap.set(id, view);
Paul Lewisde0224c2019-10-22 16:23:15648 }
649
Tim van der Lippe1e4e83c2021-10-12 16:16:58650 this.#elementContainer.appendTab(id, tabTitle, view, true);
Paul Lewised808ce2019-11-06 14:21:54651 }
652
Tim van der Lippe01601572021-12-15 15:36:29653 #fontInfoToFragment(fontInfo: Map<string, Map<string, Map<string, number[]>>>): UI.Fragment.Fragment {
Paul Lewis7d10a732019-11-06 16:11:51654 const fonts = Array.from(fontInfo.entries());
Tim van der Lippe7946bbc2020-02-13 13:58:42655 return UI.Fragment.Fragment.build`
Jan Scheffler830467c2021-02-25 13:44:47656 ${fonts.map(([font, fontMetrics]) => {
657 return UI.Fragment.Fragment.build`<section class="font-family"><h2>${font}</h2> ${
Tim van der Lippe01601572021-12-15 15:36:29658 this.#fontMetricsToFragment(font, fontMetrics)}</section>`;
Paul Lewis7d10a732019-11-06 16:11:51659 })}
Jan Scheffler830467c2021-02-25 13:44:47660 `;
Paul Lewis7d10a732019-11-06 16:11:51661 }
662
Tim van der Lippe01601572021-12-15 15:36:29663 #fontMetricsToFragment(font: string, fontMetrics: Map<string, Map<string, number[]>>): UI.Fragment.Fragment {
Paul Lewis7d10a732019-11-06 16:11:51664 const fontMetricInfo = Array.from(fontMetrics.entries());
665
Tim van der Lippe7946bbc2020-02-13 13:58:42666 return UI.Fragment.Fragment.build`
Jan Scheffler830467c2021-02-25 13:44:47667 <div class="font-metric">
668 ${fontMetricInfo.map(([label, values]) => {
Paul Lewis7d10a732019-11-06 16:11:51669 const sanitizedPath = `${font}/${label}`;
Tim van der Lippe7946bbc2020-02-13 13:58:42670 return UI.Fragment.Fragment.build`
Jan Scheffler830467c2021-02-25 13:44:47671 <div>
672 <h3>${label}</h3>
Tim van der Lippe01601572021-12-15 15:36:29673 ${this.#groupToFragment(values, 'font-info', 'value', sanitizedPath)}
Jan Scheffler830467c2021-02-25 13:44:47674 </div>`;
Paul Lewis7d10a732019-11-06 16:11:51675 })}
Jan Scheffler830467c2021-02-25 13:44:47676 </div>`;
Paul Lewis7d10a732019-11-06 16:11:51677 }
678
Tim van der Lippe01601572021-12-15 15:36:29679 #groupToFragment(
Jan Scheffler830467c2021-02-25 13:44:47680 items: Map<string, (number | UnusedDeclaration | Protocol.CSS.CSSMedia)[]>, type: string, dataLabel: string,
681 path: string = ''): UI.Fragment.Fragment {
Paul Lewised808ce2019-11-06 14:21:54682 // Sort by number of items descending.
683 const values = Array.from(items.entries()).sort((d1, d2) => {
684 const v1Nodes = d1[1];
685 const v2Nodes = d2[1];
686 return v2Nodes.length - v1Nodes.length;
687 });
688
689 const total = values.reduce((prev, curr) => prev + curr[1].length, 0);
690
Tim van der Lippe7946bbc2020-02-13 13:58:42691 return UI.Fragment.Fragment.build`<ul>
Paul Lewised808ce2019-11-06 14:21:54692 ${values.map(([title, nodes]) => {
693 const width = 100 * nodes.length / total;
Peter Marshall645f7bf2021-03-04 09:56:20694 const itemLabel = i18nString(UIStrings.nOccurrences, {n: nodes.length});
Paul Lewised808ce2019-11-06 14:21:54695
Tim van der Lippe7946bbc2020-02-13 13:58:42696 return UI.Fragment.Fragment.build`<li>
Paul Lewised808ce2019-11-06 14:21:54697 <div class="title">${title}</div>
Paul Lewis7d10a732019-11-06 16:11:51698 <button data-type="${type}" data-path="${path}" data-${dataLabel}="${title}">
Peter Marshall40dd7d92021-02-19 11:07:37699 <div class="details">${itemLabel}</div>
Paul Lewised808ce2019-11-06 14:21:54700 <div class="bar-container">
Jan Scheffler830467c2021-02-25 13:44:47701 <div class="bar" style="width: ${width}%;"></div>
Paul Lewised808ce2019-11-06 14:21:54702 </div>
703 </button>
704 </li>`;
705 })}
706 </ul>`;
Paul Lewisde0224c2019-10-22 16:23:15707 }
708
Tim van der Lippe01601572021-12-15 15:36:29709 #contrastIssuesToFragment(issues: Map<string, ContrastIssue[]>): UI.Fragment.Fragment {
Alex Rudenkod55e18c2020-09-23 11:37:06710 return UI.Fragment.Fragment.build`
Jan Scheffler830467c2021-02-25 13:44:47711 <h2>${i18nString(UIStrings.contrastIssuesS, {
712 PH1: issues.size,
Christy Chenb6e28b62021-01-22 08:18:00713 })}</h2>
Jan Scheffler830467c2021-02-25 13:44:47714 <ul>
Tim van der Lippe01601572021-12-15 15:36:29715 ${[...issues.entries()].map(([key, value]) => this.#contrastIssueToFragment(key, value))}
Jan Scheffler830467c2021-02-25 13:44:47716 </ul>
717 `;
Alex Rudenkod55e18c2020-09-23 11:37:06718 }
719
Tim van der Lippe01601572021-12-15 15:36:29720 #contrastIssueToFragment(key: string, issues: ContrastIssue[]): UI.Fragment.Fragment {
Alex Rudenkod55e18c2020-09-23 11:37:06721 console.assert(issues.length > 0);
722
Jan Scheffler830467c2021-02-25 13:44:47723 let minContrastIssue: ContrastIssue = issues[0];
Alex Rudenkod55e18c2020-09-23 11:37:06724 for (const issue of issues) {
Alex Rudenko69c377e2020-11-27 07:40:35725 // APCA contrast can be a negative value that is to be displayed. But the
726 // absolute value is used to compare against the threshold. Therefore, the min
727 // absolute value is the worst contrast.
728 if (Math.abs(issue.contrastRatio) < Math.abs(minContrastIssue.contrastRatio)) {
Alex Rudenkod55e18c2020-09-23 11:37:06729 minContrastIssue = issue;
730 }
731 }
732
Jan Scheffler830467c2021-02-25 13:44:47733 const color = (minContrastIssue.textColor.asString(Common.Color.Format.HEXA) as string);
734 const backgroundColor = (minContrastIssue.backgroundColor.asString(Common.Color.Format.HEXA) as string);
Alex Rudenkod55e18c2020-09-23 11:37:06735
Alex Rudenko69c377e2020-11-27 07:40:35736 const showAPCA = Root.Runtime.experiments.isEnabled('APCA');
737
Alex Rudenkod55e18c2020-09-23 11:37:06738 const blockFragment = UI.Fragment.Fragment.build`<li>
739 <button
Christy Chenb6e28b62021-01-22 08:18:00740 title="${i18nString(UIStrings.textColorSOverSBackgroundResults, {
741 PH1: color,
742 PH2: backgroundColor,
Jan Scheffler830467c2021-02-25 13:44:47743 PH3: issues.length,
Christy Chenb6e28b62021-01-22 08:18:00744 })}"
Alex Rudenkod55e18c2020-09-23 11:37:06745 data-type="contrast" data-key="${key}" data-section="contrast" class="block" $="color">
746 Text
747 </button>
748 <div class="block-title">
Christy Chenb6e28b62021-01-22 08:18:00749 <div class="contrast-warning hidden" $="aa"><span class="threshold-label">${
750 i18nString(UIStrings.aa)}</span></div>
751 <div class="contrast-warning hidden" $="aaa"><span class="threshold-label">${
752 i18nString(UIStrings.aaa)}</span></div>
753 <div class="contrast-warning hidden" $="apca"><span class="threshold-label">${
754 i18nString(UIStrings.apca)}</span></div>
Alex Rudenkod55e18c2020-09-23 11:37:06755 </div>
756 </li>`;
757
Alex Rudenko69c377e2020-11-27 07:40:35758 if (showAPCA) {
Jan Scheffler830467c2021-02-25 13:44:47759 const apca = (blockFragment.$('apca') as HTMLElement);
Alex Rudenko69c377e2020-11-27 07:40:35760 if (minContrastIssue.thresholdsViolated.apca) {
761 apca.appendChild(UI.Icon.Icon.create('smallicon-no'));
762 } else {
763 apca.appendChild(UI.Icon.Icon.create('smallicon-checkmark-square'));
764 }
765 apca.classList.remove('hidden');
Alex Rudenkod55e18c2020-09-23 11:37:06766 } else {
Jan Scheffler830467c2021-02-25 13:44:47767 const aa = (blockFragment.$('aa') as HTMLElement);
Alex Rudenko69c377e2020-11-27 07:40:35768 if (minContrastIssue.thresholdsViolated.aa) {
769 aa.appendChild(UI.Icon.Icon.create('smallicon-no'));
770 } else {
771 aa.appendChild(UI.Icon.Icon.create('smallicon-checkmark-square'));
772 }
Jan Scheffler830467c2021-02-25 13:44:47773 const aaa = (blockFragment.$('aaa') as HTMLElement);
Alex Rudenko69c377e2020-11-27 07:40:35774 if (minContrastIssue.thresholdsViolated.aaa) {
775 aaa.appendChild(UI.Icon.Icon.create('smallicon-no'));
776 } else {
777 aaa.appendChild(UI.Icon.Icon.create('smallicon-checkmark-square'));
778 }
779 aa.classList.remove('hidden');
780 aaa.classList.remove('hidden');
Alex Rudenkod55e18c2020-09-23 11:37:06781 }
782
Jan Scheffler830467c2021-02-25 13:44:47783 const block = (blockFragment.$('color') as HTMLElement);
Alex Rudenkod55e18c2020-09-23 11:37:06784 block.style.backgroundColor = backgroundColor;
785 block.style.color = color;
Alex Rudenkod3ba0ee2020-09-25 14:01:21786 block.style.border = getBorderString(minContrastIssue.backgroundColor);
Alex Rudenkod55e18c2020-09-23 11:37:06787
788 return blockFragment;
789 }
790
Tim van der Lippe01601572021-12-15 15:36:29791 #colorsToFragment(section: string, color: string): UI.Fragment.Fragment|undefined {
Tim van der Lippe7946bbc2020-02-13 13:58:42792 const blockFragment = UI.Fragment.Fragment.build`<li>
Paul Lewised808ce2019-11-06 14:21:54793 <button data-type="color" data-color="${color}" data-section="${section}" class="block" $="color"></button>
Jacky Hu22de4012022-04-13 10:06:38794 <div class="block-title color-text">${color}</div>
Paul Lewis8cddf9932019-09-27 16:40:07795 </li>`;
796
Jan Scheffler830467c2021-02-25 13:44:47797 const block = (blockFragment.$('color') as HTMLElement);
Paul Lewisde0224c2019-10-22 16:23:15798 block.style.backgroundColor = color;
Paul Lewis8cddf9932019-09-27 16:40:07799
Tim van der Lippe7946bbc2020-02-13 13:58:42800 const borderColor = Common.Color.Color.parse(color);
Andres Olivares3dcefe42020-09-14 16:15:42801 if (!borderColor) {
802 return;
803 }
Alex Rudenkod3ba0ee2020-09-25 14:01:21804 block.style.border = getBorderString(borderColor);
Paul Lewis8cddf9932019-09-27 16:40:07805
806 return blockFragment;
807 }
808
Tim van der Lippe01601572021-12-15 15:36:29809 #sortColorsByLuminance(srcColors: Map<string, Set<number>>): string[] {
Paul Lewisde0224c2019-10-22 16:23:15810 return Array.from(srcColors.keys()).sort((colA, colB) => {
Tim van der Lippe7946bbc2020-02-13 13:58:42811 const colorA = Common.Color.Color.parse(colA);
812 const colorB = Common.Color.Color.parse(colB);
Andres Olivares3dcefe42020-09-14 16:15:42813 if (!colorA || !colorB) {
814 return 0;
815 }
Alex Rudenko91403e72020-06-04 07:10:49816 return Common.ColorUtils.luminance(colorB.rgba()) - Common.ColorUtils.luminance(colorA.rgba());
Paul Lewis8db3fa82019-10-10 11:55:53817 });
Paul Lewis8cddf9932019-09-27 16:40:07818 }
819
Jan Scheffler830467c2021-02-25 13:44:47820 setOverviewData(data: OverviewData): void {
Tim van der Lippe2d9a95c2022-01-04 15:18:03821 void this.#render(data);
Paul Lewis8cddf9932019-09-27 16:40:07822 }
Jan Scheffler830467c2021-02-25 13:44:47823
824 // eslint-disable-next-line @typescript-eslint/naming-convention
Sigurd Schneidere18ce8e2021-07-06 08:57:07825 static readonly pushedNodes = new Set<Protocol.DOM.BackendNodeId>();
Paul Lewis4da3b302019-11-21 14:23:47826}
Kateryna Prokopenko4a802532021-09-16 08:57:06827export class DetailsView extends Common.ObjectWrapper.eventMixin<EventTypes, typeof UI.Widget.VBox>(UI.Widget.VBox) {
Tim van der Lippe1e4e83c2021-10-12 16:16:58828 #tabbedPane: UI.TabbedPane.TabbedPane;
Paul Lewised808ce2019-11-06 14:21:54829 constructor() {
830 super();
Paul Lewisebc47192019-10-09 15:06:41831
Tim van der Lippe1e4e83c2021-10-12 16:16:58832 this.#tabbedPane = new UI.TabbedPane.TabbedPane();
833 this.#tabbedPane.show(this.element);
834 this.#tabbedPane.addEventListener(UI.TabbedPane.Events.TabClosed, () => {
835 this.dispatchEventToListeners(Events.TabClosed, this.#tabbedPane.tabIds().length);
Paul Lewised808ce2019-11-06 14:21:54836 });
837 }
838
Jan Scheffler830467c2021-02-25 13:44:47839 appendTab(id: string, tabTitle: string, view: UI.Widget.Widget, isCloseable?: boolean): void {
Tim van der Lippe1e4e83c2021-10-12 16:16:58840 if (!this.#tabbedPane.hasTab(id)) {
841 this.#tabbedPane.appendTab(id, tabTitle, view, undefined, undefined, isCloseable);
Paul Lewised808ce2019-11-06 14:21:54842 }
843
Tim van der Lippe1e4e83c2021-10-12 16:16:58844 this.#tabbedPane.selectTab(id);
Paul Lewised808ce2019-11-06 14:21:54845 }
846
Jan Scheffler830467c2021-02-25 13:44:47847 closeTabs(): void {
Tim van der Lippe1e4e83c2021-10-12 16:16:58848 this.#tabbedPane.closeTabs(this.#tabbedPane.tabIds());
Paul Lewised808ce2019-11-06 14:21:54849 }
Paul Lewis4da3b302019-11-21 14:23:47850}
Paul Lewised808ce2019-11-06 14:21:54851
Kateryna Prokopenko4a802532021-09-16 08:57:06852export const enum Events {
853 TabClosed = 'TabClosed',
854}
855
856export type EventTypes = {
857 [Events.TabClosed]: number,
858};
859
Tim van der Lippe7946bbc2020-02-13 13:58:42860export class ElementDetailsView extends UI.Widget.Widget {
Tim van der Lippe1e4e83c2021-10-12 16:16:58861 readonly #controller: OverviewController;
862 #domModel: SDK.DOMModel.DOMModel;
863 readonly #cssModel: SDK.CSSModel.CSSModel;
864 readonly #linkifier: Components.Linkifier.Linkifier;
865 readonly #elementGridColumns: DataGrid.DataGrid.ColumnDescriptor[];
866 #elementGrid: DataGrid.SortableDataGrid.SortableDataGrid<unknown>;
Jan Scheffler830467c2021-02-25 13:44:47867
868 constructor(
869 controller: OverviewController, domModel: SDK.DOMModel.DOMModel, cssModel: SDK.CSSModel.CSSModel,
870 linkifier: Components.Linkifier.Linkifier) {
Paul Lewised808ce2019-11-06 14:21:54871 super();
872
Tim van der Lippe1e4e83c2021-10-12 16:16:58873 this.#controller = controller;
874 this.#domModel = domModel;
875 this.#cssModel = cssModel;
876 this.#linkifier = linkifier;
Paul Lewised808ce2019-11-06 14:21:54877
Tim van der Lippe1e4e83c2021-10-12 16:16:58878 this.#elementGridColumns = [
Andres Olivares3dcefe42020-09-14 16:15:42879 {
880 id: 'nodeId',
Christy Chenb6e28b62021-01-22 08:18:00881 title: i18nString(UIStrings.element),
Andres Olivares3dcefe42020-09-14 16:15:42882 sortable: true,
883 weight: 50,
884 titleDOMFragment: undefined,
885 sort: undefined,
886 align: undefined,
Sigurd Schneider2533de12020-09-24 10:28:44887 width: undefined,
Andres Olivares3dcefe42020-09-14 16:15:42888 fixedWidth: undefined,
889 editable: undefined,
890 nonSelectable: undefined,
891 longText: undefined,
892 disclosure: undefined,
893 allowInSortByEvenWhenHidden: undefined,
894 dataType: undefined,
895 defaultWeight: undefined,
896 },
897 {
898 id: 'declaration',
Christy Chenb6e28b62021-01-22 08:18:00899 title: i18nString(UIStrings.declaration),
Andres Olivares3dcefe42020-09-14 16:15:42900 sortable: true,
901 weight: 50,
902 titleDOMFragment: undefined,
903 sort: undefined,
904 align: undefined,
Sigurd Schneider2533de12020-09-24 10:28:44905 width: undefined,
Andres Olivares3dcefe42020-09-14 16:15:42906 fixedWidth: undefined,
907 editable: undefined,
908 nonSelectable: undefined,
909 longText: undefined,
910 disclosure: undefined,
911 allowInSortByEvenWhenHidden: undefined,
912 dataType: undefined,
913 defaultWeight: undefined,
914 },
915 {
916 id: 'sourceURL',
Christy Chenb6e28b62021-01-22 08:18:00917 title: i18nString(UIStrings.source),
Andres Olivares3dcefe42020-09-14 16:15:42918 sortable: false,
919 weight: 100,
920 titleDOMFragment: undefined,
921 sort: undefined,
922 align: undefined,
Sigurd Schneider2533de12020-09-24 10:28:44923 width: undefined,
Andres Olivares3dcefe42020-09-14 16:15:42924 fixedWidth: undefined,
925 editable: undefined,
926 nonSelectable: undefined,
927 longText: undefined,
928 disclosure: undefined,
929 allowInSortByEvenWhenHidden: undefined,
930 dataType: undefined,
931 defaultWeight: undefined,
932 },
Alex Rudenkod3ba0ee2020-09-25 14:01:21933 {
934 id: 'contrastRatio',
Christy Chenb6e28b62021-01-22 08:18:00935 title: i18nString(UIStrings.contrastRatio),
Alex Rudenkod3ba0ee2020-09-25 14:01:21936 sortable: true,
937 weight: 25,
938 titleDOMFragment: undefined,
939 sort: undefined,
940 align: undefined,
Alex Rudenkoaf4d9902020-11-25 09:18:07941 width: '150px',
942 fixedWidth: true,
Alex Rudenkod3ba0ee2020-09-25 14:01:21943 editable: undefined,
944 nonSelectable: undefined,
945 longText: undefined,
946 disclosure: undefined,
947 allowInSortByEvenWhenHidden: undefined,
948 dataType: undefined,
949 defaultWeight: undefined,
950 },
Paul Lewised808ce2019-11-06 14:21:54951 ];
952
Tim van der Lippe1e4e83c2021-10-12 16:16:58953 this.#elementGrid = new DataGrid.SortableDataGrid.SortableDataGrid({
Christy Chenb6e28b62021-01-22 08:18:00954 displayName: i18nString(UIStrings.cssOverviewElements),
Tim van der Lippe1e4e83c2021-10-12 16:16:58955 columns: this.#elementGridColumns,
Andres Olivares3dcefe42020-09-14 16:15:42956 editCallback: undefined,
957 deleteCallback: undefined,
Jan Scheffler830467c2021-02-25 13:44:47958 refreshCallback: undefined,
Andres Olivares3dcefe42020-09-14 16:15:42959 });
Tim van der Lippe1e4e83c2021-10-12 16:16:58960 this.#elementGrid.element.classList.add('element-grid');
Tim van der Lippe01601572021-12-15 15:36:29961 this.#elementGrid.element.addEventListener('mouseover', this.#onMouseOver.bind(this));
Tim van der Lippe1e4e83c2021-10-12 16:16:58962 this.#elementGrid.setStriped(true);
Tim van der Lippe01601572021-12-15 15:36:29963 this.#elementGrid.addEventListener(
964 DataGrid.DataGrid.Events.SortingChanged, this.#sortMediaQueryDataGrid.bind(this));
Paul Lewised808ce2019-11-06 14:21:54965
Tim van der Lippe65a4037e2021-11-03 11:59:57966 this.#elementGrid.asWidget().show(this.element);
Paul Lewised808ce2019-11-06 14:21:54967 }
968
Tim van der Lippe01601572021-12-15 15:36:29969 #sortMediaQueryDataGrid(): void {
Tim van der Lippe1e4e83c2021-10-12 16:16:58970 const sortColumnId = this.#elementGrid.sortColumnId();
Paul Lewised808ce2019-11-06 14:21:54971 if (!sortColumnId) {
972 return;
973 }
974
Tim van der Lippe7946bbc2020-02-13 13:58:42975 const comparator = DataGrid.SortableDataGrid.SortableDataGrid.StringComparator.bind(null, sortColumnId);
Tim van der Lippe1e4e83c2021-10-12 16:16:58976 this.#elementGrid.sortNodes(comparator, !this.#elementGrid.isSortOrderAscending());
Paul Lewised808ce2019-11-06 14:21:54977 }
978
Tim van der Lippe01601572021-12-15 15:36:29979 #onMouseOver(evt: Event): void {
Paul Lewised808ce2019-11-06 14:21:54980 // Traverse the event path on the grid to find the nearest element with a backend node ID attached. Use
981 // that for the highlighting.
Jan Scheffler830467c2021-02-25 13:44:47982 const node = (evt.composedPath() as HTMLElement[]).find(el => el.dataset && el.dataset.backendNodeId);
Paul Lewised808ce2019-11-06 14:21:54983 if (!node) {
984 return;
985 }
986
987 const backendNodeId = Number(node.dataset.backendNodeId);
Tim van der Lippe1e4e83c2021-10-12 16:16:58988 this.#controller.dispatchEventToListeners(CSSOverViewControllerEvents.RequestNodeHighlight, backendNodeId);
Paul Lewised808ce2019-11-06 14:21:54989 }
990
Philip Pfaffecc7d9382021-09-17 11:55:10991 async populateNodes(data: PopulateNodesEventNodes): Promise<void> {
Tim van der Lippe1e4e83c2021-10-12 16:16:58992 this.#elementGrid.rootNode().removeChildren();
Paul Lewised808ce2019-11-06 14:21:54993
994 if (!data.length) {
995 return;
996 }
997
998 const [firstItem] = data;
Jan Scheffler830467c2021-02-25 13:44:47999 const visibility = new Set<string>();
Philip Pfaffecc7d9382021-09-17 11:55:101000 'nodeId' in firstItem && firstItem.nodeId && visibility.add('nodeId');
1001 'declaration' in firstItem && firstItem.declaration && visibility.add('declaration');
1002 'sourceURL' in firstItem && firstItem.sourceURL && visibility.add('sourceURL');
1003 'contrastRatio' in firstItem && firstItem.contrastRatio && visibility.add('contrastRatio');
Paul Lewised808ce2019-11-06 14:21:541004
Philip Pfaffecc7d9382021-09-17 11:55:101005 let relatedNodesMap: Map<Protocol.DOM.BackendNodeId, SDK.DOMModel.DOMNode|null>|null|undefined;
1006 if ('nodeId' in firstItem && visibility.has('nodeId')) {
Paul Lewised808ce2019-11-06 14:21:541007 // Grab the nodes from the frontend, but only those that have not been
1008 // retrieved already.
Philip Pfaffe8071e532021-09-20 08:27:151009 const nodeIds = (data as {nodeId: Protocol.DOM.BackendNodeId}[]).reduce((prev, curr) => {
1010 const nodeId = curr.nodeId;
Philip Pfaffecc7d9382021-09-17 11:55:101011 if (CSSOverviewCompletedView.pushedNodes.has(nodeId)) {
Paul Lewised808ce2019-11-06 14:21:541012 return prev;
1013 }
Philip Pfaffecc7d9382021-09-17 11:55:101014 CSSOverviewCompletedView.pushedNodes.add(nodeId);
1015 return prev.add(nodeId);
1016 }, new Set<Protocol.DOM.BackendNodeId>());
Tim van der Lippe1e4e83c2021-10-12 16:16:581017 relatedNodesMap = await this.#domModel.pushNodesByBackendIdsToFrontend(nodeIds);
Paul Lewised808ce2019-11-06 14:21:541018 }
1019
1020 for (const item of data) {
Philip Pfaffecc7d9382021-09-17 11:55:101021 let frontendNode;
1022 if ('nodeId' in item && visibility.has('nodeId')) {
Andres Olivares3dcefe42020-09-14 16:15:421023 if (!relatedNodesMap) {
1024 continue;
1025 }
Philip Pfaffe8071e532021-09-20 08:27:151026 frontendNode = relatedNodesMap.get(item.nodeId);
Paul Lewised808ce2019-11-06 14:21:541027 if (!frontendNode) {
1028 continue;
1029 }
Paul Lewised808ce2019-11-06 14:21:541030 }
1031
Tim van der Lippe1e4e83c2021-10-12 16:16:581032 const node = new ElementNode(item, frontendNode, this.#linkifier, this.#cssModel);
Paul Lewised808ce2019-11-06 14:21:541033 node.selectable = false;
Tim van der Lippe1e4e83c2021-10-12 16:16:581034 this.#elementGrid.insertChild(node);
Paul Lewised808ce2019-11-06 14:21:541035 }
1036
Tim van der Lippe1e4e83c2021-10-12 16:16:581037 this.#elementGrid.setColumnsVisiblity(visibility);
1038 this.#elementGrid.renderInline();
1039 this.#elementGrid.wasShown();
Paul Lewised808ce2019-11-06 14:21:541040 }
Paul Lewis4da3b302019-11-21 14:23:471041}
Paul Lewised808ce2019-11-06 14:21:541042
Jan Scheffler830467c2021-02-25 13:44:471043export class ElementNode extends DataGrid.SortableDataGrid.SortableDataGridNode<ElementNode> {
Tim van der Lippe1e4e83c2021-10-12 16:16:581044 readonly #linkifier: Components.Linkifier.Linkifier;
1045 readonly #cssModel: SDK.CSSModel.CSSModel;
1046 readonly #frontendNode: SDK.DOMModel.DOMNode|null|undefined;
Jan Scheffler830467c2021-02-25 13:44:471047
1048 constructor(
Philip Pfaffecc7d9382021-09-17 11:55:101049 data: PopulateNodesEventNodeTypes, frontendNode: SDK.DOMModel.DOMNode|null|undefined,
Jan Scheffler830467c2021-02-25 13:44:471050 linkifier: Components.Linkifier.Linkifier, cssModel: SDK.CSSModel.CSSModel) {
Philip Pfaffecc7d9382021-09-17 11:55:101051 super(data);
Paul Lewised808ce2019-11-06 14:21:541052
Tim van der Lippe1e4e83c2021-10-12 16:16:581053 this.#frontendNode = frontendNode;
1054 this.#linkifier = linkifier;
1055 this.#cssModel = cssModel;
Paul Lewisebc47192019-10-09 15:06:411056 }
1057
Jan Scheffler830467c2021-02-25 13:44:471058 createCell(columnId: string): HTMLElement {
Paul Lewised808ce2019-11-06 14:21:541059 // Nodes.
Tim van der Lippe1e4e83c2021-10-12 16:16:581060 const frontendNode = this.#frontendNode;
Paul Lewised808ce2019-11-06 14:21:541061 if (columnId === 'nodeId') {
Paul Lewisebc47192019-10-09 15:06:411062 const cell = this.createTD(columnId);
Paul Lewised808ce2019-11-06 14:21:541063 cell.textContent = '...';
Paul Lewisebc47192019-10-09 15:06:411064
Philip Pfaffecc7d9382021-09-17 11:55:101065 if (!frontendNode) {
1066 throw new Error('Node entry is missing a related frontend node.');
1067 }
1068
Tim van der Lippe2d9a95c2022-01-04 15:18:031069 void Common.Linkifier.Linkifier.linkify(frontendNode).then(link => {
Paul Lewised808ce2019-11-06 14:21:541070 cell.textContent = '';
Philip Pfaffecc7d9382021-09-17 11:55:101071 (link as HTMLElement).dataset.backendNodeId = frontendNode.backendNodeId().toString();
Paul Lewisebc47192019-10-09 15:06:411072 cell.appendChild(link);
Alex Rudenkob7419b72020-09-29 11:10:081073 const button = document.createElement('button');
1074 button.classList.add('show-element');
Christy Chenb6e28b62021-01-22 08:18:001075 UI.Tooltip.Tooltip.install(button, i18nString(UIStrings.showElement));
Alex Rudenkob7419b72020-09-29 11:10:081076 button.tabIndex = 0;
Jan Scheffler830467c2021-02-25 13:44:471077 button.onclick = (): void => this.data.node.scrollIntoView();
Alex Rudenkob7419b72020-09-29 11:10:081078 cell.appendChild(button);
Paul Lewised808ce2019-11-06 14:21:541079 });
1080 return cell;
1081 }
1082
1083 // Links to CSS.
1084 if (columnId === 'sourceURL') {
1085 const cell = this.createTD(columnId);
1086
1087 if (this.data.range) {
Tim van der Lippe01601572021-12-15 15:36:291088 const link = this.#linkifyRuleLocation(
Tim van der Lippe1e4e83c2021-10-12 16:16:581089 this.#cssModel, this.#linkifier, this.data.styleSheetId,
Tim van der Lippe7946bbc2020-02-13 13:58:421090 TextUtils.TextRange.TextRange.fromObject(this.data.range));
Paul Lewised808ce2019-11-06 14:21:541091
Andres Olivares3dcefe42020-09-14 16:15:421092 if (!link || link.textContent === '') {
Mathias Bynens23ee1aa2020-03-02 12:06:381093 cell.textContent = '(unable to link)';
Andres Olivares3dcefe42020-09-14 16:15:421094 } else {
1095 cell.appendChild(link);
Paul Lewised808ce2019-11-06 14:21:541096 }
Paul Lewisebc47192019-10-09 15:06:411097 } else {
Paul Lewised808ce2019-11-06 14:21:541098 cell.textContent = '(unable to link to inlined styles)';
Paul Lewisebc47192019-10-09 15:06:411099 }
1100 return cell;
1101 }
1102
Alex Rudenkod3ba0ee2020-09-25 14:01:211103 if (columnId === 'contrastRatio') {
1104 const cell = this.createTD(columnId);
Alex Rudenko69c377e2020-11-27 07:40:351105 const showAPCA = Root.Runtime.experiments.isEnabled('APCA');
Alex Rudenkoa9f40de2021-01-15 13:28:371106 const contrastRatio = Platform.NumberUtilities.floor(this.data.contrastRatio, 2);
1107 const contrastRatioString = showAPCA ? contrastRatio + '%' : contrastRatio;
Jan Scheffler83e4e262021-03-17 14:56:511108 const border = getBorderString(this.data.backgroundColor);
1109 const color = this.data.textColor.asString();
1110 const backgroundColor = this.data.backgroundColor.asString();
Alex Rudenkod3ba0ee2020-09-25 14:01:211111 const contrastFragment = UI.Fragment.Fragment.build`
1112 <div class="contrast-container-in-grid" $="container">
Jan Scheffler830467c2021-02-25 13:44:471113 <span class="contrast-preview" style="border: ${border};
Jan Scheffler83e4e262021-03-17 14:56:511114 color: ${color};
1115 background-color: ${backgroundColor};">Aa</span>
Alex Rudenko69c377e2020-11-27 07:40:351116 <span>${contrastRatioString}</span>
Alex Rudenkod3ba0ee2020-09-25 14:01:211117 </div>
1118 `;
1119 const container = contrastFragment.$('container');
Alex Rudenko69c377e2020-11-27 07:40:351120 if (showAPCA) {
Christy Chenb6e28b62021-01-22 08:18:001121 container.append(UI.Fragment.Fragment.build`<span>${i18nString(UIStrings.apca)}</span>`.element());
Alex Rudenko69c377e2020-11-27 07:40:351122 if (this.data.thresholdsViolated.apca) {
1123 container.appendChild(UI.Icon.Icon.create('smallicon-no'));
1124 } else {
1125 container.appendChild(UI.Icon.Icon.create('smallicon-checkmark-square'));
1126 }
Alex Rudenkod3ba0ee2020-09-25 14:01:211127 } else {
Christy Chenb6e28b62021-01-22 08:18:001128 container.append(UI.Fragment.Fragment.build`<span>${i18nString(UIStrings.aa)}</span>`.element());
Alex Rudenko69c377e2020-11-27 07:40:351129 if (this.data.thresholdsViolated.aa) {
1130 container.appendChild(UI.Icon.Icon.create('smallicon-no'));
1131 } else {
1132 container.appendChild(UI.Icon.Icon.create('smallicon-checkmark-square'));
1133 }
Christy Chenb6e28b62021-01-22 08:18:001134 container.append(UI.Fragment.Fragment.build`<span>${i18nString(UIStrings.aaa)}</span>`.element());
Alex Rudenko69c377e2020-11-27 07:40:351135 if (this.data.thresholdsViolated.aaa) {
1136 container.appendChild(UI.Icon.Icon.create('smallicon-no'));
1137 } else {
1138 container.appendChild(UI.Icon.Icon.create('smallicon-checkmark-square'));
1139 }
Alex Rudenkod3ba0ee2020-09-25 14:01:211140 }
1141 cell.appendChild(contrastFragment.element());
1142 return cell;
1143 }
1144
Paul Lewisebc47192019-10-09 15:06:411145 return super.createCell(columnId);
1146 }
1147
Tim van der Lippe01601572021-12-15 15:36:291148 #linkifyRuleLocation(
Sigurd Schneiderb5657ef2021-08-09 13:06:331149 cssModel: SDK.CSSModel.CSSModel, linkifier: Components.Linkifier.Linkifier,
1150 styleSheetId: Protocol.CSS.StyleSheetId, ruleLocation: TextUtils.TextRange.TextRange): Element|undefined {
Paul Lewisebc47192019-10-09 15:06:411151 const styleSheetHeader = cssModel.styleSheetHeaderForId(styleSheetId);
Andres Olivares3dcefe42020-09-14 16:15:421152 if (!styleSheetHeader) {
1153 return;
1154 }
Paul Lewisebc47192019-10-09 15:06:411155 const lineNumber = styleSheetHeader.lineNumberInSource(ruleLocation.startLine);
1156 const columnNumber = styleSheetHeader.columnNumberInSource(ruleLocation.startLine, ruleLocation.startColumn);
Tim van der Lippe7946bbc2020-02-13 13:58:421157 const matchingSelectorLocation = new SDK.CSSModel.CSSLocation(styleSheetHeader, lineNumber, columnNumber);
Paul Lewisebc47192019-10-09 15:06:411158 return linkifier.linkifyCSSLocation(matchingSelectorLocation);
1159 }
Paul Lewis4da3b302019-11-21 14:23:471160}