[go: nahoru, domu]

blob: 4dc513c9fb3014216c320768b2674fbae0d00340 [file] [log] [blame]
// Copyright 2017 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 type * as Common from '../../core/common/common.js';
import * as i18n from '../../core/i18n/i18n.js';
import * as SDK from '../../core/sdk/sdk.js';
import * as TimelineModel from '../../models/timeline_model/timeline_model.js';
import * as Components from '../../ui/legacy/components/utils/utils.js';
import * as UI from '../../ui/legacy/legacy.js';
import type * as TraceEngine from '../../models/trace/trace.js';
import {EventsTimelineTreeView} from './EventsTimelineTreeView.js';
import {Events, type PerformanceModel} from './PerformanceModel.js';
import {TimelineLayersView} from './TimelineLayersView.js';
import {TimelinePaintProfilerView} from './TimelinePaintProfilerView.js';
import {type TimelineModeViewDelegate} from './TimelinePanel.js';
import {TimelineSelection} from './TimelineSelection.js';
import {BottomUpTimelineTreeView, CallTreeTimelineTreeView, type TimelineTreeView} from './TimelineTreeView.js';
import {TimelineDetailsContentHelper, TimelineUIUtils} from './TimelineUIUtils.js';
const UIStrings = {
/**
*@description Text for the summary view
*/
summary: 'Summary',
/**
*@description Text in Timeline Details View of the Performance panel
*/
bottomup: 'Bottom-Up',
/**
*@description Text in Timeline Details View of the Performance panel
*/
callTree: 'Call Tree',
/**
*@description Text in Timeline Details View of the Performance panel
*/
eventLog: 'Event Log',
/**
*@description The label for estimated total blocking time in the performance panel
*/
estimated: 'estimated',
/**
*@description Label for the total blocking time in the Performance Panel
*@example {320.23} PH1
*@example {(estimated)} PH2
*/
totalBlockingTimeSmss: 'Total blocking time: {PH1}ms{PH2}',
/**
*@description Text that is usually a hyperlink to more documentation
*/
learnMore: 'Learn more',
/**
*@description Title of the Layers tool
*/
layers: 'Layers',
/**
*@description Title of the paint profiler, old name of the performance pane
*/
paintProfiler: 'Paint Profiler',
/**
*@description Text in Timeline Details View of the Performance panel
*@example {1ms} PH1
*@example {10ms} PH2
*/
rangeSS: 'Range: {PH1} – {PH2}',
};
const str_ = i18n.i18n.registerUIStrings('panels/timeline/TimelineDetailsView.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
export class TimelineDetailsView extends UI.Widget.VBox {
private readonly detailsLinkifier: Components.Linkifier.Linkifier;
private tabbedPane: UI.TabbedPane.TabbedPane;
private readonly defaultDetailsWidget: UI.Widget.VBox;
private readonly defaultDetailsContentElement: HTMLElement;
private rangeDetailViews: Map<string, TimelineTreeView>;
private readonly additionalMetricsToolbar: UI.Toolbar.Toolbar;
private model!: PerformanceModel;
private track?: TimelineModel.TimelineModel.Track|null;
private lazyPaintProfilerView?: TimelinePaintProfilerView|null;
private lazyLayersView?: TimelineLayersView|null;
private preferredTabId?: string;
private selection?: TimelineSelection|null;
#traceEngineData: TraceEngine.TraceModel.PartialTraceParseDataDuringMigration|null = null;
constructor(delegate: TimelineModeViewDelegate) {
super();
this.element.classList.add('timeline-details');
this.detailsLinkifier = new Components.Linkifier.Linkifier();
this.tabbedPane = new UI.TabbedPane.TabbedPane();
this.tabbedPane.show(this.element);
this.defaultDetailsWidget = new UI.Widget.VBox();
this.defaultDetailsWidget.element.classList.add('timeline-details-view');
this.defaultDetailsContentElement =
this.defaultDetailsWidget.element.createChild('div', 'timeline-details-view-body vbox');
this.appendTab(Tab.Details, i18nString(UIStrings.summary), this.defaultDetailsWidget);
this.setPreferredTab(Tab.Details);
this.rangeDetailViews = new Map();
const bottomUpView = new BottomUpTimelineTreeView();
this.appendTab(Tab.BottomUp, i18nString(UIStrings.bottomup), bottomUpView);
this.rangeDetailViews.set(Tab.BottomUp, bottomUpView);
const callTreeView = new CallTreeTimelineTreeView();
this.appendTab(Tab.CallTree, i18nString(UIStrings.callTree), callTreeView);
this.rangeDetailViews.set(Tab.CallTree, callTreeView);
const eventsView = new EventsTimelineTreeView(delegate);
this.appendTab(Tab.EventLog, i18nString(UIStrings.eventLog), eventsView);
this.rangeDetailViews.set(Tab.EventLog, eventsView);
this.additionalMetricsToolbar = new UI.Toolbar.Toolbar('timeline-additional-metrics');
this.element.appendChild(this.additionalMetricsToolbar.element);
this.tabbedPane.addEventListener(UI.TabbedPane.Events.TabSelected, this.tabSelected, this);
}
setModel(
model: PerformanceModel|null, traceEngineData: TraceEngine.TraceModel.PartialTraceParseDataDuringMigration|null,
track: TimelineModel.TimelineModel.Track|null): void {
if (this.model !== model) {
if (this.model) {
this.model.removeEventListener(Events.WindowChanged, this.onWindowChanged, this);
}
this.model = (model as PerformanceModel);
if (this.model) {
this.model.addEventListener(Events.WindowChanged, this.onWindowChanged, this);
}
}
this.#traceEngineData = traceEngineData;
this.track = track;
this.tabbedPane.closeTabs([Tab.PaintProfiler, Tab.LayerViewer], false);
for (const view of this.rangeDetailViews.values()) {
view.setModel(model, track, traceEngineData);
}
this.lazyPaintProfilerView = null;
this.lazyLayersView = null;
this.setSelection(null);
// Add TBT info to the footer.
this.additionalMetricsToolbar.removeToolbarItems();
if (model && model.timelineModel()) {
const {estimated, time} = model.timelineModel().totalBlockingTime();
const isEstimate = estimated ? ` (${i18nString(UIStrings.estimated)})` : '';
const message = i18nString(UIStrings.totalBlockingTimeSmss, {PH1: time.toFixed(2), PH2: isEstimate});
const warning = document.createElement('span');
const clsLink = UI.XLink.XLink.create('https://web.dev/tbt/', i18nString(UIStrings.learnMore));
// Prevent focus ring from being cut off.
clsLink.style.margin = '3px';
warning.appendChild(clsLink);
this.additionalMetricsToolbar.appendText(message);
this.additionalMetricsToolbar.appendToolbarItem(new UI.Toolbar.ToolbarItem(warning));
}
}
private setContent(node: Node): void {
const allTabs = this.tabbedPane.otherTabs(Tab.Details);
for (let i = 0; i < allTabs.length; ++i) {
if (!this.rangeDetailViews.has(allTabs[i])) {
this.tabbedPane.closeTab(allTabs[i]);
}
}
this.defaultDetailsContentElement.removeChildren();
this.defaultDetailsContentElement.appendChild(node);
}
private updateContents(): void {
const view = this.rangeDetailViews.get(this.tabbedPane.selectedTabId || '');
if (view) {
const window = this.model.window();
view.updateContents(this.selection || TimelineSelection.fromRange(window.left, window.right));
}
}
private appendTab(id: string, tabTitle: string, view: UI.Widget.Widget, isCloseable?: boolean): void {
this.tabbedPane.appendTab(id, tabTitle, view, undefined, undefined, isCloseable);
if (this.preferredTabId !== this.tabbedPane.selectedTabId) {
this.tabbedPane.selectTab(id);
}
}
headerElement(): Element {
return this.tabbedPane.headerElement();
}
setPreferredTab(tabId: string): void {
this.preferredTabId = tabId;
}
private onWindowChanged(): void {
if (!this.selection) {
this.updateContentsFromWindow();
}
}
private updateContentsFromWindow(): void {
if (!this.model) {
this.setContent(UI.Fragment.html`<div/>`);
return;
}
const window = this.model.window();
this.updateSelectedRangeStats(window.left, window.right);
this.updateContents();
}
setSelection(selection: TimelineSelection|null): void {
this.detailsLinkifier.reset();
this.selection = selection;
if (!this.selection) {
this.updateContentsFromWindow();
return;
}
const selectionObject = this.selection.object;
if (TimelineSelection.isTraceEventSelection(selectionObject)) {
const event = selectionObject;
void TimelineUIUtils
.buildTraceEventDetails(event, this.model.timelineModel(), this.detailsLinkifier, true, this.#traceEngineData)
.then(fragment => this.appendDetailsTabsForTraceEventAndShowDetails(event, fragment));
} else if (TimelineSelection.isFrameObject(selectionObject)) {
const frame = selectionObject;
const filmStripFrame = this.model.filmStripModelFrame(frame);
this.setContent(TimelineUIUtils.generateDetailsContentForFrame(frame, filmStripFrame));
if (frame.layerTree) {
const layersView = this.layersView();
layersView.showLayerTree(frame.layerTree);
if (!this.tabbedPane.hasTab(Tab.LayerViewer)) {
this.appendTab(Tab.LayerViewer, i18nString(UIStrings.layers), layersView);
}
}
} else if (TimelineSelection.isNetworkRequestSelection(selectionObject)) {
const request = selectionObject;
void TimelineUIUtils.buildNetworkRequestDetails(request, this.model.timelineModel(), this.detailsLinkifier)
.then(this.setContent.bind(this));
} else if (TimelineSelection.isRangeSelection(selectionObject)) {
this.updateSelectedRangeStats(this.selection.startTime, this.selection.endTime);
}
this.updateContents();
}
private tabSelected(event: Common.EventTarget.EventTargetEvent<UI.TabbedPane.EventData>): void {
if (!event.data.isUserGesture) {
return;
}
this.setPreferredTab(event.data.tabId);
this.updateContents();
}
private layersView(): TimelineLayersView {
if (this.lazyLayersView) {
return this.lazyLayersView;
}
this.lazyLayersView =
new TimelineLayersView(this.model.timelineModel(), this.showSnapshotInPaintProfiler.bind(this));
return this.lazyLayersView;
}
private paintProfilerView(): TimelinePaintProfilerView {
if (this.lazyPaintProfilerView) {
return this.lazyPaintProfilerView;
}
this.lazyPaintProfilerView = new TimelinePaintProfilerView(this.model.frameModel());
return this.lazyPaintProfilerView;
}
private showSnapshotInPaintProfiler(snapshot: SDK.PaintProfiler.PaintProfilerSnapshot): void {
const paintProfilerView = this.paintProfilerView();
paintProfilerView.setSnapshot(snapshot);
if (!this.tabbedPane.hasTab(Tab.PaintProfiler)) {
this.appendTab(Tab.PaintProfiler, i18nString(UIStrings.paintProfiler), paintProfilerView, true);
}
this.tabbedPane.selectTab(Tab.PaintProfiler, true);
}
private appendDetailsTabsForTraceEventAndShowDetails(
event: SDK.TracingModel.Event|TraceEngine.Types.TraceEvents.TraceEventData, content: Node): void {
this.setContent(content);
if (SDK.TracingModel.eventIsFromNewEngine(event)) {
// TODO(crbug.com/1386091): Add support for this use case in the
// new engine.
return;
}
if (event.name === TimelineModel.TimelineModel.RecordType.Paint ||
event.name === TimelineModel.TimelineModel.RecordType.RasterTask) {
this.showEventInPaintProfiler(event);
}
}
private showEventInPaintProfiler(event: SDK.TracingModel.Event): void {
const paintProfilerModel =
SDK.TargetManager.TargetManager.instance().models(SDK.PaintProfiler.PaintProfilerModel)[0];
if (!paintProfilerModel) {
return;
}
const paintProfilerView = this.paintProfilerView();
const hasProfileData = paintProfilerView.setEvent(paintProfilerModel, event);
if (!hasProfileData) {
return;
}
if (this.tabbedPane.hasTab(Tab.PaintProfiler)) {
return;
}
this.appendTab(Tab.PaintProfiler, i18nString(UIStrings.paintProfiler), paintProfilerView);
}
private updateSelectedRangeStats(startTime: number, endTime: number): void {
if (!this.model || !this.track) {
return;
}
const aggregatedStats = TimelineUIUtils.statsForTimeRange(this.track.eventsForTreeView(), startTime, endTime);
const startOffset = startTime - this.model.timelineModel().minimumRecordTime();
const endOffset = endTime - this.model.timelineModel().minimumRecordTime();
const contentHelper = new TimelineDetailsContentHelper(null, null);
contentHelper.addSection(i18nString(
UIStrings.rangeSS,
{PH1: i18n.TimeUtilities.millisToString(startOffset), PH2: i18n.TimeUtilities.millisToString(endOffset)}));
const pieChart = TimelineUIUtils.generatePieChart(aggregatedStats);
contentHelper.appendElementRow('', pieChart);
this.setContent(contentHelper.fragment);
}
}
// TODO(crbug.com/1167717): Make this a const enum again
// eslint-disable-next-line rulesdir/const_enum
export enum Tab {
Details = 'Details',
EventLog = 'EventLog',
CallTree = 'CallTree',
BottomUp = 'BottomUp',
PaintProfiler = 'PaintProfiler',
LayerViewer = 'LayerViewer',
}