UNPKG

chrome-devtools-frontend

Version:
181 lines (156 loc) • 6.63 kB
// Copyright 2024 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import './Table.js'; import '../../../../ui/components/linkifier/linkifier.js'; import * as i18n from '../../../../core/i18n/i18n.js'; import * as Platform from '../../../../core/platform/platform.js'; import * as SDK from '../../../../core/sdk/sdk.js'; import type * as Protocol from '../../../../generated/protocol.js'; import type {SlowCSSSelectorInsightModel} from '../../../../models/trace/insights/SlowCSSSelector.js'; import * as Trace from '../../../../models/trace/trace.js'; import type * as Linkifier from '../../../../ui/components/linkifier/linkifier.js'; import * as Lit from '../../../../ui/lit/lit.js'; import type * as Overlays from '../../overlays/overlays.js'; import {BaseInsightComponent} from './BaseInsightComponent.js'; import type {TableData} from './Table.js'; const {UIStrings, i18nString} = Trace.Insights.Models.SlowCSSSelector; const {html} = Lit; export class SlowCSSSelector extends BaseInsightComponent<SlowCSSSelectorInsightModel> { static override readonly litTagName = Lit.StaticHtml.literal`devtools-performance-slow-css-selector`; override internalName = 'slow-css-selector'; #selectorLocations = new Map<string, Protocol.CSS.SourceRange[]>(); override createOverlays(): Overlays.Overlays.TimelineOverlay[] { return []; } private async toSourceFileLocation(cssModel: SDK.CSSModel.CSSModel, selector: Trace.Types.Events.SelectorTiming): Promise<Linkifier.Linkifier.LinkifierData[]|undefined> { if (!cssModel) { return undefined; } const styleSheetHeader = cssModel.styleSheetHeaderForId(selector.style_sheet_id as Protocol.CSS.StyleSheetId); if (!styleSheetHeader || !styleSheetHeader.resourceURL()) { return undefined; } // get the locations from cache if available const key: string = JSON.stringify({selectorText: selector.selector, styleSheetId: selector.style_sheet_id}); let ranges = this.#selectorLocations.get(key); if (!ranges) { const result = await cssModel.agent.invoke_getLocationForSelector( {selectorText: selector.selector, styleSheetId: selector.style_sheet_id as Protocol.CSS.StyleSheetId}); if (result.getError() || !result.ranges) { return undefined; } ranges = result.ranges; this.#selectorLocations.set(key, ranges); } const locations = ranges.map((range, itemIndex) => { return { url: styleSheetHeader.resourceURL(), lineNumber: range.startLine, columnNumber: range.startColumn, linkText: `[${itemIndex + 1}]`, title: `${styleSheetHeader.id} line ${range.startLine + 1}:${range.startColumn + 1}`, } as Linkifier.Linkifier.LinkifierData; }); return locations; } private async getSelectorLinks( cssModel: SDK.CSSModel.CSSModel|null|undefined, selector: Trace.Types.Events.SelectorTiming): Promise<Lit.LitTemplate> { if (!cssModel) { return Lit.nothing; } if (!selector.style_sheet_id) { return Lit.nothing; } const locations = await this.toSourceFileLocation(cssModel, selector); if (!locations) { return Lit.nothing; } const links = html` ${locations.map((location, itemIndex) => { const divider = itemIndex !== locations.length - 1 ? ', ' : ''; return html`<devtools-linkifier .data=${location}></devtools-linkifier>${divider}`; })}`; return links; } override renderContent(): Lit.LitTemplate { if (!this.model) { return Lit.nothing; } const target = SDK.TargetManager.TargetManager.instance().primaryPageTarget(); const cssModel = target?.model(SDK.CSSModel.CSSModel); const time = (us: Trace.Types.Timing.Micro): string => i18n.TimeUtilities.millisToString(Platform.Timing.microSecondsToMilliSeconds(us)); if (!this.model.topMatchAttempts.length && !this.model.topElapsedMs.length) { return html`<div class="insight-section">${i18nString(UIStrings.enableSelectorData)}</div>`; } // clang-format off const sections = [html` <div class="insight-section"> <devtools-performance-table .data=${{ insight: this, headers: [i18nString(UIStrings.total), ''], rows: [ {values: [i18nString(UIStrings.elapsed), i18n.TimeUtilities.millisToString(this.model.totalElapsedMs)]}, {values: [i18nString(UIStrings.matchAttempts), this.model.totalMatchAttempts]}, {values: [i18nString(UIStrings.matchCount), this.model.totalMatchCount]}, ], } as TableData}> </devtools-performance-table> </div> `]; // clang-format on if (this.model.topElapsedMs.length) { // clang-format off sections.push(html` <div class="insight-section"> <devtools-performance-table .data=${{ insight: this, headers: [i18nString(UIStrings.topSelectors), i18nString(UIStrings.elapsed)], rows: this.model.topElapsedMs.map(selector => { return { values: [ html`${selector.selector} ${Lit.Directives.until(this.getSelectorLinks(cssModel, selector))}`, time(Trace.Types.Timing.Micro(selector['elapsed (us)']))], }; }), } as TableData}> </devtools-performance-table> </div> `); // clang-format on } if (this.model.topMatchAttempts.length) { // clang-format off sections.push(html` <div class="insight-section"> <devtools-performance-table .data=${{ insight: this, headers: [i18nString(UIStrings.topSelectors), i18nString(UIStrings.matchAttempts)], rows: this.model.topMatchAttempts.map(selector => { return { values: [ html`${selector.selector} ${Lit.Directives.until(this.getSelectorLinks(cssModel, selector))}` as unknown as string, selector['match_attempts']], }; }), } as TableData}> </devtools-performance-table> </div> `); // clang-format on } return html`${sections}`; } } declare global { interface HTMLElementTagNameMap { 'devtools-performance-slow-css-selector': SlowCSSSelector; } } customElements.define('devtools-performance-slow-css-selector', SlowCSSSelector);