| /* |
| * Copyright (C) 2012 Google Inc. All rights reserved. |
| * Copyright (C) 2012 Intel Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| importScript("MemoryStatistics.js"); |
| importScript("DOMCountersGraph.js"); |
| importScript("TimelineModel.js"); |
| importScript("TimelineOverviewPane.js"); |
| importScript("TimelinePresentationModel.js"); |
| importScript("TimelineFrameController.js"); |
| |
| /** |
| * @constructor |
| * @extends {WebInspector.Panel} |
| */ |
| WebInspector.TimelinePanel = function() |
| { |
| WebInspector.Panel.call(this, "timeline"); |
| this.registerRequiredCSS("timelinePanel.css"); |
| this.registerRequiredCSS("filter.css"); |
| this.element.addStyleClass("vbox"); |
| |
| this._model = new WebInspector.TimelineModel(); |
| this._presentationModel = new WebInspector.TimelinePresentationModel(); |
| |
| this._overviewModeSetting = WebInspector.settings.createSetting("timelineOverviewMode", WebInspector.TimelineOverviewPane.Mode.Events); |
| this._glueRecordsSetting = WebInspector.settings.createSetting("timelineGlueRecords", false); |
| |
| this._panelStatusBarElement = this.element.createChild("div", "panel-status-bar"); |
| |
| this._createFilters(); |
| |
| this._overviewPane = new WebInspector.TimelineOverviewPane(this._model); |
| this._overviewPane.addEventListener(WebInspector.TimelineOverviewPane.Events.WindowChanged, this._invalidateAndScheduleRefresh.bind(this, false, true)); |
| this._overviewPane.addEventListener(WebInspector.TimelineOverviewPane.Events.ModeChanged, this._overviewModeChanged, this); |
| this._overviewPane.show(this.element); |
| |
| this.element.addEventListener("contextmenu", this._contextMenu.bind(this), false); |
| |
| this.createSidebarViewWithTree(); |
| |
| this._containerElement = this.splitView.element; |
| this._containerElement.tabIndex = 0; |
| this._containerElement.id = "timeline-container"; |
| this._containerElement.removeStyleClass("fill"); |
| this._containerElement.addEventListener("scroll", this._onScroll.bind(this), false); |
| |
| this._timelineMemorySplitter = this.element.createChild("div", "hidden"); |
| this._timelineMemorySplitter.id = "timeline-memory-splitter"; |
| WebInspector.installDragHandle(this._timelineMemorySplitter, this._startSplitterDragging.bind(this), this._splitterDragging.bind(this), this._endSplitterDragging.bind(this), "ns-resize"); |
| this._includeDomCounters = false; |
| this._memoryStatistics = new WebInspector.DOMCountersGraph(this, this._model, this.splitView.sidebarWidth()); |
| this._includeDomCounters = true; |
| WebInspector.settings.memoryCounterGraphsHeight = WebInspector.settings.createSetting("memoryCounterGraphsHeight", 150); |
| |
| var itemsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RECORDS"), {}, true); |
| this.sidebarTree.appendChild(itemsTreeElement); |
| this.sidebarTree.setFocusable(false); |
| |
| this._sidebarListElement = document.createElement("div"); |
| this.sidebarElement.appendChild(this._sidebarListElement); |
| |
| this._containerContentElement = this.splitView.mainElement; |
| this._containerContentElement.id = "resources-container-content"; |
| |
| this._timelineGrid = new WebInspector.TimelineGrid(); |
| this._itemsGraphsElement = this._timelineGrid.itemsGraphsElement; |
| this._itemsGraphsElement.id = "timeline-graphs"; |
| this._containerContentElement.appendChild(this._timelineGrid.element); |
| this._timelineGrid.gridHeaderElement.id = "timeline-grid-header"; |
| this._timelineGrid.gridHeaderElement.addStyleClass("fill"); |
| this._memoryStatistics.setMainTimelineGrid(this._timelineGrid); |
| this._containerContentElement.appendChild(this._timelineGrid.gridHeaderElement); |
| |
| this._topGapElement = document.createElement("div"); |
| this._topGapElement.className = "timeline-gap"; |
| this._itemsGraphsElement.appendChild(this._topGapElement); |
| |
| this._graphRowsElement = document.createElement("div"); |
| this._itemsGraphsElement.appendChild(this._graphRowsElement); |
| |
| this._bottomGapElement = document.createElement("div"); |
| this._bottomGapElement.className = "timeline-gap"; |
| this._itemsGraphsElement.appendChild(this._bottomGapElement); |
| |
| this._expandElements = document.createElement("div"); |
| this._expandElements.id = "orphan-expand-elements"; |
| this._itemsGraphsElement.appendChild(this._expandElements); |
| |
| this._calculator = new WebInspector.TimelineCalculator(this._model); |
| this._createStatusBarItems(); |
| |
| this._frameMode = false; |
| this._boundariesAreValid = true; |
| this._scrollTop = 0; |
| |
| this._popoverHelper = new WebInspector.PopoverHelper(this.element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this)); |
| this.element.addEventListener("mousemove", this._mouseMove.bind(this), false); |
| this.element.addEventListener("mouseout", this._mouseOut.bind(this), false); |
| |
| // Short events filter is disabled by default. |
| this._durationFilter = new WebInspector.TimelineIsLongFilter(); |
| |
| this._expandOffset = 15; |
| |
| this._headerLineCount = 1; |
| this._adjustHeaderHeight(); |
| |
| this._mainThreadTasks = /** @type {!Array.<{startTime: number, endTime: number}>} */ ([]); |
| this._cpuBarsElement = this._timelineGrid.gridHeaderElement.createChild("div", "timeline-cpu-bars"); |
| this._mainThreadMonitoringEnabled = WebInspector.settings.showCpuOnTimelineRuler.get(); |
| WebInspector.settings.showCpuOnTimelineRuler.addChangeListener(this._showCpuOnTimelineRulerChanged, this); |
| |
| this._createFileSelector(); |
| |
| this._model.addEventListener(WebInspector.TimelineModel.Events.RecordAdded, this._onTimelineEventRecorded, this); |
| this._model.addEventListener(WebInspector.TimelineModel.Events.RecordsCleared, this._onRecordsCleared, this); |
| this._model.addEventListener(WebInspector.TimelineModel.Events.RecordingStarted, this._onRecordingStarted, this); |
| this._model.addEventListener(WebInspector.TimelineModel.Events.RecordingStopped, this._onRecordingStopped, this); |
| |
| this._registerShortcuts(); |
| |
| this._allRecordsCount = 0; |
| |
| this._presentationModel.addFilter(new WebInspector.TimelineWindowFilter(this._overviewPane)); |
| this._presentationModel.addFilter(new WebInspector.TimelineCategoryFilter()); |
| this._presentationModel.addFilter(this._durationFilter); |
| } |
| |
| // Define row and header height, should be in sync with styles for timeline graphs. |
| WebInspector.TimelinePanel.rowHeight = 18; |
| WebInspector.TimelinePanel.headerHeight = 19; |
| |
| WebInspector.TimelinePanel.durationFilterPresetsMs = [0, 1, 15]; |
| |
| WebInspector.TimelinePanel.prototype = { |
| _showCpuOnTimelineRulerChanged: function() |
| { |
| var mainThreadMonitoringEnabled = WebInspector.settings.showCpuOnTimelineRuler.get(); |
| if (this._mainThreadMonitoringEnabled !== mainThreadMonitoringEnabled) { |
| this._mainThreadMonitoringEnabled = mainThreadMonitoringEnabled; |
| this._refreshMainThreadBars(); |
| } |
| }, |
| |
| /** |
| * @param {Event} event |
| * @return {boolean} |
| */ |
| _startSplitterDragging: function(event) |
| { |
| this._dragOffset = this.element.offsetHeight - this._timelineMemorySplitter.offsetTop - 2 + event.pageY; |
| return true; |
| }, |
| |
| /** |
| * @param {Event} event |
| */ |
| _splitterDragging: function(event) |
| { |
| var height = this._dragOffset - event.pageY; |
| this._setMemoryCountersHeight(height); |
| this._resize(this.splitView.sidebarWidth()); |
| event.preventDefault(); |
| }, |
| |
| /** |
| * @param {Event} event |
| */ |
| _endSplitterDragging: function(event) |
| { |
| delete this._dragOffset; |
| this._memoryStatistics.show(); |
| WebInspector.settings.memoryCounterGraphsHeight.set(this._memoryStatistics.height()); |
| }, |
| |
| _setMemoryCountersHeight: function(height) |
| { |
| const overviewHeight = this._containerElement.offsetTop - 20; |
| const sectionMinHeight = 20; |
| height = Number.constrain(height, sectionMinHeight, this.element.offsetHeight - sectionMinHeight - overviewHeight); |
| this._timelineMemorySplitter.style.bottom = (height - 3) + "px"; |
| this._memoryStatistics.setHeight(height); |
| }, |
| |
| get calculator() |
| { |
| return this._calculator; |
| }, |
| |
| defaultFocusedElement: function() |
| { |
| return this.element; |
| }, |
| |
| _createFilters: function() |
| { |
| this._filterBar = new WebInspector.FilterBar(); |
| this._filtersContainer = this.element.createChild("div", "timeline-filters-header hidden"); |
| this._filtersContainer.appendChild(this._filterBar.filtersElement()); |
| this._filterBar.addEventListener(WebInspector.FilterBar.Events.FiltersToggled, this._onFiltersToggled, this); |
| |
| this._textFilter = new WebInspector.TextFilterUI(); |
| this._textFilter.addEventListener(WebInspector.FilterUI.Events.FilterChanged, this._textFilterChanged, this); |
| this._filterBar.addFilter(this._textFilter); |
| |
| var durationOptions = []; |
| for (var presetIndex = 0; presetIndex < WebInspector.TimelinePanel.durationFilterPresetsMs.length; ++presetIndex) { |
| var durationMs = WebInspector.TimelinePanel.durationFilterPresetsMs[presetIndex]; |
| var durationOption = {}; |
| if (!durationMs) { |
| durationOption.label = WebInspector.UIString("All"); |
| durationOption.title = WebInspector.UIString("Show all records"); |
| } else { |
| durationOption.label = WebInspector.UIString("\u2265 %dms", durationMs); |
| durationOption.title = WebInspector.UIString("Hide records shorter than %dms", durationMs); |
| } |
| durationOption.value = durationMs; |
| durationOptions.push(durationOption); |
| } |
| this._durationComboBoxFilter = new WebInspector.ComboBoxFilterUI(durationOptions); |
| this._durationComboBoxFilter.addEventListener(WebInspector.FilterUI.Events.FilterChanged, this._durationFilterChanged, this); |
| this._filterBar.addFilter(this._durationComboBoxFilter); |
| |
| this._categoryFilters = {}; |
| var categoryTypes = []; |
| var categories = WebInspector.TimelinePresentationModel.categories(); |
| for (var categoryName in categories) { |
| var category = categories[categoryName]; |
| if (category.overviewStripGroupIndex < 0) |
| continue; |
| var filter = new WebInspector.CheckboxFilterUI(category.name, category.title, false); |
| filter.addEventListener(WebInspector.FilterUI.Events.FilterChanged, this._categoriesFilterChanged.bind(this, category.name), this); |
| this._filterBar.addFilter(filter); |
| this._categoryFilters[category.name] = filter; |
| } |
| }, |
| |
| _createStatusBarItems: function() |
| { |
| this._statusBarButtons = /** @type {!Array.<!WebInspector.StatusBarItem>} */ ([]); |
| |
| this.toggleTimelineButton = new WebInspector.StatusBarButton(WebInspector.UIString("Record"), "record-profile-status-bar-item"); |
| this.toggleTimelineButton.addEventListener("click", this._toggleTimelineButtonClicked, this); |
| this._statusBarButtons.push(this.toggleTimelineButton); |
| this._panelStatusBarElement.appendChild(this.toggleTimelineButton.element); |
| |
| this.clearButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear"), "clear-status-bar-item"); |
| this.clearButton.addEventListener("click", this._clearPanel, this); |
| this._statusBarButtons.push(this.clearButton); |
| this._panelStatusBarElement.appendChild(this.clearButton.element); |
| |
| this._panelStatusBarElement.appendChild(this._filterBar.filterButton()); |
| |
| this.garbageCollectButton = new WebInspector.StatusBarButton(WebInspector.UIString("Collect Garbage"), "garbage-collect-status-bar-item"); |
| this.garbageCollectButton.addEventListener("click", this._garbageCollectButtonClicked, this); |
| this._statusBarButtons.push(this.garbageCollectButton); |
| this._panelStatusBarElement.appendChild(this.garbageCollectButton.element); |
| |
| this._glueParentButton = new WebInspector.StatusBarButton(WebInspector.UIString("Glue asynchronous events to causes"), "glue-async-status-bar-item"); |
| this._glueParentButton.toggled = this._glueRecordsSetting.get(); |
| this._presentationModel.setGlueRecords(this._glueParentButton.toggled); |
| this._glueParentButton.addEventListener("click", this._glueParentButtonClicked, this); |
| this._statusBarButtons.push(this._glueParentButton); |
| this._panelStatusBarElement.appendChild(this._glueParentButton.element); |
| |
| this._statusTextContainer = this._panelStatusBarElement.createChild("div"); |
| |
| this.recordsCounter = new WebInspector.StatusBarText(""); |
| this._statusTextContainer.appendChild(this.recordsCounter.element); |
| |
| this.frameStatistics = this._statusTextContainer.createChild("div", "timeline-frame-statistics status-bar-item status-bar-text hidden"); |
| |
| function getAnchor() |
| { |
| return this.frameStatistics; |
| } |
| this._frameStatisticsPopoverHelper = new WebInspector.PopoverHelper(this.frameStatistics, getAnchor.bind(this), this._showFrameStatistics.bind(this)); |
| |
| this._miscStatusBarItems = this._panelStatusBarElement.createChild("div", "status-bar-item"); |
| }, |
| |
| _textFilterChanged: function(event) |
| { |
| var searchQuery = this._textFilter.value(); |
| this._presentationModel.setSearchFilter(null); |
| delete this._searchFilter; |
| |
| function cleanRecord(record) |
| { |
| delete record.clicked; |
| } |
| WebInspector.TimelinePresentationModel.forAllRecords(this._presentationModel.rootRecord().children, cleanRecord); |
| |
| this.searchCanceled(); |
| if (searchQuery) { |
| this._searchFilter = new WebInspector.TimelineSearchFilter(createPlainTextSearchRegex(searchQuery, "i")); |
| this._presentationModel.setSearchFilter(this._searchFilter); |
| } |
| this._invalidateAndScheduleRefresh(true, true); |
| }, |
| |
| _durationFilterChanged: function() |
| { |
| var duration = this._durationComboBoxFilter.value(); |
| var minimumRecordDuration = +duration / 1000.0; |
| this._durationFilter.setMinimumRecordDuration(minimumRecordDuration); |
| this._invalidateAndScheduleRefresh(true, true); |
| }, |
| |
| _categoriesFilterChanged: function(name, event) |
| { |
| var categories = WebInspector.TimelinePresentationModel.categories(); |
| categories[name].hidden = !this._categoryFilters[name].checked(); |
| this._invalidateAndScheduleRefresh(true, true); |
| }, |
| |
| _onFiltersToggled: function(event) |
| { |
| var toggled = /** @type {boolean} */ (event.data); |
| this._filtersContainer.enableStyleClass("hidden", !toggled); |
| this.onResize(); |
| }, |
| |
| /** |
| * @param {?WebInspector.ProgressIndicator} indicator |
| */ |
| _setOperationInProgress: function(indicator) |
| { |
| this._operationInProgress = !!indicator; |
| for (var i = 0; i < this._statusBarButtons.length; ++i) |
| this._statusBarButtons[i].setEnabled(!this._operationInProgress); |
| this._glueParentButton.setEnabled(!this._operationInProgress && !this._frameController); |
| this._statusTextContainer.enableStyleClass("hidden", !!indicator); |
| this._miscStatusBarItems.removeChildren(); |
| if (indicator) |
| this._miscStatusBarItems.appendChild(indicator.element); |
| }, |
| |
| _registerShortcuts: function() |
| { |
| this.registerShortcuts(WebInspector.TimelinePanelDescriptor.ShortcutKeys.StartStopRecording, this._toggleTimelineButtonClicked.bind(this)); |
| this.registerShortcuts(WebInspector.TimelinePanelDescriptor.ShortcutKeys.SaveToFile, this._saveToFile.bind(this)); |
| this.registerShortcuts(WebInspector.TimelinePanelDescriptor.ShortcutKeys.LoadFromFile, this._selectFileToLoad.bind(this)); |
| }, |
| |
| _createFileSelector: function() |
| { |
| if (this._fileSelectorElement) |
| this.element.removeChild(this._fileSelectorElement); |
| |
| this._fileSelectorElement = WebInspector.createFileSelectorElement(this._loadFromFile.bind(this)); |
| this.element.appendChild(this._fileSelectorElement); |
| }, |
| |
| _contextMenu: function(event) |
| { |
| var contextMenu = new WebInspector.ContextMenu(event); |
| contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Save Timeline data\u2026" : "Save Timeline Data\u2026"), this._saveToFile.bind(this), this._operationInProgress); |
| contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Load Timeline data\u2026" : "Load Timeline Data\u2026"), this._selectFileToLoad.bind(this), this._operationInProgress); |
| contextMenu.show(); |
| }, |
| |
| /** |
| * @param {Event=} event |
| * @return {boolean} |
| */ |
| _saveToFile: function(event) |
| { |
| if (this._operationInProgress) |
| return true; |
| this._model.saveToFile(); |
| return true; |
| }, |
| |
| /** |
| * @param {Event=} event |
| * @return {boolean} |
| */ |
| _selectFileToLoad: function(event) { |
| this._fileSelectorElement.click(); |
| return true; |
| }, |
| |
| /** |
| * @param {!File} file |
| */ |
| _loadFromFile: function(file) |
| { |
| var progressIndicator = this._prepareToLoadTimeline(); |
| if (!progressIndicator) |
| return; |
| this._model.loadFromFile(file, progressIndicator); |
| this._createFileSelector(); |
| }, |
| |
| /** |
| * @param {string} url |
| */ |
| loadFromURL: function(url) |
| { |
| var progressIndicator = this._prepareToLoadTimeline(); |
| if (!progressIndicator) |
| return; |
| this._model.loadFromURL(url, progressIndicator); |
| }, |
| |
| /** |
| * @return {?WebInspector.ProgressIndicator} |
| */ |
| _prepareToLoadTimeline: function() |
| { |
| if (this._operationInProgress) |
| return null; |
| if (this.toggleTimelineButton.toggled) { |
| this.toggleTimelineButton.toggled = false; |
| this._model.stopRecording(); |
| } |
| var progressIndicator = new WebInspector.ProgressIndicator(); |
| progressIndicator.addEventListener(WebInspector.ProgressIndicator.Events.Done, this._setOperationInProgress.bind(this, null)); |
| this._setOperationInProgress(progressIndicator); |
| return progressIndicator; |
| }, |
| |
| _rootRecord: function() |
| { |
| return this._presentationModel.rootRecord(); |
| }, |
| |
| _updateRecordsCounter: function(recordsInWindowCount) |
| { |
| this.recordsCounter.setText(WebInspector.UIString("%d of %d records shown", recordsInWindowCount, this._allRecordsCount)); |
| }, |
| |
| _updateFrameStatistics: function(frames) |
| { |
| if (frames.length) { |
| this._lastFrameStatistics = new WebInspector.FrameStatistics(frames); |
| var details = WebInspector.UIString("avg: %s, \u03c3: %s", |
| Number.secondsToString(this._lastFrameStatistics.average, true), Number.secondsToString(this._lastFrameStatistics.stddev, true)); |
| } else |
| this._lastFrameStatistics = null; |
| this.frameStatistics.textContent = WebInspector.UIString("%d of %d frames shown", frames.length, this._presentationModel.frames().length); |
| if (details) { |
| this.frameStatistics.appendChild(document.createTextNode(" (")); |
| this.frameStatistics.createChild("span", "timeline-frames-stats").textContent = details; |
| this.frameStatistics.appendChild(document.createTextNode(")")); |
| } |
| }, |
| |
| /** |
| * @param {Element} anchor |
| * @param {WebInspector.Popover} popover |
| */ |
| _showFrameStatistics: function(anchor, popover) |
| { |
| popover.show(WebInspector.TimelinePresentationModel.generatePopupContentForFrameStatistics(this._lastFrameStatistics), anchor); |
| }, |
| |
| _updateEventDividers: function() |
| { |
| this._timelineGrid.removeEventDividers(); |
| var clientWidth = this._graphRowsElementWidth; |
| var dividers = []; |
| var eventDividerRecords = this._presentationModel.eventDividerRecords(); |
| |
| for (var i = 0; i < eventDividerRecords.length; ++i) { |
| var record = eventDividerRecords[i]; |
| var positions = this._calculator.computeBarGraphWindowPosition(record); |
| var dividerPosition = Math.round(positions.left); |
| if (dividerPosition < 0 || dividerPosition >= clientWidth || dividers[dividerPosition]) |
| continue; |
| var divider = WebInspector.TimelinePresentationModel.createEventDivider(record.type, record.title); |
| divider.style.left = dividerPosition + "px"; |
| dividers[dividerPosition] = divider; |
| } |
| this._timelineGrid.addEventDividers(dividers); |
| }, |
| |
| _updateFrameBars: function(frames) |
| { |
| var clientWidth = this._graphRowsElementWidth; |
| if (this._frameContainer) |
| this._frameContainer.removeChildren(); |
| else { |
| const frameContainerBorderWidth = 1; |
| this._frameContainer = document.createElement("div"); |
| this._frameContainer.addStyleClass("fill"); |
| this._frameContainer.addStyleClass("timeline-frame-container"); |
| this._frameContainer.style.height = this._headerLineCount * WebInspector.TimelinePanel.rowHeight + frameContainerBorderWidth + "px"; |
| this._frameContainer.addEventListener("dblclick", this._onFrameDoubleClicked.bind(this), false); |
| } |
| |
| var dividers = [ this._frameContainer ]; |
| |
| for (var i = 0; i < frames.length; ++i) { |
| var frame = frames[i]; |
| var frameStart = this._calculator.computePosition(frame.startTime); |
| var frameEnd = this._calculator.computePosition(frame.endTime); |
| |
| var frameStrip = document.createElement("div"); |
| frameStrip.className = "timeline-frame-strip"; |
| var actualStart = Math.max(frameStart, 0); |
| var width = frameEnd - actualStart; |
| frameStrip.style.left = actualStart + "px"; |
| frameStrip.style.width = width + "px"; |
| frameStrip._frame = frame; |
| |
| const minWidthForFrameInfo = 60; |
| if (width > minWidthForFrameInfo) |
| frameStrip.textContent = Number.secondsToString(frame.endTime - frame.startTime, true); |
| |
| this._frameContainer.appendChild(frameStrip); |
| |
| if (actualStart > 0) { |
| var frameMarker = WebInspector.TimelinePresentationModel.createEventDivider(WebInspector.TimelineModel.RecordType.BeginFrame); |
| frameMarker.style.left = frameStart + "px"; |
| dividers.push(frameMarker); |
| } |
| } |
| this._timelineGrid.addEventDividers(dividers); |
| }, |
| |
| _onFrameDoubleClicked: function(event) |
| { |
| var frameBar = event.target.enclosingNodeOrSelfWithClass("timeline-frame-strip"); |
| if (!frameBar) |
| return; |
| this._overviewPane.zoomToFrame(frameBar._frame); |
| }, |
| |
| _overviewModeChanged: function(event) |
| { |
| var mode = event.data; |
| var shouldShowMemory = mode === WebInspector.TimelineOverviewPane.Mode.Memory; |
| var frameMode = mode === WebInspector.TimelineOverviewPane.Mode.Frames; |
| this._overviewModeSetting.set(mode); |
| if (frameMode !== this._frameMode) { |
| this._frameMode = frameMode; |
| this._glueParentButton.setEnabled(!frameMode); |
| this._presentationModel.setGlueRecords(this._glueParentButton.toggled && !frameMode); |
| this._repopulateRecords(); |
| |
| if (frameMode) { |
| this.element.addStyleClass("timeline-frame-overview"); |
| this.recordsCounter.element.addStyleClass("hidden"); |
| this.frameStatistics.removeStyleClass("hidden"); |
| this._frameController = new WebInspector.TimelineFrameController(this._model, this._overviewPane, this._presentationModel); |
| } else { |
| this._frameController.dispose(); |
| this._frameController = null; |
| this.element.removeStyleClass("timeline-frame-overview"); |
| this.recordsCounter.element.removeStyleClass("hidden"); |
| this.frameStatistics.addStyleClass("hidden"); |
| } |
| } |
| if (shouldShowMemory === this._memoryStatistics.visible()) |
| return; |
| this._timelineMemorySplitter.enableStyleClass("hidden", !shouldShowMemory); |
| if (!shouldShowMemory) { |
| this._memoryStatistics.hide(); |
| this._resize(this.splitView.sidebarWidth()); |
| } else { |
| this._memoryStatistics.show(); |
| this._setMemoryCountersHeight(WebInspector.settings.memoryCounterGraphsHeight.get()); |
| this._resize(this.splitView.sidebarWidth()); |
| } |
| }, |
| |
| /** |
| * @return {boolean} |
| */ |
| _toggleTimelineButtonClicked: function() |
| { |
| if (this._operationInProgress) |
| return true; |
| if (this.toggleTimelineButton.toggled) { |
| this._model.stopRecording(); |
| } else { |
| this._model.startRecording(this._includeDomCounters); |
| WebInspector.userMetrics.TimelineStarted.record(); |
| } |
| return true; |
| }, |
| |
| _garbageCollectButtonClicked: function() |
| { |
| HeapProfilerAgent.collectGarbage(); |
| }, |
| |
| _glueParentButtonClicked: function() |
| { |
| var newValue = !this._glueParentButton.toggled; |
| this._glueParentButton.toggled = newValue; |
| this._presentationModel.setGlueRecords(newValue); |
| this._glueRecordsSetting.set(newValue); |
| this._repopulateRecords(); |
| }, |
| |
| _repopulateRecords: function() |
| { |
| this._resetPanel(); |
| this._automaticallySizeWindow = false; |
| var records = this._model.records; |
| for (var i = 0; i < records.length; ++i) |
| this._innerAddRecordToTimeline(records[i]); |
| this._invalidateAndScheduleRefresh(false, false); |
| }, |
| |
| _onTimelineEventRecorded: function(event) |
| { |
| if (this._innerAddRecordToTimeline(/** @type {TimelineAgent.TimelineEvent} */(event.data))) |
| this._invalidateAndScheduleRefresh(false, false); |
| }, |
| |
| /** |
| * @param {TimelineAgent.TimelineEvent} record |
| */ |
| _innerAddRecordToTimeline: function(record) |
| { |
| if (record.type === WebInspector.TimelineModel.RecordType.Program) { |
| this._mainThreadTasks.push({ |
| startTime: WebInspector.TimelineModel.startTimeInSeconds(record), |
| endTime: WebInspector.TimelineModel.endTimeInSeconds(record) |
| }); |
| } |
| |
| var records = this._presentationModel.addRecord(record); |
| this._allRecordsCount += records.length; |
| var hasVisibleRecords = false; |
| var presentationModel = this._presentationModel; |
| function checkVisible(record) |
| { |
| hasVisibleRecords |= presentationModel.isVisible(record); |
| } |
| WebInspector.TimelinePresentationModel.forAllRecords(records, checkVisible); |
| |
| function isAdoptedRecord(record) |
| { |
| return record.parent !== presentationModel.rootRecord; |
| } |
| // Tell caller update is necessary either if we added a visible record or if we re-parented a record. |
| return hasVisibleRecords || records.some(isAdoptedRecord); |
| }, |
| |
| sidebarResized: function(event) |
| { |
| var width = event.data; |
| this._resize(width); |
| this._overviewPane.sidebarResized(width); |
| this._memoryStatistics.setSidebarWidth(width); |
| }, |
| |
| onResize: function() |
| { |
| if (this._memoryStatistics.visible()) |
| this._setMemoryCountersHeight(this._memoryStatistics.height()); |
| this._resize(this.splitView.sidebarWidth()); |
| }, |
| |
| /** |
| * @param {number} sidebarWidth |
| */ |
| _resize: function(sidebarWidth) |
| { |
| this._closeRecordDetails(); |
| this._graphRowsElementWidth = this._graphRowsElement.offsetWidth; |
| this._containerElementHeight = this._containerElement.clientHeight; |
| this._scheduleRefresh(false, true); |
| }, |
| |
| _clearPanel: function() |
| { |
| this._model.reset(); |
| }, |
| |
| _onRecordsCleared: function() |
| { |
| this._resetPanel(); |
| this._invalidateAndScheduleRefresh(true, true); |
| }, |
| |
| _onRecordingStarted: function() |
| { |
| this.toggleTimelineButton.title = WebInspector.UIString("Stop"); |
| this.toggleTimelineButton.toggled = true; |
| }, |
| |
| _onRecordingStopped: function() |
| { |
| this.toggleTimelineButton.title = WebInspector.UIString("Record"); |
| this.toggleTimelineButton.toggled = false; |
| }, |
| |
| _resetPanel: function() |
| { |
| this._presentationModel.reset(); |
| this._boundariesAreValid = false; |
| this._adjustScrollPosition(0); |
| this._closeRecordDetails(); |
| this._allRecordsCount = 0; |
| this._automaticallySizeWindow = true; |
| this._mainThreadTasks = []; |
| }, |
| |
| elementsToRestoreScrollPositionsFor: function() |
| { |
| return [this._containerElement]; |
| }, |
| |
| wasShown: function() |
| { |
| WebInspector.Panel.prototype.wasShown.call(this); |
| if (!WebInspector.TimelinePanel._categoryStylesInitialized) { |
| WebInspector.TimelinePanel._categoryStylesInitialized = true; |
| this._injectCategoryStyles(); |
| } |
| this._overviewPane.setMode(this._overviewModeSetting.get()); |
| this._refresh(); |
| }, |
| |
| willHide: function() |
| { |
| this._closeRecordDetails(); |
| WebInspector.Panel.prototype.willHide.call(this); |
| }, |
| |
| _onScroll: function(event) |
| { |
| this._closeRecordDetails(); |
| this._scrollTop = this._containerElement.scrollTop; |
| var dividersTop = Math.max(0, this._scrollTop); |
| this._timelineGrid.setScrollAndDividerTop(this._scrollTop, dividersTop); |
| this._scheduleRefresh(true, true); |
| }, |
| |
| /** |
| * @param {boolean} preserveBoundaries |
| * @param {boolean} userGesture |
| */ |
| _invalidateAndScheduleRefresh: function(preserveBoundaries, userGesture) |
| { |
| this._presentationModel.invalidateFilteredRecords(); |
| delete this._searchResults; |
| this._scheduleRefresh(preserveBoundaries, userGesture); |
| }, |
| |
| /** |
| * @param {boolean} preserveBoundaries |
| * @param {boolean} userGesture |
| */ |
| _scheduleRefresh: function(preserveBoundaries, userGesture) |
| { |
| this._closeRecordDetails(); |
| this._boundariesAreValid &= preserveBoundaries; |
| |
| if (!this.isShowing()) |
| return; |
| |
| if (preserveBoundaries || userGesture) |
| this._refresh(); |
| else { |
| if (!this._refreshTimeout) |
| this._refreshTimeout = setTimeout(this._refresh.bind(this), 300); |
| } |
| }, |
| |
| _refresh: function() |
| { |
| if (this._refreshTimeout) { |
| clearTimeout(this._refreshTimeout); |
| delete this._refreshTimeout; |
| } |
| |
| this._timelinePaddingLeft = this._expandOffset; |
| this._calculator.setWindow(this._overviewPane.windowStartTime(), this._overviewPane.windowEndTime()); |
| this._calculator.setDisplayWindow(this._timelinePaddingLeft, this._graphRowsElementWidth); |
| |
| var recordsInWindowCount = this._refreshRecords(); |
| this._updateRecordsCounter(recordsInWindowCount); |
| if (!this._boundariesAreValid) { |
| this._updateEventDividers(); |
| var frames = this._frameController && this._presentationModel.filteredFrames(this._overviewPane.windowStartTime(), this._overviewPane.windowEndTime()); |
| if (frames) { |
| this._updateFrameStatistics(frames); |
| const maxFramesForFrameBars = 30; |
| if (frames.length && frames.length < maxFramesForFrameBars) { |
| this._timelineGrid.removeDividers(); |
| this._updateFrameBars(frames); |
| } else |
| this._timelineGrid.updateDividers(this._calculator); |
| } else |
| this._timelineGrid.updateDividers(this._calculator); |
| if (this._mainThreadMonitoringEnabled) |
| this._refreshMainThreadBars(); |
| } |
| if (this._memoryStatistics.visible()) |
| this._memoryStatistics.refresh(); |
| this._boundariesAreValid = true; |
| }, |
| |
| revealRecordAt: function(time) |
| { |
| var recordToReveal; |
| function findRecordToReveal(record) |
| { |
| if (record.containsTime(time)) { |
| recordToReveal = record; |
| return true; |
| } |
| // If there is no record containing the time than use the latest one before that time. |
| if (!recordToReveal || record.endTime < time && recordToReveal.endTime < record.endTime) |
| recordToReveal = record; |
| return false; |
| } |
| WebInspector.TimelinePresentationModel.forAllRecords(this._presentationModel.rootRecord().children, null, findRecordToReveal); |
| |
| // The record ends before the window left bound so scroll to the top. |
| if (!recordToReveal) { |
| this._containerElement.scrollTop = 0; |
| return; |
| } |
| |
| this._revealRecord(recordToReveal); |
| }, |
| |
| _revealRecord: function(recordToReveal) |
| { |
| // Expand all ancestors. |
| for (var parent = recordToReveal.parent; parent !== this._rootRecord(); parent = parent.parent) { |
| if (!parent.collapsed) |
| continue; |
| this._presentationModel.invalidateFilteredRecords(); |
| parent.collapsed = false; |
| } |
| var recordsInWindow = this._presentationModel.filteredRecords(); |
| var index = recordsInWindow.indexOf(recordToReveal); |
| this._recordToHighlight = recordToReveal; |
| var oldScrollTop = this._containerElement.scrollTop; |
| this._containerElement.scrollTop = index * WebInspector.TimelinePanel.rowHeight; |
| // The record to highlight will be only kept for one refresh cycle, so make sure |
| // refresh is only called once, either via scroll event handler or directly, if |
| // we're not about to scroll. |
| if (this._containerElement.scrollTop === oldScrollTop) |
| this._refresh(); |
| }, |
| |
| _refreshRecords: function() |
| { |
| var recordsInWindow = this._presentationModel.filteredRecords(); |
| |
| // Calculate the visible area. |
| var visibleTop = this._scrollTop; |
| var visibleBottom = visibleTop + this._containerElementHeight; |
| |
| const rowHeight = WebInspector.TimelinePanel.rowHeight; |
| const headerHeight = WebInspector.TimelinePanel.headerHeight; |
| |
| // Convert visible area to visible indexes. Always include top-level record for a visible nested record. |
| var startIndex = Math.max(0, Math.min(Math.floor(visibleTop / rowHeight) - this._headerLineCount, recordsInWindow.length - 1)); |
| var endIndex = Math.min(recordsInWindow.length, Math.ceil(visibleBottom / rowHeight)); |
| var lastVisibleLine = Math.max(0, Math.floor(visibleBottom / rowHeight) - this._headerLineCount); |
| if (this._automaticallySizeWindow && recordsInWindow.length > lastVisibleLine) { |
| this._automaticallySizeWindow = false; |
| // If we're at the top, always use real timeline start as a left window bound so that expansion arrow padding logic works. |
| var windowStartTime = startIndex ? recordsInWindow[startIndex].startTime : this._model.minimumRecordTime(); |
| this._overviewPane.setWindowTimes(windowStartTime, recordsInWindow[Math.max(0, lastVisibleLine - 1)].endTime); |
| recordsInWindow = this._presentationModel.filteredRecords(); |
| endIndex = Math.min(recordsInWindow.length, lastVisibleLine); |
| } |
| |
| // Resize gaps first. |
| this._topGapElement.style.height = (startIndex * rowHeight) + "px"; |
| this.sidebarTreeElement.style.height = ((startIndex + this._headerLineCount) * rowHeight) + "px"; |
| this._bottomGapElement.style.height = (recordsInWindow.length - endIndex) * rowHeight + "px"; |
| var rowsHeight = headerHeight + recordsInWindow.length * rowHeight; |
| var totalHeight = Math.max(this._containerElementHeight, rowsHeight); |
| this.splitView.firstElement().style.height = totalHeight + "px"; |
| this.splitView.secondElement().style.height = totalHeight + "px"; |
| this.splitView.resizerElement().style.height = totalHeight + "px"; |
| |
| // Update visible rows. |
| var listRowElement = this._sidebarListElement.firstChild; |
| var width = this._graphRowsElementWidth; |
| this._itemsGraphsElement.removeChild(this._graphRowsElement); |
| var graphRowElement = this._graphRowsElement.firstChild; |
| var scheduleRefreshCallback = this._invalidateAndScheduleRefresh.bind(this, true, true); |
| this._itemsGraphsElement.removeChild(this._expandElements); |
| this._expandElements.removeChildren(); |
| |
| var highlightedRecord = this._recordToHighlight; |
| delete this._recordToHighlight; |
| var highlightedListRowElement; |
| var highlightedGraphRowElement; |
| |
| for (var i = 0; i < endIndex; ++i) { |
| var record = recordsInWindow[i]; |
| var isEven = !(i % 2); |
| |
| if (i < startIndex) { |
| var lastChildIndex = i + record.visibleChildrenCount; |
| if (lastChildIndex >= startIndex && lastChildIndex < endIndex) { |
| var expandElement = new WebInspector.TimelineExpandableElement(this._expandElements); |
| var positions = this._calculator.computeBarGraphWindowPosition(record); |
| expandElement._update(record, i, positions.left - this._expandOffset, positions.width); |
| } |
| } else { |
| if (!listRowElement) { |
| listRowElement = new WebInspector.TimelineRecordListRow().element; |
| this._sidebarListElement.appendChild(listRowElement); |
| } |
| if (!graphRowElement) { |
| graphRowElement = new WebInspector.TimelineRecordGraphRow(this._itemsGraphsElement, scheduleRefreshCallback).element; |
| this._graphRowsElement.appendChild(graphRowElement); |
| } |
| |
| if (highlightedRecord === record) { |
| highlightedListRowElement = listRowElement; |
| highlightedGraphRowElement = graphRowElement; |
| } |
| |
| listRowElement.row.update(record, isEven, visibleTop); |
| graphRowElement.row.update(record, isEven, this._calculator, this._expandOffset, i); |
| |
| listRowElement = listRowElement.nextSibling; |
| graphRowElement = graphRowElement.nextSibling; |
| } |
| } |
| |
| // Remove extra rows. |
| while (listRowElement) { |
| var nextElement = listRowElement.nextSibling; |
| listRowElement.row.dispose(); |
| listRowElement = nextElement; |
| } |
| while (graphRowElement) { |
| var nextElement = graphRowElement.nextSibling; |
| graphRowElement.row.dispose(); |
| graphRowElement = nextElement; |
| } |
| |
| this._itemsGraphsElement.insertBefore(this._graphRowsElement, this._bottomGapElement); |
| this._itemsGraphsElement.appendChild(this._expandElements); |
| this._adjustScrollPosition((recordsInWindow.length + this._headerLineCount) * rowHeight); |
| this._updateSearchHighlight(false, true); |
| |
| if (highlightedListRowElement) { |
| highlightedListRowElement.addStyleClass("highlighted-timeline-record"); |
| highlightedGraphRowElement.addStyleClass("highlighted-timeline-record"); |
| } |
| |
| return recordsInWindow.length; |
| }, |
| |
| _refreshMainThreadBars: function() |
| { |
| const barOffset = 3; |
| const minGap = 3; |
| |
| var minWidth = WebInspector.TimelineCalculator._minWidth; |
| var widthAdjustment = minWidth / 2; |
| |
| var width = this._graphRowsElementWidth; |
| var boundarySpan = this._overviewPane.windowEndTime() - this._overviewPane.windowStartTime(); |
| var scale = boundarySpan / (width - minWidth - this._timelinePaddingLeft); |
| var startTime = this._overviewPane.windowStartTime() - this._timelinePaddingLeft * scale; |
| var endTime = startTime + width * scale; |
| |
| var tasks = this._mainThreadMonitoringEnabled ? this._mainThreadTasks : []; |
| |
| /** |
| * @param {number} value |
| * @param {{startTime: number, endTime: number}} task |
| * @return {number} |
| */ |
| function compareEndTime(value, task) |
| { |
| return value < task.endTime ? -1 : 1; |
| } |
| |
| var taskIndex = insertionIndexForObjectInListSortedByFunction(startTime, tasks, compareEndTime); |
| |
| var container = this._cpuBarsElement; |
| var element = container.firstChild; |
| var lastElement; |
| var lastLeft; |
| var lastRight; |
| |
| for (; taskIndex < tasks.length; ++taskIndex) { |
| var task = tasks[taskIndex]; |
| if (task.startTime > endTime) |
| break; |
| |
| var left = Math.max(0, this._calculator.computePosition(task.startTime) + barOffset - widthAdjustment); |
| var right = Math.min(width, this._calculator.computePosition(task.endTime) + barOffset + widthAdjustment); |
| |
| if (lastElement) { |
| var gap = Math.floor(left) - Math.ceil(lastRight); |
| if (gap < minGap) { |
| lastRight = right; |
| lastElement._tasksInfo.lastTaskIndex = taskIndex; |
| continue; |
| } |
| lastElement.style.width = (lastRight - lastLeft) + "px"; |
| } |
| |
| if (!element) |
| element = container.createChild("div", "timeline-graph-bar"); |
| |
| element.style.left = left + "px"; |
| element._tasksInfo = {tasks: tasks, firstTaskIndex: taskIndex, lastTaskIndex: taskIndex}; |
| lastLeft = left; |
| lastRight = right; |
| |
| lastElement = element; |
| element = element.nextSibling; |
| } |
| |
| if (lastElement) |
| lastElement.style.width = (lastRight - lastLeft) + "px"; |
| |
| while (element) { |
| var nextElement = element.nextSibling; |
| element._tasksInfo = null; |
| container.removeChild(element); |
| element = nextElement; |
| } |
| }, |
| |
| _adjustHeaderHeight: function() |
| { |
| const headerBorderWidth = 1; |
| const headerMargin = 2; |
| |
| var headerHeight = this._headerLineCount * WebInspector.TimelinePanel.rowHeight; |
| this.sidebarElement.firstChild.style.height = headerHeight + "px"; |
| this._timelineGrid.dividersLabelBarElement.style.height = headerHeight + headerMargin + "px"; |
| this._itemsGraphsElement.style.top = headerHeight + headerBorderWidth + "px"; |
| }, |
| |
| _adjustScrollPosition: function(totalHeight) |
| { |
| // Prevent the container from being scrolled off the end. |
| if ((this._scrollTop + this._containerElementHeight) > totalHeight + 1) |
| this._containerElement.scrollTop = (totalHeight - this._containerElement.offsetHeight); |
| }, |
| |
| _getPopoverAnchor: function(element) |
| { |
| return element.enclosingNodeOrSelfWithClass("timeline-graph-bar") || |
| element.enclosingNodeOrSelfWithClass("timeline-tree-item") || |
| element.enclosingNodeOrSelfWithClass("timeline-frame-strip"); |
| }, |
| |
| _mouseOut: function(e) |
| { |
| this._hideQuadHighlight(); |
| }, |
| |
| /** |
| * @param {Event} e |
| */ |
| _mouseMove: function(e) |
| { |
| var anchor = this._getPopoverAnchor(e.target); |
| |
| if (anchor && anchor.row && anchor.row._record.highlightQuad) |
| this._highlightQuad(anchor.row._record.highlightQuad); |
| else |
| this._hideQuadHighlight(); |
| |
| if (anchor && anchor._tasksInfo) { |
| var offset = anchor.offsetLeft; |
| this._timelineGrid.showCurtains(offset >= 0 ? offset : 0, anchor.offsetWidth); |
| } else |
| this._timelineGrid.hideCurtains(); |
| }, |
| |
| /** |
| * @param {Array.<number>} quad |
| */ |
| _highlightQuad: function(quad) |
| { |
| if (this._highlightedQuad === quad) |
| return; |
| this._highlightedQuad = quad; |
| DOMAgent.highlightQuad(quad, WebInspector.Color.PageHighlight.Content.toProtocolRGBA(), WebInspector.Color.PageHighlight.ContentOutline.toProtocolRGBA()); |
| }, |
| |
| _hideQuadHighlight: function() |
| { |
| if (this._highlightedQuad) { |
| delete this._highlightedQuad; |
| DOMAgent.hideHighlight(); |
| } |
| }, |
| |
| /** |
| * @param {Element} anchor |
| * @param {WebInspector.Popover} popover |
| */ |
| _showPopover: function(anchor, popover) |
| { |
| if (anchor.hasStyleClass("timeline-frame-strip")) { |
| var frame = anchor._frame; |
| popover.show(WebInspector.TimelinePresentationModel.generatePopupContentForFrame(frame), anchor); |
| } else { |
| if (anchor.row && anchor.row._record) |
| anchor.row._record.generatePopupContent(showCallback); |
| else if (anchor._tasksInfo) |
| popover.show(this._presentationModel.generateMainThreadBarPopupContent(anchor._tasksInfo), anchor, null, null, WebInspector.Popover.Orientation.Bottom); |
| } |
| |
| function showCallback(popupContent) |
| { |
| popover.show(popupContent, anchor); |
| } |
| }, |
| |
| _closeRecordDetails: function() |
| { |
| this._popoverHelper.hidePopover(); |
| }, |
| |
| _injectCategoryStyles: function() |
| { |
| var style = document.createElement("style"); |
| var categories = WebInspector.TimelinePresentationModel.categories(); |
| |
| style.textContent = Object.values(categories).map(WebInspector.TimelinePresentationModel.createStyleRuleForCategory).join("\n"); |
| document.head.appendChild(style); |
| }, |
| |
| jumpToNextSearchResult: function() |
| { |
| if (!this._searchResults || !this._searchResults.length) |
| return; |
| var index = this._selectedSearchResult ? this._searchResults.indexOf(this._selectedSearchResult) : -1; |
| this._jumpToSearchResult(index + 1); |
| }, |
| |
| jumpToPreviousSearchResult: function() |
| { |
| if (!this._searchResults || !this._searchResults.length) |
| return; |
| var index = this._selectedSearchResult ? this._searchResults.indexOf(this._selectedSearchResult) : 0; |
| this._jumpToSearchResult(index - 1); |
| }, |
| |
| _jumpToSearchResult: function(index) |
| { |
| this._selectSearchResult((index + this._searchResults.length) % this._searchResults.length); |
| this._highlightSelectedSearchResult(true); |
| }, |
| |
| _selectSearchResult: function(index) |
| { |
| this._selectedSearchResult = this._searchResults[index]; |
| WebInspector.searchController.updateCurrentMatchIndex(index, this); |
| }, |
| |
| _highlightSelectedSearchResult: function(revealRecord) |
| { |
| this._clearHighlight(); |
| if (this._searchFilter) |
| return; |
| |
| var record = this._selectedSearchResult; |
| if (!record) |
| return; |
| |
| for (var element = this._sidebarListElement.firstChild; element; element = element.nextSibling) { |
| if (element.row._record === record) { |
| element.row.highlight(this._searchRegExp, this._highlightDomChanges); |
| return; |
| } |
| } |
| |
| if (revealRecord) |
| this._revealRecord(record); |
| }, |
| |
| _clearHighlight: function() |
| { |
| if (this._highlightDomChanges) |
| WebInspector.revertDomChanges(this._highlightDomChanges); |
| this._highlightDomChanges = []; |
| }, |
| |
| /** |
| * @param {boolean} revealRecord |
| * @param {boolean} shouldJump |
| */ |
| _updateSearchHighlight: function(revealRecord, shouldJump) |
| { |
| if (this._searchFilter || !this._searchRegExp) { |
| this._clearHighlight(); |
| return; |
| } |
| |
| if (!this._searchResults) |
| this._updateSearchResults(shouldJump); |
| this._highlightSelectedSearchResult(revealRecord); |
| }, |
| |
| _updateSearchResults: function(shouldJump) |
| { |
| var searchRegExp = this._searchRegExp; |
| if (!searchRegExp) |
| return; |
| |
| var matches = []; |
| var presentationModel = this._presentationModel; |
| |
| function processRecord(record) |
| { |
| if (presentationModel.isVisible(record) && WebInspector.TimelineRecordListRow.testContentMatching(record, searchRegExp)) |
| matches.push(record); |
| return false; |
| } |
| WebInspector.TimelinePresentationModel.forAllRecords(presentationModel.rootRecord().children, processRecord); |
| |
| var matchesCount = matches.length; |
| if (matchesCount) { |
| this._searchResults = matches; |
| WebInspector.searchController.updateSearchMatchesCount(matchesCount, this); |
| |
| var selectedIndex = matches.indexOf(this._selectedSearchResult); |
| if (shouldJump && selectedIndex === -1) |
| selectedIndex = 0; |
| this._selectSearchResult(selectedIndex); |
| } else { |
| WebInspector.searchController.updateSearchMatchesCount(0, this); |
| delete this._selectedSearchResult; |
| } |
| }, |
| |
| searchCanceled: function() |
| { |
| this._clearHighlight(); |
| delete this._searchResults; |
| delete this._selectedSearchResult; |
| delete this._searchRegExp; |
| }, |
| |
| /** |
| * @param {string} query |
| * @param {boolean} shouldJump |
| */ |
| performSearch: function(query, shouldJump) |
| { |
| this._searchRegExp = createPlainTextSearchRegex(query, "i"); |
| delete this._searchResults; |
| this._updateSearchHighlight(true, shouldJump); |
| }, |
| |
| __proto__: WebInspector.Panel.prototype |
| } |
| |
| /** |
| * @constructor |
| * @param {WebInspector.TimelineModel} model |
| * @implements {WebInspector.TimelineGrid.Calculator} |
| */ |
| WebInspector.TimelineCalculator = function(model) |
| { |
| this._model = model; |
| } |
| |
| WebInspector.TimelineCalculator._minWidth = 5; |
| |
| WebInspector.TimelineCalculator.prototype = { |
| /** |
| * @param {number} time |
| */ |
| computePosition: function(time) |
| { |
| return (time - this._minimumBoundary) / this.boundarySpan() * this._workingArea + this.paddingLeft; |
| }, |
| |
| computeBarGraphPercentages: function(record) |
| { |
| var start = (record.startTime - this._minimumBoundary) / this.boundarySpan() * 100; |
| var end = (record.startTime + record.selfTime - this._minimumBoundary) / this.boundarySpan() * 100; |
| var endWithChildren = (record.lastChildEndTime - this._minimumBoundary) / this.boundarySpan() * 100; |
| var cpuWidth = record.coalesced ? endWithChildren - start : record.cpuTime / this.boundarySpan() * 100; |
| return {start: start, end: end, endWithChildren: endWithChildren, cpuWidth: cpuWidth}; |
| }, |
| |
| computeBarGraphWindowPosition: function(record) |
| { |
| var percentages = this.computeBarGraphPercentages(record); |
| var widthAdjustment = 0; |
| |
| var left = this.computePosition(record.startTime); |
| var width = (percentages.end - percentages.start) / 100 * this._workingArea; |
| if (width < WebInspector.TimelineCalculator._minWidth) { |
| widthAdjustment = WebInspector.TimelineCalculator._minWidth - width; |
| width = WebInspector.TimelineCalculator._minWidth; |
| } |
| var widthWithChildren = (percentages.endWithChildren - percentages.start) / 100 * this._workingArea + widthAdjustment; |
| var cpuWidth = percentages.cpuWidth / 100 * this._workingArea + widthAdjustment; |
| if (percentages.endWithChildren > percentages.end) |
| widthWithChildren += widthAdjustment; |
| return {left: left, width: width, widthWithChildren: widthWithChildren, cpuWidth: cpuWidth}; |
| }, |
| |
| setWindow: function(minimumBoundary, maximumBoundary) |
| { |
| this._minimumBoundary = minimumBoundary; |
| this._maximumBoundary = maximumBoundary; |
| }, |
| |
| /** |
| * @param {number} paddingLeft |
| * @param {number} clientWidth |
| */ |
| setDisplayWindow: function(paddingLeft, clientWidth) |
| { |
| this._workingArea = clientWidth - WebInspector.TimelineCalculator._minWidth - paddingLeft; |
| this.paddingLeft = paddingLeft; |
| }, |
| |
| formatTime: function(value) |
| { |
| return Number.secondsToString(value + this._minimumBoundary - this._model.minimumRecordTime()); |
| }, |
| |
| maximumBoundary: function() |
| { |
| return this._maximumBoundary; |
| }, |
| |
| minimumBoundary: function() |
| { |
| return this._minimumBoundary; |
| }, |
| |
| zeroTime: function() |
| { |
| return this._model.minimumRecordTime(); |
| }, |
| |
| boundarySpan: function() |
| { |
| return this._maximumBoundary - this._minimumBoundary; |
| } |
| } |
| |
| /** |
| * @constructor |
| */ |
| WebInspector.TimelineRecordListRow = function() |
| { |
| this.element = document.createElement("div"); |
| this.element.row = this; |
| this.element.style.cursor = "pointer"; |
| var iconElement = document.createElement("span"); |
| iconElement.className = "timeline-tree-icon"; |
| this.element.appendChild(iconElement); |
| |
| this._typeElement = document.createElement("span"); |
| this._typeElement.className = "type"; |
| this.element.appendChild(this._typeElement); |
| |
| var separatorElement = document.createElement("span"); |
| separatorElement.className = "separator"; |
| separatorElement.textContent = " "; |
| |
| this._dataElement = document.createElement("span"); |
| this._dataElement.className = "data dimmed"; |
| |
| this.element.appendChild(separatorElement); |
| this.element.appendChild(this._dataElement); |
| } |
| |
| WebInspector.TimelineRecordListRow.prototype = { |
| update: function(record, isEven, offset) |
| { |
| this._record = record; |
| this._offset = offset; |
| |
| this.element.className = "timeline-tree-item timeline-category-" + record.category.name; |
| if (isEven) |
| this.element.addStyleClass("even"); |
| if (record.hasWarnings()) |
| this.element.addStyleClass("warning"); |
| else if (record.childHasWarnings()) |
| this.element.addStyleClass("child-warning"); |
| if (record.isBackground) |
| this.element.addStyleClass("background"); |
| |
| this._typeElement.textContent = record.title; |
| |
| if (this._dataElement.firstChild) |
| this._dataElement.removeChildren(); |
| |
| if (record.detailsNode()) |
| this._dataElement.appendChild(record.detailsNode()); |
| }, |
| |
| highlight: function(regExp, domChanges) |
| { |
| var matchInfo = this.element.textContent.match(regExp); |
| if (matchInfo) |
| WebInspector.highlightSearchResult(this.element, matchInfo.index, matchInfo[0].length, domChanges); |
| }, |
| |
| dispose: function() |
| { |
| this.element.remove(); |
| } |
| } |
| |
| /** |
| * @param {!WebInspector.TimelinePresentationModel.Record} record |
| * @param {!RegExp} regExp |
| */ |
| WebInspector.TimelineRecordListRow.testContentMatching = function(record, regExp) |
| { |
| var toSearchText = record.title; |
| if (record.detailsNode()) |
| toSearchText += " " + record.detailsNode().textContent; |
| return regExp.test(toSearchText); |
| } |
| |
| /** |
| * @constructor |
| */ |
| WebInspector.TimelineRecordGraphRow = function(graphContainer, scheduleRefresh) |
| { |
| this.element = document.createElement("div"); |
| this.element.row = this; |
| |
| this._barAreaElement = document.createElement("div"); |
| this._barAreaElement.className = "timeline-graph-bar-area"; |
| this.element.appendChild(this._barAreaElement); |
| |
| this._barWithChildrenElement = document.createElement("div"); |
| this._barWithChildrenElement.className = "timeline-graph-bar with-children"; |
| this._barWithChildrenElement.row = this; |
| this._barAreaElement.appendChild(this._barWithChildrenElement); |
| |
| this._barCpuElement = document.createElement("div"); |
| this._barCpuElement.className = "timeline-graph-bar cpu" |
| this._barCpuElement.row = this; |
| this._barAreaElement.appendChild(this._barCpuElement); |
| |
| this._barElement = document.createElement("div"); |
| this._barElement.className = "timeline-graph-bar"; |
| this._barElement.row = this; |
| this._barAreaElement.appendChild(this._barElement); |
| |
| this._expandElement = new WebInspector.TimelineExpandableElement(graphContainer); |
| this._expandElement._element.addEventListener("click", this._onClick.bind(this)); |
| |
| this._scheduleRefresh = scheduleRefresh; |
| } |
| |
| WebInspector.TimelineRecordGraphRow.prototype = { |
| update: function(record, isEven, calculator, expandOffset, index) |
| { |
| this._record = record; |
| this.element.className = "timeline-graph-side timeline-category-" + record.category.name; |
| if (isEven) |
| this.element.addStyleClass("even"); |
| if (record.isBackground) |
| this.element.addStyleClass("background"); |
| |
| var barPosition = calculator.computeBarGraphWindowPosition(record); |
| this._barWithChildrenElement.style.left = barPosition.left + "px"; |
| this._barWithChildrenElement.style.width = barPosition.widthWithChildren + "px"; |
| this._barElement.style.left = barPosition.left + "px"; |
| this._barElement.style.width = barPosition.width + "px"; |
| this._barCpuElement.style.left = barPosition.left + "px"; |
| this._barCpuElement.style.width = barPosition.cpuWidth + "px"; |
| this._expandElement._update(record, index, barPosition.left - expandOffset, barPosition.width); |
| }, |
| |
| _onClick: function(event) |
| { |
| this._record.collapsed = !this._record.collapsed; |
| this._record.clicked = true; |
| this._scheduleRefresh(false, true); |
| }, |
| |
| dispose: function() |
| { |
| this.element.remove(); |
| this._expandElement._dispose(); |
| } |
| } |
| |
| /** |
| * @constructor |
| */ |
| WebInspector.TimelineExpandableElement = function(container) |
| { |
| this._element = container.createChild("div", "timeline-expandable"); |
| this._element.createChild("div", "timeline-expandable-left"); |
| this._element.createChild("div", "timeline-expandable-arrow"); |
| } |
| |
| WebInspector.TimelineExpandableElement.prototype = { |
| _update: function(record, index, left, width) |
| { |
| const rowHeight = WebInspector.TimelinePanel.rowHeight; |
| if (record.visibleChildrenCount || record.expandable) { |
| this._element.style.top = index * rowHeight + "px"; |
| this._element.style.left = left + "px"; |
| this._element.style.width = Math.max(12, width + 25) + "px"; |
| if (!record.collapsed) { |
| this._element.style.height = (record.visibleChildrenCount + 1) * rowHeight + "px"; |
| this._element.addStyleClass("timeline-expandable-expanded"); |
| this._element.removeStyleClass("timeline-expandable-collapsed"); |
| } else { |
| this._element.style.height = rowHeight + "px"; |
| this._element.addStyleClass("timeline-expandable-collapsed"); |
| this._element.removeStyleClass("timeline-expandable-expanded"); |
| } |
| this._element.removeStyleClass("hidden"); |
| } else |
| this._element.addStyleClass("hidden"); |
| }, |
| |
| _dispose: function() |
| { |
| this._element.remove(); |
| } |
| } |
| |
| /** |
| * @constructor |
| * @implements {WebInspector.TimelinePresentationModel.Filter} |
| */ |
| WebInspector.TimelineCategoryFilter = function() |
| { |
| } |
| |
| WebInspector.TimelineCategoryFilter.prototype = { |
| /** |
| * @param {!WebInspector.TimelinePresentationModel.Record} record |
| * @return {boolean} |
| */ |
| accept: function(record) |
| { |
| return !record.category.hidden && record.type !== WebInspector.TimelineModel.RecordType.BeginFrame; |
| } |
| } |
| |
| /** |
| * @constructor |
| * @implements {WebInspector.TimelinePresentationModel.Filter} |
| */ |
| WebInspector.TimelineIsLongFilter = function() |
| { |
| this._minimumRecordDuration = 0; |
| } |
| |
| WebInspector.TimelineIsLongFilter.prototype = { |
| /** |
| * @param {number} value |
| */ |
| setMinimumRecordDuration: function(value) |
| { |
| this._minimumRecordDuration = value; |
| }, |
| |
| /** |
| * @param {!WebInspector.TimelinePresentationModel.Record} record |
| * @return {boolean} |
| */ |
| accept: function(record) |
| { |
| return this._minimumRecordDuration ? ((record.lastChildEndTime - record.startTime) >= this._minimumRecordDuration) : true; |
| } |
| } |
| |
| /** |
| * @param {!RegExp} regExp |
| * @constructor |
| * @implements {WebInspector.TimelinePresentationModel.Filter} |
| */ |
| WebInspector.TimelineSearchFilter = function(regExp) |
| { |
| this._regExp = regExp; |
| } |
| |
| WebInspector.TimelineSearchFilter.prototype = { |
| |
| /** |
| * @param {!WebInspector.TimelinePresentationModel.Record} record |
| * @return {boolean} |
| */ |
| accept: function(record) |
| { |
| return WebInspector.TimelineRecordListRow.testContentMatching(record, this._regExp); |
| } |
| } |