| // Copyright 2023 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 * as TraceEngine from '../../models/trace/trace.js'; |
| import type * as PerfUI from '../../ui/legacy/components/perf_ui/perf_ui.js'; |
| |
| import { |
| type CompatibilityTracksAppender, |
| type TrackAppender, |
| type HighlightedEntryInfo, |
| type TrackAppenderName, |
| } from './CompatibilityTracksAppender.js'; |
| import * as i18n from '../../core/i18n/i18n.js'; |
| import {buildGroupStyle, buildTrackHeader, getFormattedTime} from './AppenderUtils.js'; |
| |
| const UIStrings = { |
| /** |
| *@description Text in Timeline Flame Chart Data Provider of the Performance panel |
| */ |
| layoutShifts: 'Layout Shifts', |
| }; |
| |
| const str_ = i18n.i18n.registerUIStrings('panels/timeline/LayoutShiftsTrackAppender.ts', UIStrings); |
| const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); |
| |
| export class LayoutShiftsTrackAppender implements TrackAppender { |
| readonly appenderName: TrackAppenderName = 'LayoutShifts'; |
| |
| #compatibilityBuilder: CompatibilityTracksAppender; |
| #flameChartData: PerfUI.FlameChart.FlameChartTimelineData; |
| #traceParsedData: Readonly<TraceEngine.TraceModel.PartialTraceParseDataDuringMigration>; |
| |
| constructor( |
| compatibilityBuilder: CompatibilityTracksAppender, flameChartData: PerfUI.FlameChart.FlameChartTimelineData, |
| traceParsedData: TraceEngine.TraceModel.PartialTraceParseDataDuringMigration) { |
| this.#compatibilityBuilder = compatibilityBuilder; |
| this.#flameChartData = flameChartData; |
| this.#traceParsedData = traceParsedData; |
| } |
| |
| /** |
| * Appends into the flame chart data the data corresponding to the |
| * layout shifts track. |
| * @param trackStartLevel the horizontal level of the flame chart events where |
| * the track's events will start being appended. |
| * @param expanded wether the track should be rendered expanded. |
| * @returns the first available level to append more data after having |
| * appended the track's events. |
| */ |
| appendTrackAtLevel(trackStartLevel: number, expanded?: boolean): number { |
| if (this.#traceParsedData.LayoutShifts.clusters.length === 0) { |
| return trackStartLevel; |
| } |
| this.#appendTrackHeaderAtLevel(trackStartLevel, expanded); |
| return this.#appendLayoutShiftsAtLevel(trackStartLevel); |
| } |
| |
| /** |
| * Adds into the flame chart data the header corresponding to the |
| * layout shifts track. A header is added in the shape of a group in the |
| * flame chart data. A group has a predefined style and a reference |
| * to the definition of the legacy track (which should be removed |
| * in the future). |
| * @param currentLevel the flame chart level at which the header is |
| * appended. |
| */ |
| #appendTrackHeaderAtLevel(currentLevel: number, expanded?: boolean): void { |
| const style = buildGroupStyle({collapsible: false}); |
| const group = buildTrackHeader( |
| currentLevel, i18nString(UIStrings.layoutShifts), style, |
| /* selectable= */ true, expanded); |
| this.#compatibilityBuilder.registerTrackForGroup(group, this); |
| } |
| |
| /** |
| * Adds into the flame chart data all the layout shifts. These are taken from |
| * the clusters that are collected in the LayoutShiftsHandler. |
| * @param currentLevel the flame chart level from which layout shifts will |
| * be appended. |
| * @returns the next level after the last occupied by the appended |
| * layout shifts (the first available level to append more data). |
| */ |
| #appendLayoutShiftsAtLevel(currentLevel: number): number { |
| const allLayoutShifts = this.#traceParsedData.LayoutShifts.clusters.flatMap(cluster => cluster.events); |
| const newLevel = this.#compatibilityBuilder.appendEventsAtLevel(allLayoutShifts, currentLevel, this); |
| |
| // Bit of a hack: LayoutShifts are instant events, so have no duration. But |
| // OPP doesn't do well at making tiny events easy to spot and click. So we |
| // set it to a small duration so that the user is able to see and click |
| // them more easily. Long term we will explore a better UI solution to |
| // allow us to do this properly and not hack around it. |
| const msDuration = TraceEngine.Types.Timing.MicroSeconds(5_000); |
| for (let i = 0; i < allLayoutShifts.length; ++i) { |
| const index = this.#compatibilityBuilder.indexForEvent(allLayoutShifts[i]); |
| if (index === undefined) { |
| continue; |
| } |
| this.#flameChartData.entryTotalTimes[index] = TraceEngine.Helpers.Timing.microSecondsToMilliseconds(msDuration); |
| } |
| return newLevel; |
| } |
| |
| /* |
| ------------------------------------------------------------------------------------ |
| The following methods are invoked by the flame chart renderer to query features about |
| events on rendering. |
| ------------------------------------------------------------------------------------ |
| */ |
| |
| /** |
| * Gets the color an event added by this appender should be rendered with. |
| */ |
| colorForEvent(_event: TraceEngine.Types.TraceEvents.TraceEventData): string { |
| return 'rgb(155 127 230)'; |
| } |
| |
| /** |
| * Gets the title an event added by this appender should be rendered with. |
| */ |
| titleForEvent(event: TraceEngine.Types.TraceEvents.TraceEventData): string { |
| if (TraceEngine.Types.TraceEvents.isTraceEventLayoutShift(event)) { |
| return 'Layout shift'; |
| } |
| return event.name; |
| } |
| |
| /** |
| * Returns the info shown when an event added by this appender |
| * is hovered in the timeline. |
| */ |
| highlightedEntryInfo(event: TraceEngine.Types.TraceEvents.TraceEventLayoutShift): HighlightedEntryInfo { |
| const title = this.titleForEvent(event); |
| return {title, formattedTime: getFormattedTime(event.dur)}; |
| } |
| } |