UNPKG

chrome-devtools-frontend

Version:
218 lines (194 loc) 7.71 kB
// 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. /* eslint-disable rulesdir/no-imperative-dom-api */ import * as Common from '../../core/common/common.js'; import * as i18n from '../../core/i18n/i18n.js'; import * as Trace from '../../models/trace/trace.js'; import * as DataGrid from '../../ui/legacy/components/data_grid/data_grid.js'; import * as UI from '../../ui/legacy/legacy.js'; import * as VisualLogging from '../../ui/visual_logging/visual_logging.js'; import {Category, IsLong} from './TimelineFilters.js'; import type {TimelineModeViewDelegate} from './TimelinePanel.js'; import {selectionIsEvent, type TimelineSelection} from './TimelineSelection.js'; import {TimelineTreeView} from './TimelineTreeView.js'; import {TimelineUIUtils} from './TimelineUIUtils.js'; import * as Utils from './utils/utils.js'; const UIStrings = { /** *@description Text for the start time of an activity */ startTime: 'Start time', /** *@description Screen reader label for a select box that filters the Performance panel Event Log by duration. */ durationFilter: 'Duration filter', /** *@description Text for everything */ all: 'All', } as const; const str_ = i18n.i18n.registerUIStrings('panels/timeline/EventsTimelineTreeView.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); export class EventsTimelineTreeView extends TimelineTreeView { private readonly filtersControl: Filters; private readonly delegate: TimelineModeViewDelegate; private currentTree!: Trace.Extras.TraceTree.Node; constructor(delegate: TimelineModeViewDelegate) { super(); this.element.setAttribute('jslog', `${VisualLogging.pane('event-log').track({resize: true})}`); this.filtersControl = new Filters(); this.filtersControl.addEventListener(Events.FILTER_CHANGED, this.onFilterChanged, this); this.init(); this.delegate = delegate; this.dataGrid.markColumnAsSortedBy('start-time', DataGrid.DataGrid.Order.Ascending); this.splitWidget.showBoth(); } override filters(): Trace.Extras.TraceFilter.TraceFilter[] { return [...super.filters(), ...this.filtersControl.filters()]; } override updateContents(selection: TimelineSelection): void { super.updateContents(selection); if (selectionIsEvent(selection)) { this.selectEvent(selection.event, true); } } override buildTree(): Trace.Extras.TraceTree.Node { this.currentTree = this.buildTopDownTree(true, null); return this.currentTree; } private onFilterChanged(): void { const lastSelectedNode = this.lastSelectedNode(); const selectedEvent = lastSelectedNode?.event; this.refreshTree(); if (selectedEvent) { this.selectEvent(selectedEvent, false); } } private findNodeWithEvent(event: Trace.Types.Events.Event): Trace.Extras.TraceTree.Node|null { if (event.name === Trace.Types.Events.Name.RUN_TASK) { // No node is ever created for the top level RunTask event, so // bail out preemptively return null; } const iterators = [this.currentTree.children().values()]; while (iterators.length) { const {done, value: child} = iterators[iterators.length - 1].next(); if (done) { iterators.pop(); continue; } if (child.event === event) { return child; } iterators.push(child.children().values()); } return null; } private selectEvent(event: Trace.Types.Events.Event, expand?: boolean): void { const node = this.findNodeWithEvent(event); if (!node) { return; } this.selectProfileNode(node, false); if (expand) { const dataGridNode = this.dataGridNodeForTreeNode(node); if (dataGridNode) { dataGridNode.expand(); } } } override populateColumns(columns: DataGrid.DataGrid.ColumnDescriptor[]): void { columns.push(({ id: 'start-time', title: i18nString(UIStrings.startTime), width: '80px', fixedWidth: true, sortable: true, } as DataGrid.DataGrid.ColumnDescriptor)); super.populateColumns(columns); columns.filter(c => c.fixedWidth).forEach(c => { c.width = '80px'; }); } override populateToolbar(toolbar: UI.Toolbar.Toolbar): void { super.populateToolbar(toolbar); this.filtersControl.populateToolbar(toolbar); } override showDetailsForNode(node: Trace.Extras.TraceTree.Node): boolean { const parsedTrace = this.parsedTrace(); if (!parsedTrace) { return false; } const traceEvent = node.event; if (!traceEvent) { return false; } void TimelineUIUtils.buildTraceEventDetails(parsedTrace, traceEvent, this.linkifier, false, null) .then(fragment => this.detailsView.element.appendChild(fragment)); return true; } override onHover(node: Trace.Extras.TraceTree.Node|null): void { this.delegate.highlightEvent(node?.event ?? null); } } export class Filters extends Common.ObjectWrapper.ObjectWrapper<EventTypes> { private readonly categoryFilter: Category; private readonly durationFilter: IsLong; private readonly filtersInternal: Array<IsLong|Category>; constructor() { super(); this.categoryFilter = new Category(); this.durationFilter = new IsLong(); this.filtersInternal = [this.categoryFilter, this.durationFilter]; } filters(): Trace.Extras.TraceFilter.TraceFilter[] { return this.filtersInternal; } populateToolbar(toolbar: UI.Toolbar.Toolbar): void { const durationFilterUI = new UI.Toolbar.ToolbarComboBox( durationFilterChanged.bind(this), i18nString(UIStrings.durationFilter), undefined, 'duration'); for (const durationMs of Filters.durationFilterPresetsMs) { durationFilterUI.addOption(durationFilterUI.createOption( durationMs ? `≥ ${i18n.TimeUtilities.millisToString(durationMs)}` : i18nString(UIStrings.all), String(durationMs))); } toolbar.appendToolbarItem(durationFilterUI); const categoryFiltersUI = new Map<string, UI.Toolbar.ToolbarCheckbox>(); const categories = Utils.EntryStyles.getCategoryStyles(); for (const categoryName in categories) { const category = categories[categoryName as Utils.EntryStyles.EventCategory]; if (!category.visible) { continue; } const checkbox = new UI.Toolbar.ToolbarCheckbox( category.title, undefined, categoriesFilterChanged.bind(this, categoryName as Utils.EntryStyles.EventCategory), categoryName); checkbox.setChecked(true); categoryFiltersUI.set(category.name, checkbox); toolbar.appendToolbarItem(checkbox); } function durationFilterChanged(this: Filters): void { const duration = (durationFilterUI.selectedOption() as HTMLOptionElement).value; const minimumRecordDuration = parseInt(duration, 10); this.durationFilter.setMinimumRecordDuration(Trace.Types.Timing.Milli(minimumRecordDuration)); this.notifyFiltersChanged(); } function categoriesFilterChanged(this: Filters, name: Utils.EntryStyles.EventCategory): void { const categories = Utils.EntryStyles.getCategoryStyles(); const checkBox = categoryFiltersUI.get(name); categories[name].hidden = !checkBox?.checked(); this.notifyFiltersChanged(); } } private notifyFiltersChanged(): void { this.dispatchEventToListeners(Events.FILTER_CHANGED); } private static readonly durationFilterPresetsMs = [0, 1, 15]; } const enum Events { FILTER_CHANGED = 'FilterChanged', } interface EventTypes { [Events.FILTER_CHANGED]: void; }