chrome-devtools-frontend
Version:
Chrome DevTools UI
138 lines (113 loc) • 4.72 kB
text/typescript
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as i18n from '../../../../core/i18n/i18n.js';
import type {LCPBreakdownInsightModel} from '../../../../models/trace/insights/LCPBreakdown.js';
import * as Trace from '../../../../models/trace/trace.js';
import * as UI from '../../../../ui/legacy/legacy.js';
import * as Lit from '../../../../ui/lit/lit.js';
import type * as Overlays from '../../overlays/overlays.js';
import {BaseInsightComponent} from './BaseInsightComponent.js';
import {Table, type TableDataRow} from './Table.js';
const {UIStrings, i18nString} = Trace.Insights.Models.LCPBreakdown;
const {html} = Lit;
const {widgetConfig} = UI.Widget;
export class LCPBreakdown extends BaseInsightComponent<LCPBreakdownInsightModel> {
override internalName = 'lcp-by-phase';
#overlay: Trace.Types.Overlays.TimespanBreakdown|null = null;
protected override hasAskAiSupport(): boolean {
return true;
}
protected override createOverlays(): Trace.Types.Overlays.Overlay[] {
this.#overlay = null;
if (!this.model || !this.model.subparts || !this.model.lcpTs) {
return [];
}
const overlays = this.model.createOverlays?.();
if (!overlays) {
return [];
}
this.#overlay = overlays[0] as Trace.Types.Overlays.TimespanBreakdown;
return overlays;
}
#renderFieldSubparts(): Lit.LitTemplate|null {
if (!this.fieldMetrics) {
return null;
}
const {ttfb, loadDelay, loadDuration, renderDelay} = this.fieldMetrics.lcpBreakdown;
if (!ttfb || !loadDelay || !loadDuration || !renderDelay) {
return null;
}
const ttfbMillis = i18n.TimeUtilities.preciseMillisToString(Trace.Helpers.Timing.microToMilli(ttfb.value));
const loadDelayMillis =
i18n.TimeUtilities.preciseMillisToString(Trace.Helpers.Timing.microToMilli(loadDelay.value));
const loadDurationMillis =
i18n.TimeUtilities.preciseMillisToString(Trace.Helpers.Timing.microToMilli(loadDuration.value));
const renderDelayMillis =
i18n.TimeUtilities.preciseMillisToString(Trace.Helpers.Timing.microToMilli(renderDelay.value));
const rows = [
{values: [i18nString(UIStrings.timeToFirstByte), ttfbMillis]},
{values: [i18nString(UIStrings.resourceLoadDelay), loadDelayMillis]},
{values: [i18nString(UIStrings.resourceLoadDuration), loadDurationMillis]},
{values: [i18nString(UIStrings.elementRenderDelay), renderDelayMillis]},
];
// clang-format off
return html`
<div class="insight-section">
<devtools-widget .widgetConfig=${widgetConfig(Table, {
data: {
insight: this,
headers: [i18nString(UIStrings.subpart), i18nString(UIStrings.fieldDuration)],
rows,
}})}>
</devtools-widget>
</div>
`;
// clang-format on
}
override toggleTemporaryOverlays(
overlays: Trace.Types.Overlays.Overlay[]|null, options: Overlays.Overlays.TimelineOverlaySetOptions): void {
super.toggleTemporaryOverlays(overlays, {...options, updateTraceWindowPercentage: 0});
}
override getOverlayOptionsForInitialOverlays(): Overlays.Overlays.TimelineOverlaySetOptions {
return {updateTraceWindow: true, updateTraceWindowPercentage: 0};
}
override renderContent(): Lit.LitTemplate {
if (!this.model) {
return Lit.nothing;
}
const {subparts} = this.model;
if (!subparts) {
return html`<div class="insight-section">${i18nString(UIStrings.noLcp)}</div>`;
}
const rows: TableDataRow[] = Object.values(subparts).map((subpart: Trace.Insights.Models.LCPBreakdown.Subpart) => {
const section = this.#overlay?.sections.find(section => subpart.label === section.label);
const timing = Trace.Helpers.Timing.microToMilli(subpart.range);
return {
values: [subpart.label, i18n.TimeUtilities.preciseMillisToString(timing)],
overlays: section && [{
type: 'TIMESPAN_BREAKDOWN',
sections: [section],
}],
};
});
// clang-format off
const sections: Lit.LitTemplate[] = [html`
<div class="insight-section">
<devtools-widget .widgetConfig=${widgetConfig(Table, {
data: {
insight: this,
headers: [i18nString(UIStrings.subpart), i18nString(UIStrings.duration)],
rows,
}})}>
</devtools-widget>
</div>`
];
// clang-format on
const fieldDataSection = this.#renderFieldSubparts();
if (fieldDataSection) {
sections.push(fieldDataSection);
}
return html`${sections}`;
}
}