blob: 8efe73e56fa4c0dcfba6074e1957369c4791ce35 [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 Lippe7946bbc2020-02-13 13:58:425import * as Common from '../common/common.js';
6import * as Components from '../components/components.js';
7import * as DataGrid from '../data_grid/data_grid.js';
8import * as SDK from '../sdk/sdk.js';
9import * as TextUtils from '../text_utils/text_utils.js';
10import * as UI from '../ui/ui.js';
11
Andres Olivares3dcefe42020-09-14 16:15:4212import {Events, OverviewController} from './CSSOverviewController.js'; // eslint-disable-line no-unused-vars
Tim van der Lippe9d0cb5f2020-01-09 14:10:3813import {CSSOverviewSidebarPanel, SidebarEvents} from './CSSOverviewSidebarPanel.js';
Andres Olivares3dcefe42020-09-14 16:15:4214import {UnusedDeclaration} from './CSSOverviewUnusedDeclarations.js'; // eslint-disable-line no-unused-vars
15
16/**
17 * @typedef {!Map<string,!Set<number>>}
18 */
19// @ts-ignore typedef
20export let NodeStyleStats;
21
22/**
23 * @typedef {{
Alex Rudenkod55e18c2020-09-23 11:37:0624 * nodeId: number,
25 * contrastRatio: number,
26 * textColor: Common.Color.Color,
27 * backgroundColor: Common.Color.Color,
28 * thresholdsViolated: !{aa: boolean, aaa:boolean},
29 * }}
30 */
31// @ts-ignore typedef
32export let ContrastIssue;
33
34/**
35 * @typedef {{
Andres Olivares3dcefe42020-09-14 16:15:4236 * backgroundColors: NodeStyleStats,
37 * textColors: NodeStyleStats,
Alex Rudenkod55e18c2020-09-23 11:37:0638 * textColorContrastIssues: !Map<string, !Array<!ContrastIssue>>,
Andres Olivares3dcefe42020-09-14 16:15:4239 * fillColors: NodeStyleStats,
40 * borderColors: NodeStyleStats,
41 * globalStyleStats: !{
42 * styleRules: number,
43 * inlineStyles: number,
44 * externalSheets: number,
45 * stats: !{
46 * type: number,
47 * class: number,
48 * id: number,
49 * universal: number,
50 * attribute: number,
51 * nonSimple: number
52 * }
53 * },
54 * fontInfo: FontInfo,
55 * elementCount: number,
56 * mediaQueries: !Map<string, !Array<!Protocol.CSS.CSSMedia>>,
57 * unusedDeclarations: !Map<string, !Array<!UnusedDeclaration>>,
58 * }}
59 */
60// @ts-ignore typedef
61export let OverviewData;
62
63/**
64 * @typedef {!Map<string, !Map<string, !Map<string, !Array<number>>>>}
65 */
66// @ts-ignore typedef
67export let FontInfo;
Tim van der Lippe9d0cb5f2020-01-09 14:10:3868
Paul Lewis8cddf9932019-09-27 16:40:0769/**
Alex Rudenkod3ba0ee2020-09-25 14:01:2170 * @param {!Common.Color.Color} color
71 */
72function getBorderString(color) {
73 let [h, s, l] = color.hsla();
74 h = Math.round(h * 360);
75 s = Math.round(s * 100);
76 l = Math.round(l * 100);
77
78 // Reduce the lightness of the border to make sure that there's always a visible outline.
79 l = Math.max(0, l - 15);
80
81 return `1px solid hsl(${h}deg ${s}% ${l}%)`;
82}
83
84/**
Paul Lewis8cddf9932019-09-27 16:40:0785 * @unrestricted
86 */
Tim van der Lippe7946bbc2020-02-13 13:58:4287export class CSSOverviewCompletedView extends UI.Panel.PanelWithSidebar {
Andres Olivares3dcefe42020-09-14 16:15:4288 /**
89 * @param {!OverviewController} controller
90 * @param {!SDK.SDKModel.Target} target
91 */
Paul Lewisebc47192019-10-09 15:06:4192 constructor(controller, target) {
Paul Lewis8cddf9932019-09-27 16:40:0793 super('css_overview_completed_view');
Jack Franklin71519f82020-11-03 12:08:5994 this.registerRequiredCSS('css_overview/cssOverviewCompletedView.css', {enableLegacyPatching: true});
Paul Lewis8cddf9932019-09-27 16:40:0795
96 this._controller = controller;
97 this._formatter = new Intl.NumberFormat('en-US');
Paul Lewisde0224c2019-10-22 16:23:1598
Tim van der Lippe7946bbc2020-02-13 13:58:4299 this._mainContainer = new UI.SplitWidget.SplitWidget(true, true);
100 this._resultsContainer = new UI.Widget.VBox();
Paul Lewis4da3b302019-11-21 14:23:47101 this._elementContainer = new DetailsView();
Paul Lewised808ce2019-11-06 14:21:54102
103 // If closing the last tab, collapse the sidebar.
104 this._elementContainer.addEventListener(UI.TabbedPane.Events.TabClosed, evt => {
105 if (evt.data === 0) {
106 this._mainContainer.setSidebarMinimized(true);
107 }
108 });
Paul Lewisde0224c2019-10-22 16:23:15109
110 // Dupe the styles into the main container because of the shadow root will prevent outer styles.
Jack Franklin71519f82020-11-03 12:08:59111 this._mainContainer.registerRequiredCSS('css_overview/cssOverviewCompletedView.css', {enableLegacyPatching: true});
Paul Lewisde0224c2019-10-22 16:23:15112
113 this._mainContainer.setMainWidget(this._resultsContainer);
114 this._mainContainer.setSidebarWidget(this._elementContainer);
115 this._mainContainer.setVertical(false);
116 this._mainContainer.setSecondIsSidebar(true);
117 this._mainContainer.setSidebarMinimized(true);
Paul Lewis8cddf9932019-09-27 16:40:07118
Tim van der Lippe9d0cb5f2020-01-09 14:10:38119 this._sideBar = new CSSOverviewSidebarPanel();
Paul Lewis8cddf9932019-09-27 16:40:07120 this.splitWidget().setSidebarWidget(this._sideBar);
121 this.splitWidget().setMainWidget(this._mainContainer);
122
Sigurd Schneider5a5b7352020-10-05 11:13:05123 const cssModel = target.model(SDK.CSSModel.CSSModel);
124 const domModel = target.model(SDK.DOMModel.DOMModel);
125 if (!cssModel || !domModel) {
126 throw new Error('Target must provide CSS and DOM models');
127 }
128 this._cssModel = cssModel;
129 this._domModel = domModel;
Paul Lewisde0224c2019-10-22 16:23:15130 this._domAgent = target.domAgent();
Tim van der Lippe7946bbc2020-02-13 13:58:42131 this._linkifier = new Components.Linkifier.Linkifier(/* maxLinkLength */ 20, /* useLinkDecorator */ true);
Paul Lewis8aef9012019-10-29 14:15:17132
Paul Lewised808ce2019-11-06 14:21:54133 this._viewMap = new Map();
Paul Lewisebc47192019-10-09 15:06:41134
Paul Lewis8cddf9932019-09-27 16:40:07135 this._sideBar.addItem(ls`Overview summary`, 'summary');
136 this._sideBar.addItem(ls`Colors`, 'colors');
Paul Lewis7d10a732019-11-06 16:11:51137 this._sideBar.addItem(ls`Font info`, 'font-info');
Paul Lewised808ce2019-11-06 14:21:54138 this._sideBar.addItem(ls`Unused declarations`, 'unused-declarations');
Paul Lewisebc47192019-10-09 15:06:41139 this._sideBar.addItem(ls`Media queries`, 'media-queries');
Paul Lewis8cddf9932019-09-27 16:40:07140 this._sideBar.select('summary');
141
Tim van der Lippe9d0cb5f2020-01-09 14:10:38142 this._sideBar.addEventListener(SidebarEvents.ItemSelected, this._sideBarItemSelected, this);
143 this._sideBar.addEventListener(SidebarEvents.Reset, this._sideBarReset, this);
144 this._controller.addEventListener(Events.Reset, this._reset, this);
145 this._controller.addEventListener(Events.PopulateNodes, this._createElementsView, this);
Paul Lewisde0224c2019-10-22 16:23:15146 this._resultsContainer.element.addEventListener('click', this._onClick.bind(this));
147
148 this._data = null;
Paul Lewis8cddf9932019-09-27 16:40:07149 }
150
Paul Lewisebc47192019-10-09 15:06:41151
Paul Lewised808ce2019-11-06 14:21:54152 /**
153 * @override
154 */
155 wasShown() {
156 super.wasShown();
Paul Lewisebc47192019-10-09 15:06:41157
Paul Lewised808ce2019-11-06 14:21:54158 // TODO(paullewis): update the links in the panels in case source has been .
Paul Lewis8aef9012019-10-29 14:15:17159 }
160
Andres Olivares3dcefe42020-09-14 16:15:42161 /**
162 * @param {!Common.EventTarget.EventTargetEvent} event
163 */
Paul Lewis8cddf9932019-09-27 16:40:07164 _sideBarItemSelected(event) {
Andres Olivares3dcefe42020-09-14 16:15:42165 const data = /** @type {string} */ (event.data);
166 const section = /** @type {!UI.Fragment.Fragment}*/ (this._fragment).$(data);
Tim van der Lippe1d6e57a2019-09-30 11:55:34167 if (!section) {
Paul Lewis8cddf9932019-09-27 16:40:07168 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34169 }
Paul Lewis8cddf9932019-09-27 16:40:07170
171 section.scrollIntoView();
172 }
173
174 _sideBarReset() {
Tim van der Lippe9d0cb5f2020-01-09 14:10:38175 this._controller.dispatchEventToListeners(Events.Reset);
Paul Lewis8cddf9932019-09-27 16:40:07176 }
177
178 _reset() {
Paul Lewisde0224c2019-10-22 16:23:15179 this._resultsContainer.element.removeChildren();
Paul Lewisde0224c2019-10-22 16:23:15180 this._mainContainer.setSidebarMinimized(true);
Paul Lewised808ce2019-11-06 14:21:54181 this._elementContainer.closeTabs();
182 this._viewMap = new Map();
Paul Lewis8cddf9932019-09-27 16:40:07183 }
184
Andres Olivares3dcefe42020-09-14 16:15:42185 /**
186 * @param {!Event} evt
187 */
Paul Lewisde0224c2019-10-22 16:23:15188 _onClick(evt) {
Andres Olivares3dcefe42020-09-14 16:15:42189 if (!evt.target) {
190 return;
191 }
192 const target = /** @type {!HTMLElement} */ (evt.target);
193 const dataset = target.dataset;
194
195 const type = dataset.type;
196 if (!type || !this._data) {
Paul Lewis8cddf9932019-09-27 16:40:07197 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34198 }
Paul Lewis8cddf9932019-09-27 16:40:07199
Paul Lewised808ce2019-11-06 14:21:54200 let payload;
201 switch (type) {
Alex Rudenkod55e18c2020-09-23 11:37:06202 case 'contrast': {
203 const section = dataset.section;
204 const key = dataset.key;
205
206 if (!key) {
207 return;
208 }
209
210 // Remap the Set to an object that is the same shape as the unused declarations.
Alex Rudenkod3ba0ee2020-09-25 14:01:21211 const nodes = this._data.textColorContrastIssues.get(key) || [];
Alex Rudenkod55e18c2020-09-23 11:37:06212 payload = {type, key, nodes, section};
213 break;
214 }
Paul Lewised808ce2019-11-06 14:21:54215 case 'color': {
Andres Olivares3dcefe42020-09-14 16:15:42216 const color = dataset.color;
217 const section = dataset.section;
Paul Lewised808ce2019-11-06 14:21:54218 if (!color) {
219 return;
220 }
Paul Lewis12bc63e2019-10-23 10:53:29221
Paul Lewised808ce2019-11-06 14:21:54222 let nodes;
223 switch (section) {
224 case 'text':
225 nodes = this._data.textColors.get(color);
226 break;
Paul Lewis12bc63e2019-10-23 10:53:29227
Paul Lewised808ce2019-11-06 14:21:54228 case 'background':
229 nodes = this._data.backgroundColors.get(color);
230 break;
Paul Lewis12bc63e2019-10-23 10:53:29231
Paul Lewised808ce2019-11-06 14:21:54232 case 'fill':
233 nodes = this._data.fillColors.get(color);
234 break;
Paul Lewis12bc63e2019-10-23 10:53:29235
Paul Lewised808ce2019-11-06 14:21:54236 case 'border':
237 nodes = this._data.borderColors.get(color);
238 break;
239 }
240
241 if (!nodes) {
242 return;
243 }
244
245 // Remap the Set to an object that is the same shape as the unused declarations.
246 nodes = Array.from(nodes).map(nodeId => ({nodeId}));
247 payload = {type, color, nodes, section};
248 break;
249 }
250
251 case 'unused-declarations': {
Andres Olivares3dcefe42020-09-14 16:15:42252 const declaration = dataset.declaration;
253 if (!declaration) {
254 return;
255 }
Paul Lewised808ce2019-11-06 14:21:54256 const nodes = this._data.unusedDeclarations.get(declaration);
257 if (!nodes) {
258 return;
259 }
260
261 payload = {type, declaration, nodes};
262 break;
263 }
264
265 case 'media-queries': {
Andres Olivares3dcefe42020-09-14 16:15:42266 const text = dataset.text;
267 if (!text) {
268 return;
269 }
Paul Lewised808ce2019-11-06 14:21:54270 const nodes = this._data.mediaQueries.get(text);
271 if (!nodes) {
272 return;
273 }
274
275 payload = {type, text, nodes};
276 break;
277 }
278
Paul Lewis7d10a732019-11-06 16:11:51279 case 'font-info': {
Andres Olivares3dcefe42020-09-14 16:15:42280 const value = dataset.value;
281 if (!dataset.path) {
282 return;
283 }
284
285 const [fontFamily, fontMetric] = dataset.path.split('/');
286 if (!value) {
287 return;
288 }
289
290 const fontFamilyInfo = this._data.fontInfo.get(fontFamily);
291 if (!fontFamilyInfo) {
292 return;
293 }
294
295 const fontMetricInfo = fontFamilyInfo.get(fontMetric);
296 if (!fontMetricInfo) {
297 return;
298 }
299
300 const nodesIds = /** @type {!Array<number>} */ (fontMetricInfo.get(value));
Paul Lewis7d10a732019-11-06 16:11:51301 if (!nodesIds) {
302 return;
303 }
304
305 const nodes = nodesIds.map(nodeId => ({nodeId}));
306 const name = `${value} (${fontFamily}, ${fontMetric})`;
307 payload = {type, name, nodes};
308 break;
309 }
310
Paul Lewised808ce2019-11-06 14:21:54311 default:
312 return;
Paul Lewisde0224c2019-10-22 16:23:15313 }
314
315 evt.consume();
Tim van der Lippe9d0cb5f2020-01-09 14:10:38316 this._controller.dispatchEventToListeners(Events.PopulateNodes, payload);
Paul Lewised808ce2019-11-06 14:21:54317 this._mainContainer.setSidebarMinimized(false);
Paul Lewisde0224c2019-10-22 16:23:15318 }
319
Andres Olivares3dcefe42020-09-14 16:15:42320 /**
321 * @param {!Event} evt
322 */
Paul Lewisde0224c2019-10-22 16:23:15323 _onMouseOver(evt) {
324 // Traverse the event path on the grid to find the nearest element with a backend node ID attached. Use
325 // that for the highlighting.
Andres Olivares3dcefe42020-09-14 16:15:42326 const node =
327 (/** @type {!Array<!HTMLElement>} */ (evt.composedPath())).find(el => el.dataset && el.dataset.backendNodeId);
Paul Lewisde0224c2019-10-22 16:23:15328 if (!node) {
329 return;
330 }
331
332 const backendNodeId = Number(node.dataset.backendNodeId);
Tim van der Lippe9d0cb5f2020-01-09 14:10:38333 this._controller.dispatchEventToListeners(Events.RequestNodeHighlight, backendNodeId);
Paul Lewisde0224c2019-10-22 16:23:15334 }
335
Andres Olivares3dcefe42020-09-14 16:15:42336 /**
337 * @param {!OverviewData} data
338 */
Paul Lewis8aef9012019-10-29 14:15:17339 async _render(data) {
Paul Lewisde0224c2019-10-22 16:23:15340 if (!data || !('backgroundColors' in data) || !('textColors' in data)) {
341 return;
342 }
343
344 this._data = data;
Paul Lewis8aef9012019-10-29 14:15:17345 const {
346 elementCount,
347 backgroundColors,
348 textColors,
Alex Rudenkod55e18c2020-09-23 11:37:06349 textColorContrastIssues,
Paul Lewis8aef9012019-10-29 14:15:17350 fillColors,
351 borderColors,
352 globalStyleStats,
353 mediaQueries,
Paul Lewis7d10a732019-11-06 16:11:51354 unusedDeclarations,
355 fontInfo
Paul Lewis8aef9012019-10-29 14:15:17356 } = this._data;
Paul Lewis8cddf9932019-09-27 16:40:07357
358 // Convert rgb values from the computed styles to either undefined or HEX(A) strings.
Paul Lewisde0224c2019-10-22 16:23:15359 const sortedBackgroundColors = this._sortColorsByLuminance(backgroundColors);
360 const sortedTextColors = this._sortColorsByLuminance(textColors);
Paul Lewis12bc63e2019-10-23 10:53:29361 const sortedFillColors = this._sortColorsByLuminance(fillColors);
362 const sortedBorderColors = this._sortColorsByLuminance(borderColors);
Paul Lewis8cddf9932019-09-27 16:40:07363
Tim van der Lippe7946bbc2020-02-13 13:58:42364 this._fragment = UI.Fragment.Fragment.build`
Paul Lewis8cddf9932019-09-27 16:40:07365 <div class="vbox overview-completed-view">
Paul Lewis8aef9012019-10-29 14:15:17366 <div $="summary" class="results-section horizontally-padded summary">
Paul Lewis8cddf9932019-09-27 16:40:07367 <h1>${ls`Overview summary`}</h1>
368
369 <ul>
370 <li>
Paul Lewised808ce2019-11-06 14:21:54371 <div class="label">${ls`Elements`}</div>
Paul Lewis8cddf9932019-09-27 16:40:07372 <div class="value">${this._formatter.format(elementCount)}</div>
373 </li>
374 <li>
375 <div class="label">${ls`External stylesheets`}</div>
376 <div class="value">${this._formatter.format(globalStyleStats.externalSheets)}</div>
377 </li>
378 <li>
379 <div class="label">${ls`Inline style elements`}</div>
380 <div class="value">${this._formatter.format(globalStyleStats.inlineStyles)}</div>
381 </li>
382 <li>
383 <div class="label">${ls`Style rules`}</div>
384 <div class="value">${this._formatter.format(globalStyleStats.styleRules)}</div>
385 </li>
386 <li>
Paul Lewisebc47192019-10-09 15:06:41387 <div class="label">${ls`Media queries`}</div>
Paul Lewised808ce2019-11-06 14:21:54388 <div class="value">${this._formatter.format(mediaQueries.size)}</div>
Paul Lewis8cddf9932019-09-27 16:40:07389 </li>
390 <li>
391 <div class="label">${ls`Type selectors`}</div>
Paul Lewisde0224c2019-10-22 16:23:15392 <div class="value">${this._formatter.format(globalStyleStats.stats.type)}</div>
Paul Lewis8cddf9932019-09-27 16:40:07393 </li>
394 <li>
395 <div class="label">${ls`ID selectors`}</div>
Paul Lewisde0224c2019-10-22 16:23:15396 <div class="value">${this._formatter.format(globalStyleStats.stats.id)}</div>
Paul Lewis8cddf9932019-09-27 16:40:07397 </li>
398 <li>
399 <div class="label">${ls`Class selectors`}</div>
Paul Lewisde0224c2019-10-22 16:23:15400 <div class="value">${this._formatter.format(globalStyleStats.stats.class)}</div>
Paul Lewis8cddf9932019-09-27 16:40:07401 </li>
402 <li>
403 <div class="label">${ls`Universal selectors`}</div>
Paul Lewisde0224c2019-10-22 16:23:15404 <div class="value">${this._formatter.format(globalStyleStats.stats.universal)}</div>
Paul Lewis8cddf9932019-09-27 16:40:07405 </li>
406 <li>
407 <div class="label">${ls`Attribute selectors`}</div>
Paul Lewisde0224c2019-10-22 16:23:15408 <div class="value">${this._formatter.format(globalStyleStats.stats.attribute)}</div>
Paul Lewis8cddf9932019-09-27 16:40:07409 </li>
410 <li>
411 <div class="label">${ls`Non-simple selectors`}</div>
Paul Lewisde0224c2019-10-22 16:23:15412 <div class="value">${this._formatter.format(globalStyleStats.stats.nonSimple)}</div>
Paul Lewis8cddf9932019-09-27 16:40:07413 </li>
414 </ul>
415 </div>
416
Paul Lewis8aef9012019-10-29 14:15:17417 <div $="colors" class="results-section horizontally-padded colors">
Paul Lewis8cddf9932019-09-27 16:40:07418 <h1>${ls`Colors`}</h1>
Paul Lewised808ce2019-11-06 14:21:54419 <h2>${ls`Background colors: ${sortedBackgroundColors.length}`}</h2>
Paul Lewis8cddf9932019-09-27 16:40:07420 <ul>
Paul Lewisde0224c2019-10-22 16:23:15421 ${sortedBackgroundColors.map(this._colorsToFragment.bind(this, 'background'))}
Paul Lewis8cddf9932019-09-27 16:40:07422 </ul>
423
Paul Lewised808ce2019-11-06 14:21:54424 <h2>${ls`Text colors: ${sortedTextColors.length}`}</h2>
Paul Lewis8cddf9932019-09-27 16:40:07425 <ul>
Paul Lewisde0224c2019-10-22 16:23:15426 ${sortedTextColors.map(this._colorsToFragment.bind(this, 'text'))}
Paul Lewis8cddf9932019-09-27 16:40:07427 </ul>
Paul Lewis12bc63e2019-10-23 10:53:29428
Alex Rudenkod55e18c2020-09-23 11:37:06429 ${textColorContrastIssues.size > 0 ? this._contrastIssuesToFragment(textColorContrastIssues) : ''}
430
Paul Lewised808ce2019-11-06 14:21:54431 <h2>${ls`Fill colors: ${sortedFillColors.length}`}</h2>
Paul Lewis12bc63e2019-10-23 10:53:29432 <ul>
433 ${sortedFillColors.map(this._colorsToFragment.bind(this, 'fill'))}
434 </ul>
435
Paul Lewised808ce2019-11-06 14:21:54436 <h2>${ls`Border colors: ${sortedBorderColors.length}`}</h2>
Paul Lewis12bc63e2019-10-23 10:53:29437 <ul>
438 ${sortedBorderColors.map(this._colorsToFragment.bind(this, 'border'))}
439 </ul>
Paul Lewis8cddf9932019-09-27 16:40:07440 </div>
Paul Lewisebc47192019-10-09 15:06:41441
Paul Lewis66a15fc2019-11-07 11:17:11442 <div $="font-info" class="results-section font-info">
Paul Lewis7d10a732019-11-06 16:11:51443 <h1>${ls`Font info`}</h1>
444 ${
445 fontInfo.size > 0 ? this._fontInfoToFragment(fontInfo) :
Tim van der Lippe7946bbc2020-02-13 13:58:42446 UI.Fragment.Fragment.build`<div>${ls`There are no fonts.`}</div>`}
Paul Lewis7d10a732019-11-06 16:11:51447 </div>
448
Paul Lewised808ce2019-11-06 14:21:54449 <div $="unused-declarations" class="results-section unused-declarations">
450 <h1>${ls`Unused declarations`}</h1>
Paul Lewisa3dcbf32019-10-29 14:41:51451 ${
Paul Lewised808ce2019-11-06 14:21:54452 unusedDeclarations.size > 0 ?
453 this._groupToFragment(unusedDeclarations, 'unused-declarations', 'declaration') :
Tim van der Lippe7946bbc2020-02-13 13:58:42454 UI.Fragment.Fragment.build`<div class="horizontally-padded">${ls`There are no unused declarations.`}</div>`}
Paul Lewis8aef9012019-10-29 14:15:17455 </div>
456
Paul Lewisebc47192019-10-09 15:06:41457 <div $="media-queries" class="results-section media-queries">
458 <h1>${ls`Media queries`}</h1>
Paul Lewisa3dcbf32019-10-29 14:41:51459 ${
Paul Lewised808ce2019-11-06 14:21:54460 mediaQueries.size > 0 ?
461 this._groupToFragment(mediaQueries, 'media-queries', 'text') :
Tim van der Lippe7946bbc2020-02-13 13:58:42462 UI.Fragment.Fragment.build`<div class="horizontally-padded">${ls`There are no media queries.`}</div>`}
Paul Lewisebc47192019-10-09 15:06:41463 </div>
Paul Lewis8cddf9932019-09-27 16:40:07464 </div>`;
465
Paul Lewisde0224c2019-10-22 16:23:15466 this._resultsContainer.element.appendChild(this._fragment.element());
Paul Lewis8cddf9932019-09-27 16:40:07467 }
468
Andres Olivares3dcefe42020-09-14 16:15:42469 /**
470 * @param {!Common.EventTarget.EventTargetEvent} evt
471 */
Paul Lewised808ce2019-11-06 14:21:54472 _createElementsView(evt) {
473 const {type, nodes} = evt.data;
Paul Lewisde0224c2019-10-22 16:23:15474
Paul Lewised808ce2019-11-06 14:21:54475 let id = '';
476 let tabTitle = '';
477
478 switch (type) {
Alex Rudenkod55e18c2020-09-23 11:37:06479 case 'contrast': {
480 const {section, key} = evt.data;
481 id = `${section}-${key}`;
482 tabTitle = ls`Contrast issues`;
483 break;
484 }
485
Mathias Bynens88e8f152020-03-25 14:33:12486 case 'color': {
Paul Lewised808ce2019-11-06 14:21:54487 const {section, color} = evt.data;
488 id = `${section}-${color}`;
489 tabTitle = `${color.toUpperCase()} (${section})`;
490 break;
Mathias Bynens88e8f152020-03-25 14:33:12491 }
Paul Lewised808ce2019-11-06 14:21:54492
Mathias Bynens88e8f152020-03-25 14:33:12493 case 'unused-declarations': {
Paul Lewised808ce2019-11-06 14:21:54494 const {declaration} = evt.data;
495 id = `${declaration}`;
496 tabTitle = `${declaration}`;
497 break;
Mathias Bynens88e8f152020-03-25 14:33:12498 }
Paul Lewised808ce2019-11-06 14:21:54499
Mathias Bynens88e8f152020-03-25 14:33:12500 case 'media-queries': {
Paul Lewised808ce2019-11-06 14:21:54501 const {text} = evt.data;
502 id = `${text}`;
503 tabTitle = `${text}`;
504 break;
Mathias Bynens88e8f152020-03-25 14:33:12505 }
Paul Lewis7d10a732019-11-06 16:11:51506
Mathias Bynens88e8f152020-03-25 14:33:12507 case 'font-info': {
Paul Lewis7d10a732019-11-06 16:11:51508 const {name} = evt.data;
509 id = `${name}`;
510 tabTitle = `${name}`;
511 break;
Mathias Bynens88e8f152020-03-25 14:33:12512 }
Paul Lewisde0224c2019-10-22 16:23:15513 }
514
Paul Lewised808ce2019-11-06 14:21:54515 let view = this._viewMap.get(id);
516 if (!view) {
Paul Lewis4da3b302019-11-21 14:23:47517 view = new ElementDetailsView(this._controller, this._domModel, this._cssModel, this._linkifier);
Paul Lewised808ce2019-11-06 14:21:54518 view.populateNodes(nodes);
519 this._viewMap.set(id, view);
Paul Lewisde0224c2019-10-22 16:23:15520 }
521
Paul Lewised808ce2019-11-06 14:21:54522 this._elementContainer.appendTab(id, tabTitle, view, true);
523 }
524
Andres Olivares3dcefe42020-09-14 16:15:42525 /**
526 * @param {!FontInfo} fontInfo
527 */
Paul Lewis7d10a732019-11-06 16:11:51528 _fontInfoToFragment(fontInfo) {
529 const fonts = Array.from(fontInfo.entries());
Tim van der Lippe7946bbc2020-02-13 13:58:42530 return UI.Fragment.Fragment.build`
Paul Lewis7d10a732019-11-06 16:11:51531 ${fonts.map(([font, fontMetrics]) => {
Tim van der Lippe7946bbc2020-02-13 13:58:42532 return UI.Fragment.Fragment.build
Paul Lewis7d10a732019-11-06 16:11:51533 `<section class="font-family"><h2>${font}</h2> ${this._fontMetricsToFragment(font, fontMetrics)}</section>`;
534 })}
535 `;
536 }
537
Andres Olivares3dcefe42020-09-14 16:15:42538 /**
539 * @param {string} font
540 * @param {!Map<string, !Map<string, !Array<number>>>} fontMetrics
541 */
Paul Lewis7d10a732019-11-06 16:11:51542 _fontMetricsToFragment(font, fontMetrics) {
543 const fontMetricInfo = Array.from(fontMetrics.entries());
544
Tim van der Lippe7946bbc2020-02-13 13:58:42545 return UI.Fragment.Fragment.build`
Paul Lewis7d10a732019-11-06 16:11:51546 <div class="font-metric">
547 ${fontMetricInfo.map(([label, values]) => {
548 const sanitizedPath = `${font}/${label}`;
Tim van der Lippe7946bbc2020-02-13 13:58:42549 return UI.Fragment.Fragment.build`
Paul Lewis7d10a732019-11-06 16:11:51550 <div>
551 <h3>${label}</h3>
552 ${this._groupToFragment(values, 'font-info', 'value', sanitizedPath)}
553 </div>`;
554 })}
555 </div>`;
556 }
557
Andres Olivares3dcefe42020-09-14 16:15:42558 /**
559 * @param {!Map<string, !Array<number|!UnusedDeclaration|!Protocol.CSS.CSSMedia>>} items
560 * @param {string} type
561 * @param {string} dataLabel
562 * @param {string} path
563 */
Paul Lewis7d10a732019-11-06 16:11:51564 _groupToFragment(items, type, dataLabel, path = '') {
Paul Lewised808ce2019-11-06 14:21:54565 // Sort by number of items descending.
566 const values = Array.from(items.entries()).sort((d1, d2) => {
567 const v1Nodes = d1[1];
568 const v2Nodes = d2[1];
569 return v2Nodes.length - v1Nodes.length;
570 });
571
572 const total = values.reduce((prev, curr) => prev + curr[1].length, 0);
573
Tim van der Lippe7946bbc2020-02-13 13:58:42574 return UI.Fragment.Fragment.build`<ul>
Paul Lewised808ce2019-11-06 14:21:54575 ${values.map(([title, nodes]) => {
576 const width = 100 * nodes.length / total;
577 const itemLabel = nodes.length === 1 ? ls`occurrence` : ls`occurrences`;
578
Tim van der Lippe7946bbc2020-02-13 13:58:42579 return UI.Fragment.Fragment.build`<li>
Paul Lewised808ce2019-11-06 14:21:54580 <div class="title">${title}</div>
Paul Lewis7d10a732019-11-06 16:11:51581 <button data-type="${type}" data-path="${path}" data-${dataLabel}="${title}">
Paul Lewised808ce2019-11-06 14:21:54582 <div class="details">${ls`${nodes.length} ${itemLabel}`}</div>
583 <div class="bar-container">
584 <div class="bar" style="width: ${width}%"></div>
585 </div>
586 </button>
587 </li>`;
588 })}
589 </ul>`;
Paul Lewisde0224c2019-10-22 16:23:15590 }
591
Andres Olivares3dcefe42020-09-14 16:15:42592 /**
Alex Rudenkod55e18c2020-09-23 11:37:06593 * @param {!Map<string, !Array<!ContrastIssue>>} issues
594 */
595 _contrastIssuesToFragment(issues) {
596 return UI.Fragment.Fragment.build`
597 <h2>${ls`Contrast issues: ${issues.size}`}</h2>
598 <ul>
599 ${[...issues.entries()].map(([key, value]) => this._contrastIssueToFragment(key, value))}
600 </ul>
601 `;
602 }
603
604 /**
605 * @param {string} key
606 * @param {!Array<!ContrastIssue>} issues
607 */
608 _contrastIssueToFragment(key, issues) {
609 console.assert(issues.length > 0);
610
611 let minContrastIssue = issues[0];
612 for (const issue of issues) {
613 if (issue.contrastRatio < minContrastIssue.contrastRatio) {
614 minContrastIssue = issue;
615 }
616 }
617
618 const color = /** @type { string }*/ (minContrastIssue.textColor.asString(Common.Color.Format.HEXA));
619 const backgroundColor =
620 /** @type { string }*/ (minContrastIssue.backgroundColor.asString(Common.Color.Format.HEXA));
621
622 const blockFragment = UI.Fragment.Fragment.build`<li>
623 <button
624 title="${
625 ls`Text color ${color} over ${backgroundColor} background results in low contrast for ${
626 issues.length} elements`}"
627 data-type="contrast" data-key="${key}" data-section="contrast" class="block" $="color">
628 Text
629 </button>
630 <div class="block-title">
631 <div class="contrast-warning" $="aa"><span class="threshold-label">${ls`AA`}</span></div>
632 <div class="contrast-warning" $="aaa"><span class="threshold-label">${ls`AAA`}</span></div>
633 </div>
634 </li>`;
635
636 const aa = /** @type {!HTMLElement} */ (blockFragment.$('aa'));
637 if (minContrastIssue.thresholdsViolated.aa) {
638 aa.appendChild(UI.Icon.Icon.create('smallicon-no'));
639 } else {
640 aa.appendChild(UI.Icon.Icon.create('smallicon-checkmark-square'));
641 }
642 const aaa = /** @type {!HTMLElement} */ (blockFragment.$('aaa'));
643 if (minContrastIssue.thresholdsViolated.aaa) {
644 aaa.appendChild(UI.Icon.Icon.create('smallicon-no'));
645 } else {
646 aaa.appendChild(UI.Icon.Icon.create('smallicon-checkmark-square'));
647 }
648
649 const block = /** @type {!HTMLElement} */ (blockFragment.$('color'));
650 block.style.backgroundColor = backgroundColor;
651 block.style.color = color;
Alex Rudenkod3ba0ee2020-09-25 14:01:21652 block.style.border = getBorderString(minContrastIssue.backgroundColor);
Alex Rudenkod55e18c2020-09-23 11:37:06653
654 return blockFragment;
655 }
656
657 /**
Andres Olivares3dcefe42020-09-14 16:15:42658 * @param {string} section
659 * @param {string} color
660 */
Paul Lewisde0224c2019-10-22 16:23:15661 _colorsToFragment(section, color) {
Tim van der Lippe7946bbc2020-02-13 13:58:42662 const blockFragment = UI.Fragment.Fragment.build`<li>
Paul Lewised808ce2019-11-06 14:21:54663 <button data-type="color" data-color="${color}" data-section="${section}" class="block" $="color"></button>
Paul Lewisde0224c2019-10-22 16:23:15664 <div class="block-title">${color}</div>
Paul Lewis8cddf9932019-09-27 16:40:07665 </li>`;
666
Andres Olivares3dcefe42020-09-14 16:15:42667 const block = /** @type {!HTMLElement} */ (blockFragment.$('color'));
Paul Lewisde0224c2019-10-22 16:23:15668 block.style.backgroundColor = color;
Paul Lewis8cddf9932019-09-27 16:40:07669
Tim van der Lippe7946bbc2020-02-13 13:58:42670 const borderColor = Common.Color.Color.parse(color);
Andres Olivares3dcefe42020-09-14 16:15:42671 if (!borderColor) {
672 return;
673 }
Alex Rudenkod3ba0ee2020-09-25 14:01:21674 block.style.border = getBorderString(borderColor);
Paul Lewis8cddf9932019-09-27 16:40:07675
676 return blockFragment;
677 }
678
Andres Olivares3dcefe42020-09-14 16:15:42679 /**
680 * @param {!NodeStyleStats} srcColors
681 */
Paul Lewisde0224c2019-10-22 16:23:15682 _sortColorsByLuminance(srcColors) {
683 return Array.from(srcColors.keys()).sort((colA, colB) => {
Tim van der Lippe7946bbc2020-02-13 13:58:42684 const colorA = Common.Color.Color.parse(colA);
685 const colorB = Common.Color.Color.parse(colB);
Andres Olivares3dcefe42020-09-14 16:15:42686 if (!colorA || !colorB) {
687 return 0;
688 }
Alex Rudenko91403e72020-06-04 07:10:49689 return Common.ColorUtils.luminance(colorB.rgba()) - Common.ColorUtils.luminance(colorA.rgba());
Paul Lewis8db3fa82019-10-10 11:55:53690 });
Paul Lewis8cddf9932019-09-27 16:40:07691 }
692
Andres Olivares3dcefe42020-09-14 16:15:42693 /**
694 * @param {!OverviewData} data
695 */
Paul Lewis8cddf9932019-09-27 16:40:07696 setOverviewData(data) {
697 this._render(data);
698 }
Paul Lewis4da3b302019-11-21 14:23:47699}
Paul Lewisebc47192019-10-09 15:06:41700
Paul Lewis4da3b302019-11-21 14:23:47701CSSOverviewCompletedView.pushedNodes = new Set();
702
Tim van der Lippe7946bbc2020-02-13 13:58:42703export class DetailsView extends UI.Widget.VBox {
Paul Lewised808ce2019-11-06 14:21:54704 constructor() {
705 super();
Paul Lewisebc47192019-10-09 15:06:41706
Tim van der Lippe7946bbc2020-02-13 13:58:42707 this._tabbedPane = new UI.TabbedPane.TabbedPane();
Paul Lewised808ce2019-11-06 14:21:54708 this._tabbedPane.show(this.element);
709 this._tabbedPane.addEventListener(UI.TabbedPane.Events.TabClosed, () => {
710 this.dispatchEventToListeners(UI.TabbedPane.Events.TabClosed, this._tabbedPane.tabIds().length);
711 });
712 }
713
714 /**
715 * @param {string} id
716 * @param {string} tabTitle
Tim van der Lippe7946bbc2020-02-13 13:58:42717 * @param {!UI.Widget.Widget} view
Paul Lewised808ce2019-11-06 14:21:54718 * @param {boolean=} isCloseable
719 */
720 appendTab(id, tabTitle, view, isCloseable) {
721 if (!this._tabbedPane.hasTab(id)) {
722 this._tabbedPane.appendTab(id, tabTitle, view, undefined, undefined, isCloseable);
723 }
724
725 this._tabbedPane.selectTab(id);
726 }
727
728 closeTabs() {
729 this._tabbedPane.closeTabs(this._tabbedPane.tabIds());
730 }
Paul Lewis4da3b302019-11-21 14:23:47731}
Paul Lewised808ce2019-11-06 14:21:54732
Tim van der Lippe7946bbc2020-02-13 13:58:42733export class ElementDetailsView extends UI.Widget.Widget {
Andres Olivares3dcefe42020-09-14 16:15:42734 /**
735 * @param {!OverviewController} controller
736 * @param {!SDK.DOMModel.DOMModel} domModel
737 * @param {!SDK.CSSModel.CSSModel} cssModel
738 * @param {!Components.Linkifier.Linkifier} linkifier
739 */
Paul Lewised808ce2019-11-06 14:21:54740 constructor(controller, domModel, cssModel, linkifier) {
741 super();
742
743 this._controller = controller;
744 this._domModel = domModel;
Paul Lewisebc47192019-10-09 15:06:41745 this._cssModel = cssModel;
746 this._linkifier = linkifier;
Paul Lewised808ce2019-11-06 14:21:54747
748 this._elementGridColumns = [
Andres Olivares3dcefe42020-09-14 16:15:42749 {
750 id: 'nodeId',
751 title: ls`Element`,
752 sortable: true,
753 weight: 50,
754 titleDOMFragment: undefined,
755 sort: undefined,
756 align: undefined,
Sigurd Schneider2533de12020-09-24 10:28:44757 width: undefined,
Andres Olivares3dcefe42020-09-14 16:15:42758 fixedWidth: undefined,
759 editable: undefined,
760 nonSelectable: undefined,
761 longText: undefined,
762 disclosure: undefined,
763 allowInSortByEvenWhenHidden: undefined,
764 dataType: undefined,
765 defaultWeight: undefined,
766 },
767 {
768 id: 'declaration',
769 title: ls`Declaration`,
770 sortable: true,
771 weight: 50,
772 titleDOMFragment: undefined,
773 sort: undefined,
774 align: undefined,
Sigurd Schneider2533de12020-09-24 10:28:44775 width: undefined,
Andres Olivares3dcefe42020-09-14 16:15:42776 fixedWidth: undefined,
777 editable: undefined,
778 nonSelectable: undefined,
779 longText: undefined,
780 disclosure: undefined,
781 allowInSortByEvenWhenHidden: undefined,
782 dataType: undefined,
783 defaultWeight: undefined,
784 },
785 {
786 id: 'sourceURL',
787 title: ls`Source`,
788 sortable: false,
789 weight: 100,
790 titleDOMFragment: undefined,
791 sort: undefined,
792 align: undefined,
Sigurd Schneider2533de12020-09-24 10:28:44793 width: undefined,
Andres Olivares3dcefe42020-09-14 16:15:42794 fixedWidth: undefined,
795 editable: undefined,
796 nonSelectable: undefined,
797 longText: undefined,
798 disclosure: undefined,
799 allowInSortByEvenWhenHidden: undefined,
800 dataType: undefined,
801 defaultWeight: undefined,
802 },
Alex Rudenkod3ba0ee2020-09-25 14:01:21803 {
804 id: 'contrastRatio',
805 title: ls`Contrast ratio`,
806 sortable: true,
807 weight: 25,
808 titleDOMFragment: undefined,
809 sort: undefined,
810 align: undefined,
811 width: undefined,
812 fixedWidth: undefined,
813 editable: undefined,
814 nonSelectable: undefined,
815 longText: undefined,
816 disclosure: undefined,
817 allowInSortByEvenWhenHidden: undefined,
818 dataType: undefined,
819 defaultWeight: undefined,
820 },
Paul Lewised808ce2019-11-06 14:21:54821 ];
822
Andres Olivares3dcefe42020-09-14 16:15:42823 this._elementGrid = new DataGrid.SortableDataGrid.SortableDataGrid({
824 displayName: ls`CSS Overview Elements`,
825 columns: this._elementGridColumns,
826 editCallback: undefined,
827 deleteCallback: undefined,
828 refreshCallback: undefined
829 });
Paul Lewised808ce2019-11-06 14:21:54830 this._elementGrid.element.classList.add('element-grid');
831 this._elementGrid.element.addEventListener('mouseover', this._onMouseOver.bind(this));
832 this._elementGrid.setStriped(true);
833 this._elementGrid.addEventListener(
834 DataGrid.DataGrid.Events.SortingChanged, this._sortMediaQueryDataGrid.bind(this));
835
836 this.element.appendChild(this._elementGrid.element);
837 }
838
839 _sortMediaQueryDataGrid() {
840 const sortColumnId = this._elementGrid.sortColumnId();
841 if (!sortColumnId) {
842 return;
843 }
844
Tim van der Lippe7946bbc2020-02-13 13:58:42845 const comparator = DataGrid.SortableDataGrid.SortableDataGrid.StringComparator.bind(null, sortColumnId);
Paul Lewised808ce2019-11-06 14:21:54846 this._elementGrid.sortNodes(comparator, !this._elementGrid.isSortOrderAscending());
847 }
848
Andres Olivares3dcefe42020-09-14 16:15:42849 /**
850 * @param {!Event} evt
851 */
Paul Lewised808ce2019-11-06 14:21:54852 _onMouseOver(evt) {
853 // Traverse the event path on the grid to find the nearest element with a backend node ID attached. Use
854 // that for the highlighting.
Andres Olivares3dcefe42020-09-14 16:15:42855 const node =
856 (/** @type {!Array<!HTMLElement>} */ (evt.composedPath())).find(el => el.dataset && el.dataset.backendNodeId);
Paul Lewised808ce2019-11-06 14:21:54857 if (!node) {
858 return;
859 }
860
861 const backendNodeId = Number(node.dataset.backendNodeId);
Tim van der Lippe9d0cb5f2020-01-09 14:10:38862 this._controller.dispatchEventToListeners(Events.RequestNodeHighlight, backendNodeId);
Paul Lewised808ce2019-11-06 14:21:54863 }
864
Andres Olivares3dcefe42020-09-14 16:15:42865 /**
866 * @param {!Array<!Object<string, *>>} data
867 */
Paul Lewised808ce2019-11-06 14:21:54868 async populateNodes(data) {
869 this._elementGrid.rootNode().removeChildren();
870
871 if (!data.length) {
872 return;
873 }
874
875 const [firstItem] = data;
Sigurd Schneider2ee231d2020-10-30 16:11:26876 const visibility = new Set();
877 firstItem.nodeId && visibility.add('nodeId');
878 firstItem.declaration && visibility.add('declaration');
879 firstItem.sourceURL && visibility.add('sourceURL');
880 firstItem.contrastRatio && visibility.add('contrastRatio');
Paul Lewised808ce2019-11-06 14:21:54881
882 let relatedNodesMap;
Sigurd Schneider2ee231d2020-10-30 16:11:26883 if (visibility.has('nodeId')) {
Paul Lewised808ce2019-11-06 14:21:54884 // Grab the nodes from the frontend, but only those that have not been
885 // retrieved already.
Andres Olivares3dcefe42020-09-14 16:15:42886 const nodeIds = /** @type {!Set<number>} */ (data.reduce((prev, curr) => {
Tim van der Lippe9d0cb5f2020-01-09 14:10:38887 if (CSSOverviewCompletedView.pushedNodes.has(curr.nodeId)) {
Paul Lewised808ce2019-11-06 14:21:54888 return prev;
889 }
890
Tim van der Lippe9d0cb5f2020-01-09 14:10:38891 CSSOverviewCompletedView.pushedNodes.add(curr.nodeId);
Paul Lewised808ce2019-11-06 14:21:54892 return prev.add(curr.nodeId);
Andres Olivares3dcefe42020-09-14 16:15:42893 }, new Set()));
Paul Lewised808ce2019-11-06 14:21:54894 relatedNodesMap = await this._domModel.pushNodesByBackendIdsToFrontend(nodeIds);
895 }
896
897 for (const item of data) {
Sigurd Schneider2ee231d2020-10-30 16:11:26898 if (visibility.has('nodeId')) {
Andres Olivares3dcefe42020-09-14 16:15:42899 if (!relatedNodesMap) {
900 continue;
901 }
Paul Lewised808ce2019-11-06 14:21:54902 const frontendNode = relatedNodesMap.get(item.nodeId);
903 if (!frontendNode) {
904 continue;
905 }
906
907 item.node = frontendNode;
908 }
909
Paul Lewis4da3b302019-11-21 14:23:47910 const node = new ElementNode(this._elementGrid, item, this._linkifier, this._cssModel);
Paul Lewised808ce2019-11-06 14:21:54911 node.selectable = false;
912 this._elementGrid.insertChild(node);
913 }
914
915 this._elementGrid.setColumnsVisiblity(visibility);
916 this._elementGrid.renderInline();
917 this._elementGrid.wasShown();
918 }
Paul Lewis4da3b302019-11-21 14:23:47919}
Paul Lewised808ce2019-11-06 14:21:54920
Andres Olivares3dcefe42020-09-14 16:15:42921/**
922 * @extends {DataGrid.SortableDataGrid.SortableDataGridNode<!ElementNode>}
923 */
Tim van der Lippe7946bbc2020-02-13 13:58:42924export class ElementNode extends DataGrid.SortableDataGrid.SortableDataGridNode {
Paul Lewised808ce2019-11-06 14:21:54925 /**
Andres Olivares3dcefe42020-09-14 16:15:42926 * @param {!DataGrid.SortableDataGrid.SortableDataGrid<!ElementNode>} dataGrid
Paul Lewised808ce2019-11-06 14:21:54927 * @param {!Object<string,*>} data
Tim van der Lippe7946bbc2020-02-13 13:58:42928 * @param {!Components.Linkifier.Linkifier} linkifier
929 * @param {!SDK.CSSModel.CSSModel} cssModel
Paul Lewised808ce2019-11-06 14:21:54930 */
931 constructor(dataGrid, data, linkifier, cssModel) {
932 super(dataGrid, data.hasChildren);
933
934 this.data = data;
935 this._linkifier = linkifier;
936 this._cssModel = cssModel;
Paul Lewisebc47192019-10-09 15:06:41937 }
938
939 /**
940 * @override
941 * @param {string} columnId
Sigurd Schneider11657912020-06-30 13:26:52942 * @return {!HTMLElement}
Paul Lewisebc47192019-10-09 15:06:41943 */
944 createCell(columnId) {
Paul Lewised808ce2019-11-06 14:21:54945 // Nodes.
946 if (columnId === 'nodeId') {
Paul Lewisebc47192019-10-09 15:06:41947 const cell = this.createTD(columnId);
Paul Lewised808ce2019-11-06 14:21:54948 cell.textContent = '...';
Paul Lewisebc47192019-10-09 15:06:41949
Tim van der Lippe7946bbc2020-02-13 13:58:42950 Common.Linkifier.Linkifier.linkify(this.data.node).then(link => {
Paul Lewised808ce2019-11-06 14:21:54951 cell.textContent = '';
Andres Olivares3dcefe42020-09-14 16:15:42952 /** @type {!HTMLElement} */ (link).dataset.backendNodeId = this.data.node.backendNodeId();
Paul Lewisebc47192019-10-09 15:06:41953 cell.appendChild(link);
Alex Rudenkob7419b72020-09-29 11:10:08954 const button = document.createElement('button');
955 button.classList.add('show-element');
956 button.title = ls`Show element`;
957 button.tabIndex = 0;
958 button.onclick = () => this.data.node.scrollIntoView();
959 cell.appendChild(button);
Paul Lewised808ce2019-11-06 14:21:54960 });
961 return cell;
962 }
963
964 // Links to CSS.
965 if (columnId === 'sourceURL') {
966 const cell = this.createTD(columnId);
967
968 if (this.data.range) {
969 const link = this._linkifyRuleLocation(
Tim van der Lippe7946bbc2020-02-13 13:58:42970 this._cssModel, this._linkifier, this.data.styleSheetId,
971 TextUtils.TextRange.TextRange.fromObject(this.data.range));
Paul Lewised808ce2019-11-06 14:21:54972
Andres Olivares3dcefe42020-09-14 16:15:42973 if (!link || link.textContent === '') {
Mathias Bynens23ee1aa2020-03-02 12:06:38974 cell.textContent = '(unable to link)';
Andres Olivares3dcefe42020-09-14 16:15:42975 } else {
976 cell.appendChild(link);
Paul Lewised808ce2019-11-06 14:21:54977 }
Paul Lewisebc47192019-10-09 15:06:41978 } else {
Paul Lewised808ce2019-11-06 14:21:54979 cell.textContent = '(unable to link to inlined styles)';
Paul Lewisebc47192019-10-09 15:06:41980 }
981 return cell;
982 }
983
Alex Rudenkod3ba0ee2020-09-25 14:01:21984 if (columnId === 'contrastRatio') {
985 const cell = this.createTD(columnId);
986 const contrastFragment = UI.Fragment.Fragment.build`
987 <div class="contrast-container-in-grid" $="container">
988 <span class="contrast-preview" style="border: ${getBorderString(this.data.backgroundColor)}; color: ${
989 this.data.textColor.asString()}; background-color: ${this.data.backgroundColor.asString()};">Aa</span>
990 <span>${this.data.contrastRatio.toFixed(2)}</span>
991 </div>
992 `;
993 const container = contrastFragment.$('container');
994 container.append(UI.Fragment.Fragment.build`<span>${ls`AA`}</span>`.element());
995 if (this.data.thresholdsViolated.aa) {
996 container.appendChild(UI.Icon.Icon.create('smallicon-no'));
997 } else {
998 container.appendChild(UI.Icon.Icon.create('smallicon-checkmark-square'));
999 }
1000 container.append(UI.Fragment.Fragment.build`<span>${ls`AAA`}</span>`.element());
1001 if (this.data.thresholdsViolated.aaa) {
1002 container.appendChild(UI.Icon.Icon.create('smallicon-no'));
1003 } else {
1004 container.appendChild(UI.Icon.Icon.create('smallicon-checkmark-square'));
1005 }
1006 cell.appendChild(contrastFragment.element());
1007 return cell;
1008 }
1009
Paul Lewisebc47192019-10-09 15:06:411010 return super.createCell(columnId);
1011 }
1012
Andres Olivares3dcefe42020-09-14 16:15:421013 /**
1014 * @param {!SDK.CSSModel.CSSModel} cssModel
1015 * @param {!Components.Linkifier.Linkifier} linkifier
1016 * @param {!Protocol.CSS.StyleSheetId} styleSheetId
1017 * @param {!TextUtils.TextRange.TextRange} ruleLocation
1018 */
Paul Lewisebc47192019-10-09 15:06:411019 _linkifyRuleLocation(cssModel, linkifier, styleSheetId, ruleLocation) {
1020 const styleSheetHeader = cssModel.styleSheetHeaderForId(styleSheetId);
Andres Olivares3dcefe42020-09-14 16:15:421021 if (!styleSheetHeader) {
1022 return;
1023 }
Paul Lewisebc47192019-10-09 15:06:411024 const lineNumber = styleSheetHeader.lineNumberInSource(ruleLocation.startLine);
1025 const columnNumber = styleSheetHeader.columnNumberInSource(ruleLocation.startLine, ruleLocation.startColumn);
Tim van der Lippe7946bbc2020-02-13 13:58:421026 const matchingSelectorLocation = new SDK.CSSModel.CSSLocation(styleSheetHeader, lineNumber, columnNumber);
Paul Lewisebc47192019-10-09 15:06:411027 return linkifier.linkifyCSSLocation(matchingSelectorLocation);
1028 }
Paul Lewis4da3b302019-11-21 14:23:471029}