| // Copyright 2016 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. | 
 |  | 
 | /** | 
 |  * @implements {PerfUI.FlameChartDelegate} | 
 |  * @implements {UI.Searchable} | 
 |  * @unrestricted | 
 |  */ | 
 | Timeline.TimelineFlameChartView = class extends UI.VBox { | 
 |   /** | 
 |    * @param {!Timeline.TimelineModeViewDelegate} delegate | 
 |    */ | 
 |   constructor(delegate) { | 
 |     super(); | 
 |     this.element.classList.add('timeline-flamechart'); | 
 |     this._delegate = delegate; | 
 |     /** @type {?Timeline.PerformanceModel} */ | 
 |     this._model = null; | 
 |     /** @type {!Array<number>|undefined} */ | 
 |     this._searchResults; | 
 |     /** @type {!Array<!Common.EventTarget.EventDescriptor>} */ | 
 |     this._eventListeners = []; | 
 |  | 
 |     this._showMemoryGraphSetting = Common.settings.createSetting('timelineShowMemory', false); | 
 |  | 
 |     // Create main and network flamecharts. | 
 |     this._networkSplitWidget = new UI.SplitWidget(false, false, 'timelineFlamechartMainView', 150); | 
 |  | 
 |     const mainViewGroupExpansionSetting = Common.settings.createSetting('timelineFlamechartMainViewGroupExpansion', {}); | 
 |     this._mainDataProvider = new Timeline.TimelineFlameChartDataProvider(); | 
 |     this._mainDataProvider.addEventListener( | 
 |         Timeline.TimelineFlameChartDataProvider.Events.DataChanged, () => this._mainFlameChart.scheduleUpdate()); | 
 |     this._mainFlameChart = new PerfUI.FlameChart(this._mainDataProvider, this, mainViewGroupExpansionSetting); | 
 |     this._mainFlameChart.alwaysShowVerticalScroll(); | 
 |     this._mainFlameChart.enableRuler(false); | 
 |  | 
 |     this._networkFlameChartGroupExpansionSetting = | 
 |         Common.settings.createSetting('timelineFlamechartNetworkViewGroupExpansion', {}); | 
 |     this._networkDataProvider = new Timeline.TimelineFlameChartNetworkDataProvider(); | 
 |     this._networkFlameChart = new PerfUI.FlameChart( | 
 |         this._networkDataProvider, this, this._networkFlameChartGroupExpansionSetting); | 
 |     this._networkFlameChart.alwaysShowVerticalScroll(); | 
 |     this._networkFlameChart.disableRangeSelection(); | 
 |  | 
 |     this._networkPane = new UI.VBox(); | 
 |     this._networkPane.setMinimumSize(23, 23); | 
 |     this._networkFlameChart.show(this._networkPane.element); | 
 |     this._splitResizer = this._networkPane.element.createChild('div', 'timeline-flamechart-resizer'); | 
 |     this._networkSplitWidget.hideDefaultResizer(true); | 
 |     this._networkSplitWidget.installResizer(this._splitResizer); | 
 |  | 
 |     this._networkSplitWidget.setMainWidget(this._mainFlameChart); | 
 |     this._networkSplitWidget.setSidebarWidget(this._networkPane); | 
 |  | 
 |     // Create counters chart splitter. | 
 |     this._chartSplitWidget = new UI.SplitWidget(false, true, 'timelineCountersSplitViewState'); | 
 |     this._countersView = new Timeline.CountersGraph(this._delegate); | 
 |     this._chartSplitWidget.setMainWidget(this._networkSplitWidget); | 
 |     this._chartSplitWidget.setSidebarWidget(this._countersView); | 
 |     this._chartSplitWidget.hideDefaultResizer(); | 
 |     this._chartSplitWidget.installResizer(/** @type {!Element} */ (this._countersView.resizerElement())); | 
 |     this._updateCountersGraphToggle(); | 
 |  | 
 |     // Create top level properties splitter. | 
 |     this._detailsSplitWidget = new UI.SplitWidget(false, true, 'timelinePanelDetailsSplitViewState'); | 
 |     this._detailsSplitWidget.element.classList.add('timeline-details-split'); | 
 |     this._detailsView = new Timeline.TimelineDetailsView(delegate); | 
 |     this._detailsSplitWidget.installResizer(this._detailsView.headerElement()); | 
 |     this._detailsSplitWidget.setMainWidget(this._chartSplitWidget); | 
 |     this._detailsSplitWidget.setSidebarWidget(this._detailsView); | 
 |     this._detailsSplitWidget.show(this.element); | 
 |  | 
 |     this._onMainEntrySelected = this._onEntrySelected.bind(this, this._mainDataProvider); | 
 |     this._onNetworkEntrySelected = this._onEntrySelected.bind(this, this._networkDataProvider); | 
 |     this._mainFlameChart.addEventListener(PerfUI.FlameChart.Events.EntrySelected, this._onMainEntrySelected, this); | 
 |     this._mainFlameChart.addEventListener(PerfUI.FlameChart.Events.EntryInvoked, this._onMainEntrySelected, this); | 
 |     this._networkFlameChart.addEventListener(PerfUI.FlameChart.Events.EntrySelected, this._onNetworkEntrySelected, this); | 
 |     this._networkFlameChart.addEventListener(PerfUI.FlameChart.Events.EntryInvoked, this._onNetworkEntrySelected, this); | 
 |     this._mainFlameChart.addEventListener(PerfUI.FlameChart.Events.EntryHighlighted, this._onEntryHighlighted, this); | 
 |     this._nextExtensionIndex = 0; | 
 |  | 
 |     this._boundRefresh = this._refresh.bind(this); | 
 |     this._selectedTrack = null; | 
 |  | 
 |     this._mainDataProvider.setEventColorMapping(Timeline.TimelineUIUtils.eventColor); | 
 |     this._groupBySetting = | 
 |         Common.settings.createSetting('timelineTreeGroupBy', Timeline.AggregatedTimelineTreeView.GroupBy.None); | 
 |     this._groupBySetting.addChangeListener(this._updateColorMapper, this); | 
 |     this._updateColorMapper(); | 
 |   } | 
 |  | 
 |   _updateColorMapper() { | 
 |     /** @type {!Map<string, string>} */ | 
 |     this._urlToColorCache = new Map(); | 
 |     if (!this._model) { | 
 |       return; | 
 |     } | 
 |     this._mainDataProvider.setEventColorMapping(Timeline.TimelineUIUtils.eventColor); | 
 |     this._mainFlameChart.update(); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!Common.Event} event | 
 |    */ | 
 |   _onWindowChanged(event) { | 
 |     const window = /** @type {!Timeline.PerformanceModel.Window} */ (event.data.window); | 
 |     const animate = !!event.data.animate; | 
 |     this._mainFlameChart.setWindowTimes(window.left, window.right, animate); | 
 |     this._networkFlameChart.setWindowTimes(window.left, window.right, animate); | 
 |     this._networkDataProvider.setWindowTimes(window.left, window.right); | 
 |     this._updateSearchResults(false, false); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @override | 
 |    * @param {number} windowStartTime | 
 |    * @param {number} windowEndTime | 
 |    * @param {boolean} animate | 
 |    */ | 
 |   windowChanged(windowStartTime, windowEndTime, animate) { | 
 |     this._model.setWindow({left: windowStartTime, right: windowEndTime}, animate); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @override | 
 |    * @param {number} startTime | 
 |    * @param {number} endTime | 
 |    */ | 
 |   updateRangeSelection(startTime, endTime) { | 
 |     this._delegate.select(Timeline.TimelineSelection.fromRange(startTime, endTime)); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @override | 
 |    * @param {!PerfUI.FlameChart} flameChart | 
 |    * @param {?PerfUI.FlameChart.Group} group | 
 |    */ | 
 |   updateSelectedGroup(flameChart, group) { | 
 |     if (flameChart !== this._mainFlameChart) { | 
 |       return; | 
 |     } | 
 |     const track = group ? this._mainDataProvider.groupTrack(group) : null; | 
 |     this._selectedTrack = track; | 
 |     this._updateTrack(); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {?Timeline.PerformanceModel} model | 
 |    */ | 
 |   setModel(model) { | 
 |     if (model === this._model) { | 
 |       return; | 
 |     } | 
 |     Common.EventTarget.removeEventListeners(this._eventListeners); | 
 |     this._model = model; | 
 |     this._selectedTrack = null; | 
 |     this._mainDataProvider.setModel(this._model); | 
 |     this._networkDataProvider.setModel(this._model); | 
 |     if (this._model) { | 
 |       this._eventListeners = [ | 
 |         this._model.addEventListener(Timeline.PerformanceModel.Events.WindowChanged, this._onWindowChanged, this), | 
 |         this._model.addEventListener( | 
 |             Timeline.PerformanceModel.Events.ExtensionDataAdded, this._appendExtensionData, this) | 
 |       ]; | 
 |       const window = model.window(); | 
 |       this._mainFlameChart.setWindowTimes(window.left, window.right); | 
 |       this._networkFlameChart.setWindowTimes(window.left, window.right); | 
 |       this._networkDataProvider.setWindowTimes(window.left, window.right); | 
 |       this._updateSearchResults(false, false); | 
 |     } | 
 |     this._updateColorMapper(); | 
 |     this._updateTrack(); | 
 |     this._nextExtensionIndex = 0; | 
 |     this._appendExtensionData(); | 
 |     this._refresh(); | 
 |   } | 
 |  | 
 |   _updateTrack() { | 
 |     this._countersView.setModel(this._model, this._selectedTrack); | 
 |     this._detailsView.setModel(this._model, this._selectedTrack); | 
 |   } | 
 |  | 
 |   _refresh() { | 
 |     if (this._networkDataProvider.isEmpty()) { | 
 |       this._mainFlameChart.enableRuler(true); | 
 |       this._networkSplitWidget.hideSidebar(); | 
 |     } else { | 
 |       this._mainFlameChart.enableRuler(false); | 
 |       this._networkSplitWidget.showBoth(); | 
 |       this.resizeToPreferredHeights(); | 
 |     } | 
 |     this._mainFlameChart.reset(); | 
 |     this._networkFlameChart.reset(); | 
 |     this._updateSearchResults(false, false); | 
 |   } | 
 |  | 
 |   _appendExtensionData() { | 
 |     if (!this._model) { | 
 |       return; | 
 |     } | 
 |     const extensions = this._model.extensionInfo(); | 
 |     while (this._nextExtensionIndex < extensions.length) { | 
 |       this._mainDataProvider.appendExtensionEvents(extensions[this._nextExtensionIndex++]); | 
 |     } | 
 |     this._mainFlameChart.scheduleUpdate(); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!Common.Event} commonEvent | 
 |    */ | 
 |   _onEntryHighlighted(commonEvent) { | 
 |     SDK.OverlayModel.hideDOMNodeHighlight(); | 
 |     const entryIndex = /** @type {number} */ (commonEvent.data); | 
 |     const event = this._mainDataProvider.eventByIndex(entryIndex); | 
 |     if (!event) { | 
 |       return; | 
 |     } | 
 |     const target = this._model && this._model.timelineModel().targetByEvent(event); | 
 |     if (!target) { | 
 |       return; | 
 |     } | 
 |     const timelineData = TimelineModel.TimelineData.forEvent(event); | 
 |     const backendNodeId = timelineData.backendNodeId; | 
 |     if (!backendNodeId) { | 
 |       return; | 
 |     } | 
 |     new SDK.DeferredDOMNode(target, backendNodeId).highlight(); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {?SDK.TracingModel.Event} event | 
 |    */ | 
 |   highlightEvent(event) { | 
 |     const entryIndex = | 
 |         event ? this._mainDataProvider.entryIndexForSelection(Timeline.TimelineSelection.fromTraceEvent(event)) : -1; | 
 |     if (entryIndex >= 0) { | 
 |       this._mainFlameChart.highlightEntry(entryIndex); | 
 |     } else { | 
 |       this._mainFlameChart.hideHighlight(); | 
 |     } | 
 |   } | 
 |  | 
 |   /** | 
 |    * @override | 
 |    */ | 
 |   willHide() { | 
 |     this._networkFlameChartGroupExpansionSetting.removeChangeListener(this.resizeToPreferredHeights, this); | 
 |     this._showMemoryGraphSetting.removeChangeListener(this._updateCountersGraphToggle, this); | 
 |     Bindings.blackboxManager.removeChangeListener(this._boundRefresh); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @override | 
 |    */ | 
 |   wasShown() { | 
 |     this._networkFlameChartGroupExpansionSetting.addChangeListener(this.resizeToPreferredHeights, this); | 
 |     this._showMemoryGraphSetting.addChangeListener(this._updateCountersGraphToggle, this); | 
 |     Bindings.blackboxManager.addChangeListener(this._boundRefresh); | 
 |     if (this._needsResizeToPreferredHeights) { | 
 |       this.resizeToPreferredHeights(); | 
 |     } | 
 |     this._mainFlameChart.scheduleUpdate(); | 
 |     this._networkFlameChart.scheduleUpdate(); | 
 |   } | 
 |  | 
 |   _updateCountersGraphToggle() { | 
 |     if (this._showMemoryGraphSetting.get()) { | 
 |       this._chartSplitWidget.showBoth(); | 
 |     } else { | 
 |       this._chartSplitWidget.hideSidebar(); | 
 |     } | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {?Timeline.TimelineSelection} selection | 
 |    */ | 
 |   setSelection(selection) { | 
 |     let index = this._mainDataProvider.entryIndexForSelection(selection); | 
 |     this._mainFlameChart.setSelectedEntry(index); | 
 |     index = this._networkDataProvider.entryIndexForSelection(selection); | 
 |     this._networkFlameChart.setSelectedEntry(index); | 
 |     if (this._detailsView) { | 
 |       this._detailsView.setSelection(selection); | 
 |     } | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!PerfUI.FlameChartDataProvider} dataProvider | 
 |    * @param {!Common.Event} event | 
 |    */ | 
 |   _onEntrySelected(dataProvider, event) { | 
 |     const entryIndex = /** @type{number} */ (event.data); | 
 |     if (Root.Runtime.experiments.isEnabled('timelineEventInitiators') && dataProvider === this._mainDataProvider) { | 
 |       if (this._mainDataProvider.buildFlowForInitiator(entryIndex)) { | 
 |         this._mainFlameChart.scheduleUpdate(); | 
 |       } | 
 |     } | 
 |     this._delegate.select(dataProvider.createSelection(entryIndex)); | 
 |   } | 
 |  | 
 |   resizeToPreferredHeights() { | 
 |     if (!this.isShowing()) { | 
 |       this._needsResizeToPreferredHeights = true; | 
 |       return; | 
 |     } | 
 |     this._needsResizeToPreferredHeights = false; | 
 |     this._networkPane.element.classList.toggle( | 
 |         'timeline-network-resizer-disabled', !this._networkDataProvider.isExpanded()); | 
 |     this._networkSplitWidget.setSidebarSize( | 
 |         this._networkDataProvider.preferredHeight() + this._splitResizer.clientHeight + PerfUI.FlameChart.HeaderHeight + | 
 |         2); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!UI.SearchableView} searchableView | 
 |    */ | 
 |   setSearchableView(searchableView) { | 
 |     this._searchableView = searchableView; | 
 |   } | 
 |  | 
 |   // UI.Searchable implementation | 
 |  | 
 |   /** | 
 |    * @override | 
 |    */ | 
 |   jumpToNextSearchResult() { | 
 |     if (!this._searchResults || !this._searchResults.length) { | 
 |       return; | 
 |     } | 
 |     const index = typeof this._selectedSearchResult !== 'undefined' ? | 
 |         this._searchResults.indexOf(this._selectedSearchResult) : | 
 |         -1; | 
 |     this._selectSearchResult(mod(index + 1, this._searchResults.length)); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @override | 
 |    */ | 
 |   jumpToPreviousSearchResult() { | 
 |     if (!this._searchResults || !this._searchResults.length) { | 
 |       return; | 
 |     } | 
 |     const index = | 
 |         typeof this._selectedSearchResult !== 'undefined' ? this._searchResults.indexOf(this._selectedSearchResult) : 0; | 
 |     this._selectSearchResult(mod(index - 1, this._searchResults.length)); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @override | 
 |    * @return {boolean} | 
 |    */ | 
 |   supportsCaseSensitiveSearch() { | 
 |     return true; | 
 |   } | 
 |  | 
 |   /** | 
 |    * @override | 
 |    * @return {boolean} | 
 |    */ | 
 |   supportsRegexSearch() { | 
 |     return true; | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {number} index | 
 |    */ | 
 |   _selectSearchResult(index) { | 
 |     this._searchableView.updateCurrentMatchIndex(index); | 
 |     this._selectedSearchResult = this._searchResults[index]; | 
 |     this._delegate.select(this._mainDataProvider.createSelection(this._selectedSearchResult)); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {boolean} shouldJump | 
 |    * @param {boolean=} jumpBackwards | 
 |    */ | 
 |   _updateSearchResults(shouldJump, jumpBackwards) { | 
 |     const oldSelectedSearchResult = this._selectedSearchResult; | 
 |     delete this._selectedSearchResult; | 
 |     this._searchResults = []; | 
 |     if (!this._searchRegex || !this._model) { | 
 |       return; | 
 |     } | 
 |     const regExpFilter = new Timeline.TimelineFilters.RegExp(this._searchRegex); | 
 |     const window = this._model.window(); | 
 |     this._searchResults = this._mainDataProvider.search(window.left, window.right, regExpFilter); | 
 |     this._searchableView.updateSearchMatchesCount(this._searchResults.length); | 
 |     if (!shouldJump || !this._searchResults.length) { | 
 |       return; | 
 |     } | 
 |     let selectedIndex = this._searchResults.indexOf(oldSelectedSearchResult); | 
 |     if (selectedIndex === -1) { | 
 |       selectedIndex = jumpBackwards ? this._searchResults.length - 1 : 0; | 
 |     } | 
 |     this._selectSearchResult(selectedIndex); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @override | 
 |    */ | 
 |   searchCanceled() { | 
 |     if (typeof this._selectedSearchResult !== 'undefined') { | 
 |       this._delegate.select(null); | 
 |     } | 
 |     delete this._searchResults; | 
 |     delete this._selectedSearchResult; | 
 |     delete this._searchRegex; | 
 |   } | 
 |  | 
 |   /** | 
 |    * @override | 
 |    * @param {!UI.SearchableView.SearchConfig} searchConfig | 
 |    * @param {boolean} shouldJump | 
 |    * @param {boolean=} jumpBackwards | 
 |    */ | 
 |   performSearch(searchConfig, shouldJump, jumpBackwards) { | 
 |     this._searchRegex = searchConfig.toSearchRegex(); | 
 |     this._updateSearchResults(shouldJump, jumpBackwards); | 
 |   } | 
 | }; | 
 |  | 
 | /** | 
 |  * @unrestricted | 
 |  */ | 
 | Timeline.TimelineFlameChartView.Selection = class { | 
 |   /** | 
 |    * @param {!Timeline.TimelineSelection} selection | 
 |    * @param {number} entryIndex | 
 |    */ | 
 |   constructor(selection, entryIndex) { | 
 |     this.timelineSelection = selection; | 
 |     this.entryIndex = entryIndex; | 
 |   } | 
 | }; | 
 |  | 
 | Timeline.FlameChartStyle = { | 
 |   textColor: '#333' | 
 | }; | 
 |  | 
 | /** | 
 |  * @implements {PerfUI.FlameChartMarker} | 
 |  * @unrestricted | 
 |  */ | 
 | Timeline.TimelineFlameChartMarker = class { | 
 |   /** | 
 |    * @param {number} startTime | 
 |    * @param {number} startOffset | 
 |    * @param {!Timeline.TimelineMarkerStyle} style | 
 |    */ | 
 |   constructor(startTime, startOffset, style) { | 
 |     this._startTime = startTime; | 
 |     this._startOffset = startOffset; | 
 |     this._style = style; | 
 |   } | 
 |  | 
 |   /** | 
 |    * @override | 
 |    * @return {number} | 
 |    */ | 
 |   startTime() { | 
 |     return this._startTime; | 
 |   } | 
 |  | 
 |   /** | 
 |    * @override | 
 |    * @return {string} | 
 |    */ | 
 |   color() { | 
 |     return this._style.color; | 
 |   } | 
 |  | 
 |   /** | 
 |    * @override | 
 |    * @return {?string} | 
 |    */ | 
 |   title() { | 
 |     if (this._style.lowPriority) { | 
 |       return null; | 
 |     } | 
 |     const startTime = Number.millisToString(this._startOffset); | 
 |     return ls`${this._style.title} at ${startTime}`; | 
 |   } | 
 |  | 
 |   /** | 
 |    * @override | 
 |    * @param {!CanvasRenderingContext2D} context | 
 |    * @param {number} x | 
 |    * @param {number} height | 
 |    * @param {number} pixelsPerMillisecond | 
 |    */ | 
 |   draw(context, x, height, pixelsPerMillisecond) { | 
 |     const lowPriorityVisibilityThresholdInPixelsPerMs = 4; | 
 |  | 
 |     if (this._style.lowPriority && pixelsPerMillisecond < lowPriorityVisibilityThresholdInPixelsPerMs) { | 
 |       return; | 
 |     } | 
 |  | 
 |     context.save(); | 
 |     if (this._style.tall) { | 
 |       context.strokeStyle = this._style.color; | 
 |       context.lineWidth = this._style.lineWidth; | 
 |       context.translate(this._style.lineWidth < 1 || (this._style.lineWidth & 1) ? 0.5 : 0, 0.5); | 
 |       context.beginPath(); | 
 |       context.moveTo(x, 0); | 
 |       context.setLineDash(this._style.dashStyle); | 
 |       context.lineTo(x, context.canvas.height); | 
 |       context.stroke(); | 
 |     } | 
 |     context.restore(); | 
 |   } | 
 | }; | 
 |  | 
 | /** @enum {string} */ | 
 | Timeline.TimelineFlameChartView._ColorBy = { | 
 |   URL: 'URL', | 
 | }; |