| /* |
| * 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("PieChart.js"); |
| importScript("TimelineModel.js"); |
| importScript("TimelineOverviewPane.js"); |
| importScript("TimelinePresentationModel.js"); |
| importScript("TimelineFrameController.js"); |
| importScript("TimelineEventOverview.js"); |
| importScript("TimelineFrameOverview.js"); |
| importScript("TimelineMemoryOverview.js"); |
| |
| /** |
| * @constructor |
| * @implements {WebInspector.Searchable} |
| * @extends {WebInspector.Panel} |
| */ |
| WebInspector.TimelinePanel = function() |
| { |
| WebInspector.Panel.call(this, "timeline"); |
| this.registerRequiredCSS("timelinePanel.css"); |
| this.registerRequiredCSS("filter.css"); |
| this.element.classList.add("vbox"); |
| this.element.addEventListener("contextmenu", this._contextMenu.bind(this), false); |
| |
| // Create model. |
| this._model = new WebInspector.TimelineModel(); |
| this._calculator = new WebInspector.TimelineCalculator(this._model); |
| 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._presentationModeSetting = WebInspector.settings.createSetting("timelineOverviewMode", WebInspector.TimelinePanel.Mode.Events); |
| this._glueRecordsSetting = WebInspector.settings.createSetting("timelineGlueRecords", false); |
| |
| this._createStatusBarItems(); |
| |
| this._createPresentationSelector(); |
| |
| // Create top overview component. |
| this._overviewPane = new WebInspector.TimelineOverviewPane(this._model); |
| this._overviewPane.addEventListener(WebInspector.TimelineOverviewPane.Events.WindowChanged, this._windowChanged.bind(this)); |
| this._overviewPane.show(this._presentationSelector.element); |
| |
| // Create presentation model. |
| this._presentationModel = new WebInspector.TimelinePresentationModel(); |
| this._presentationModel.addFilter(new WebInspector.TimelineWindowFilter(this._overviewPane)); |
| this._presentationModel.addFilter(new WebInspector.TimelineCategoryFilter()); |
| this._presentationModel.addFilter(this._durationFilter); |
| this._presentationModel.setGlueRecords(this._glueParentButton.toggled); |
| |
| this._frameMode = false; |
| this._boundariesAreValid = true; |
| this._scrollTop = 0; |
| |
| // Create layout componets. |
| |
| // ------------------------------- |
| // | Overview | |
| // |-------------------------------| |
| // | | | | |
| // | | Records | | |
| // | | | Details | |
| // |----------------| | |
| // | | Memory | | |
| // ------------------------------- |
| |
| // Create top level properties splitter. |
| this._detailsSplitView = new WebInspector.SplitView(false, "timeline-details"); |
| this._detailsSplitView.element.classList.remove("fill"); |
| this._detailsSplitView.element.classList.add("timeline-details-split"); |
| this._detailsSplitView.sidebarElement.classList.add("timeline-details"); |
| this._detailsSplitView.show(this.element); |
| this._detailsSplitView.mainElement.classList.add("vbox"); |
| this._detailsSplitView.setMainElementConstraints(undefined, 40); |
| this._detailsView = new WebInspector.TimelineDetailsView(); |
| this._detailsView.show(this._detailsSplitView.sidebarElement); |
| this._detailsSplitView.installResizer(this._detailsView.titleElement()); |
| |
| WebInspector.dockController.addEventListener(WebInspector.DockController.Events.DockSideChanged, this._dockSideChanged.bind(this)); |
| WebInspector.settings.splitVerticallyWhenDockedToRight.addChangeListener(this._dockSideChanged.bind(this)); |
| this._dockSideChanged(); |
| |
| // Create memory splitter as a left child of properties. |
| this._searchableView = new WebInspector.SearchableView(this); |
| this._searchableView.show(this._detailsSplitView.mainElement); |
| |
| this._timelineMemorySplitter = new WebInspector.SplitView(false, "timeline-memory"); |
| this._timelineMemorySplitter.element.classList.remove("fill"); |
| this._timelineMemorySplitter.element.classList.add("timeline-memory-split"); |
| this._timelineMemorySplitter.show(this._searchableView.element); |
| if (this._presentationModeSetting.get() !== WebInspector.TimelinePanel.Mode.Memory) |
| this._timelineMemorySplitter.showOnlyFirst(); |
| |
| // Create records sidebar as a top memory splitter child. |
| this._sidebarView = new WebInspector.SidebarView(WebInspector.SidebarView.SidebarPosition.Start, "timeline-split"); |
| this._sidebarView.addEventListener(WebInspector.SidebarView.EventTypes.Resized, this._sidebarResized, this); |
| this._sidebarView.setSecondIsSidebar(false); |
| this._sidebarView.show(this._timelineMemorySplitter.mainElement); |
| this._containerElement = this._sidebarView.element; |
| this._containerElement.tabIndex = 0; |
| this._containerElement.id = "timeline-container"; |
| this._containerElement.addEventListener("scroll", this._onScroll.bind(this), false); |
| |
| // Create memory statistics as a bottom memory splitter child. |
| this._memoryStatistics = new WebInspector.DOMCountersGraph(this, this._model); |
| this._memoryStatistics.show(this._timelineMemorySplitter.sidebarElement); |
| this._timelineMemorySplitter.installResizer(this._memoryStatistics.resizeElement()); |
| |
| // Create records list in the records sidebar. |
| this._sidebarView.sidebarElement.classList.add("vbox"); |
| this._sidebarView.sidebarElement.createChild("div", "timeline-records-title").textContent = WebInspector.UIString("RECORDS"); |
| this._sidebarListElement = this._sidebarView.sidebarElement.createChild("div", "timeline-records-list"); |
| |
| // Create grid in the records main area. |
| this._gridContainer = new WebInspector.ViewWithResizeCallback(this._onViewportResize.bind(this)); |
| this._gridContainer.element.classList.add("fill"); |
| this._gridContainer.element.id = "resources-container-content"; |
| this._gridContainer.show(this._sidebarView.mainElement); |
| this._timelineGrid = new WebInspector.TimelineGrid(); |
| this._itemsGraphsElement = this._timelineGrid.itemsGraphsElement; |
| this._itemsGraphsElement.id = "timeline-graphs"; |
| this._gridContainer.element.appendChild(this._timelineGrid.element); |
| this._timelineGrid.gridHeaderElement.id = "timeline-grid-header"; |
| this._timelineGrid.gridHeaderElement.classList.add("fill"); |
| this._memoryStatistics.setMainTimelineGrid(this._timelineGrid); |
| this._timelineMemorySplitter.mainElement.appendChild(this._timelineGrid.gridHeaderElement); |
| |
| // Create gap elements |
| this._topGapElement = this._itemsGraphsElement.createChild("div", "timeline-gap"); |
| this._graphRowsElement = this._itemsGraphsElement.createChild("div"); |
| this._bottomGapElement = this._itemsGraphsElement.createChild("div", "timeline-gap"); |
| this._expandElements = this._itemsGraphsElement.createChild("div"); |
| this._expandElements.id = "orphan-expand-elements"; |
| |
| 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); |
| this.element.addEventListener("keydown", this._keyDown.bind(this), false); |
| |
| this._expandOffset = 15; |
| |
| // Create gpu tasks containers. |
| this._mainThreadTasks = /** @type {!Array.<!TimelineAgent.TimelineEvent>} */ ([]); |
| this._gpuTasks = /** @type {!Array.<!TimelineAgent.TimelineEvent>} */ ([]); |
| var utilizationStripsElement = this._timelineGrid.gridHeaderElement.createChild("div", "timeline-utilization-strips vbox"); |
| this._cpuBarsElement = utilizationStripsElement.createChild("div", "timeline-utilization-strip"); |
| if (WebInspector.experimentsSettings.gpuTimeline.isEnabled()) |
| this._gpuBarsElement = utilizationStripsElement.createChild("div", "timeline-utilization-strip gpu"); |
| |
| this._createFileSelector(); |
| this._registerShortcuts(); |
| this._allRecordsCount = 0; |
| |
| WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.WillReloadPage, this._willReloadPage, this); |
| WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.Load, this._loadEventFired, this); |
| |
| this._createOverviewControls(); |
| this._selectPresentationMode(this._presentationModeSetting.get()); |
| } |
| |
| WebInspector.TimelinePanel.Mode = { |
| Events: "Events", |
| Frames: "Frames", |
| Memory: "Memory" |
| }; |
| |
| // Define row and header height, should be in sync with styles for timeline graphs. |
| WebInspector.TimelinePanel.rowHeight = 18; |
| WebInspector.TimelinePanel.headerHeight = 20; |
| |
| WebInspector.TimelinePanel.durationFilterPresetsMs = [0, 1, 15]; |
| |
| WebInspector.TimelinePanel.prototype = { |
| _createPresentationSelector: function() |
| { |
| this._presentationSelector = new WebInspector.View(); |
| this._presentationSelector.element.classList.add("hbox"); |
| this._presentationSelector.element.id = "timeline-overview-panel"; |
| this._presentationSelector.show(this.element); |
| |
| this._topPaneSidebarElement = this._presentationSelector.element.createChild("div"); |
| this._topPaneSidebarElement.id = "timeline-overview-sidebar"; |
| |
| var overviewTreeElement = this._topPaneSidebarElement.createChild("ol", "sidebar-tree vbox"); |
| var topPaneSidebarTree = new TreeOutline(overviewTreeElement); |
| |
| this._overviewItems = {}; |
| this._overviewItems[WebInspector.TimelinePanel.Mode.Events] = new WebInspector.SidebarTreeElement("timeline-overview-sidebar-events", |
| WebInspector.UIString("Events")); |
| this._overviewItems[WebInspector.TimelinePanel.Mode.Frames] = new WebInspector.SidebarTreeElement("timeline-overview-sidebar-frames", |
| WebInspector.UIString("Frames")); |
| this._overviewItems[WebInspector.TimelinePanel.Mode.Memory] = new WebInspector.SidebarTreeElement("timeline-overview-sidebar-memory", |
| WebInspector.UIString("Memory")); |
| |
| for (var mode in this._overviewItems) { |
| var item = this._overviewItems[mode]; |
| item.onselect = this._onModeChanged.bind(this, mode); |
| topPaneSidebarTree.appendChild(item); |
| } |
| }, |
| |
| get calculator() |
| { |
| return this._calculator; |
| }, |
| |
| defaultFocusedElement: function() |
| { |
| return this.element; |
| }, |
| |
| /** |
| * @return {!WebInspector.SearchableView} |
| */ |
| searchableView: function() |
| { |
| return this._searchableView; |
| }, |
| |
| /** |
| * @param {!WebInspector.FilterBar} filterBar |
| * @return {boolean} |
| */ |
| _createFilters: function(filterBar) |
| { |
| this._textFilter = new WebInspector.TextFilterUI(); |
| this._textFilter.addEventListener(WebInspector.FilterUI.Events.FilterChanged, this._textFilterChanged, 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._durationFilter = new WebInspector.TimelineIsLongFilter(); |
| this._durationComboBoxFilter = new WebInspector.ComboBoxFilterUI(durationOptions); |
| this._durationComboBoxFilter.addEventListener(WebInspector.FilterUI.Events.FilterChanged, this._durationFilterChanged, 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); |
| filter.addEventListener(WebInspector.FilterUI.Events.FilterChanged, this._categoriesFilterChanged.bind(this, category.name), this); |
| filterBar.addFilter(filter); |
| this._categoryFilters[category.name] = filter; |
| } |
| return true; |
| }, |
| |
| _createStatusBarItems: function() |
| { |
| var panelStatusBarElement = this.element.createChild("div", "panel-status-bar"); |
| 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); |
| 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); |
| panelStatusBarElement.appendChild(this.clearButton.element); |
| |
| this._filterBar = new WebInspector.FilterBar(); |
| panelStatusBarElement.appendChild(this._filterBar.filterButton().element); |
| |
| 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); |
| 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._glueParentButton.addEventListener("click", this._glueParentButtonClicked, this); |
| this._statusBarButtons.push(this._glueParentButton); |
| panelStatusBarElement.appendChild(this._glueParentButton.element); |
| |
| panelStatusBarElement.appendChild(WebInspector.SettingsTab.createSettingCheckbox(WebInspector.UIString("Capture stacks"), WebInspector.settings.timelineCaptureStacks, true, undefined, |
| WebInspector.UIString("Capture JavaScript stack on every timeline event"))); |
| |
| this._statusTextContainer = panelStatusBarElement.createChild("div"); |
| this.recordsCounter = new WebInspector.StatusBarText(""); |
| this._statusTextContainer.appendChild(this.recordsCounter.element); |
| |
| this._miscStatusBarItems = panelStatusBarElement.createChild("div", "status-bar-item"); |
| |
| var hasFilters = this._createFilters(this._filterBar); |
| this._filterBar.filterButton().setEnabled(hasFilters); |
| 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); |
| }, |
| |
| _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._fileSelectorElement.remove(); |
| |
| 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(); |
| }, |
| |
| /** |
| * @return {boolean} |
| */ |
| _saveToFile: function() |
| { |
| if (this._operationInProgress) |
| return true; |
| this._model.saveToFile(); |
| return true; |
| }, |
| |
| /** |
| * @return {boolean} |
| */ |
| _selectFileToLoad: function() { |
| 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); |
| }, |
| |
| _dockSideChanged: function() |
| { |
| var dockSide = WebInspector.dockController.dockSide(); |
| var vertically = false; |
| if (dockSide === WebInspector.DockController.State.DockedToBottom) |
| vertically = true; |
| else |
| vertically = !WebInspector.settings.splitVerticallyWhenDockedToRight.get(); |
| this._detailsSplitView.setVertical(vertically); |
| this._detailsView.setVertical(vertically); |
| }, |
| |
| /** |
| * @return {?WebInspector.ProgressIndicator} |
| */ |
| _prepareToLoadTimeline: function() |
| { |
| if (this._operationInProgress) |
| return null; |
| if (this._recordingInProgress()) { |
| this.toggleTimelineButton.toggled = false; |
| this._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) |
| { |
| this._lastFrameStatistics = frames.length ? new WebInspector.FrameStatistics(frames) : null; |
| }, |
| |
| _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.classList.add("fill"); |
| this._frameContainer.classList.add("timeline-frame-container"); |
| this._frameContainer.style.height = 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); |
| }, |
| |
| _createOverviewControls: function() |
| { |
| this._overviewControls = {}; |
| this._overviewControls[WebInspector.TimelinePanel.Mode.Events] = new WebInspector.TimelineEventOverview(this._model); |
| this._frameOverviewControl = new WebInspector.TimelineFrameOverview(this._model); |
| this._overviewControls[WebInspector.TimelinePanel.Mode.Frames] = this._frameOverviewControl; |
| this._overviewControls[WebInspector.TimelinePanel.Mode.Memory] = new WebInspector.TimelineMemoryOverview(this._model); |
| }, |
| |
| _selectPresentationMode: function(mode) |
| { |
| if (!this._overviewItems[mode]) |
| mode = WebInspector.TimelinePanel.Mode.Events; |
| this._overviewItems[mode].revealAndSelect(false); |
| }, |
| |
| _onModeChanged: function(mode) |
| { |
| this._overviewPane.willSetOverviewControl(this._overviewControls[mode]); |
| |
| var shouldShowMemory = mode === WebInspector.TimelinePanel.Mode.Memory; |
| var frameMode = mode === WebInspector.TimelinePanel.Mode.Frames; |
| this._presentationModeSetting.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.classList.add("timeline-frame-overview"); |
| this.recordsCounter.element.classList.add("hidden"); |
| this._frameController = new WebInspector.TimelineFrameController(this._model, this._frameOverviewControl, this._presentationModel); |
| } else { |
| this._frameController.dispose(); |
| this._frameController = null; |
| this.element.classList.remove("timeline-frame-overview"); |
| this.recordsCounter.element.classList.remove("hidden"); |
| } |
| } |
| |
| if (shouldShowMemory) |
| this._timelineMemorySplitter.showBoth(); |
| else |
| this._timelineMemorySplitter.showOnlyFirst(); |
| this.onResize(); |
| this._updateSelectionDetails(); |
| this._overviewPane.didSetOverviewControl(); |
| }, |
| |
| /** |
| * @param {boolean} userInitiated |
| */ |
| _startRecording: function(userInitiated) |
| { |
| this._userInitiatedRecording = userInitiated; |
| this._model.startRecording(true); |
| if (userInitiated) |
| WebInspector.userMetrics.TimelineStarted.record(); |
| }, |
| |
| _stopRecording: function() |
| { |
| this._userInitiatedRecording = false; |
| this._model.stopRecording(); |
| }, |
| |
| /** |
| * @return {boolean} |
| */ |
| _toggleTimelineButtonClicked: function() |
| { |
| if (this._operationInProgress) |
| return true; |
| if (this._recordingInProgress()) |
| this._stopRecording(); |
| else |
| this._startRecording(true); |
| 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 |
| * @return {boolean} |
| */ |
| _innerAddRecordToTimeline: function(record) |
| { |
| if (record.type === WebInspector.TimelineModel.RecordType.Program) |
| this._mainThreadTasks.push(record); |
| |
| if (record.type === WebInspector.TimelineModel.RecordType.GPUTask) { |
| this._gpuTasks.push(record); |
| return WebInspector.TimelineModel.startTimeInSeconds(record) < this._overviewPane.windowEndTime(); |
| } |
| |
| 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() |
| { |
| var width = this._sidebarView.sidebarWidth(); |
| this._topPaneSidebarElement.style.flexBasis = width + "px"; |
| if (this._presentationModeSetting.get() === WebInspector.TimelinePanel.Mode.Memory) |
| this._memoryStatistics.setSidebarWidth(width); |
| this._timelineGrid.gridHeaderElement.style.left = width + "px"; |
| }, |
| |
| /** |
| * @param {number} width |
| */ |
| setSidebarWidth: function(width) |
| { |
| if (this._presentationModeSetting.get() === WebInspector.TimelinePanel.Mode.Memory) |
| this._sidebarView.setSidebarWidth(width); |
| }, |
| |
| _onViewportResize: function() |
| { |
| this._resize(this._sidebarView.sidebarWidth()); |
| }, |
| |
| /** |
| * @param {number} sidebarWidth |
| */ |
| _resize: function(sidebarWidth) |
| { |
| this._closeRecordDetails(); |
| this._graphRowsElementWidth = this._graphRowsElement.offsetWidth; |
| this._containerElementHeight = this._containerElement.clientHeight; |
| this._timelineGrid.gridHeaderElement.style.width = this._itemsGraphsElement.offsetWidth + "px"; |
| 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; |
| }, |
| |
| _recordingInProgress: function() |
| { |
| return this.toggleTimelineButton.toggled; |
| }, |
| |
| _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 = []; |
| this._gpuTasks = []; |
| }, |
| |
| 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.willSetOverviewControl(this._overviewControls[this._presentationModeSetting.get()]); |
| this._onViewportResize(); |
| 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 {?WebInspector.TimelinePresentationModel.Record} record |
| */ |
| _selectRecord: function(record) |
| { |
| if (record === this._lastSelectedRecord) |
| return; |
| |
| // Remove selection rendering. |
| if (this._lastSelectedRecord) { |
| var listRow = /** @type {!WebInspector.TimelineRecordListRow} */ (this._lastSelectedRecord.getUserObject("WebInspector.TimelineRecordListRow")); |
| if (listRow) |
| listRow.renderAsSelected(false); |
| var graphRow = /** @type {!WebInspector.TimelineRecordGraphRow} */ (this._lastSelectedRecord.getUserObject("WebInspector.TimelineRecordGraphRow")); |
| if (graphRow) |
| graphRow.renderAsSelected(false); |
| } |
| |
| if (!record) { |
| this._updateSelectionDetails(); |
| return; |
| } |
| |
| this._revealRecord(record); |
| this._lastSelectedRecord = record; |
| var listRow = /** @type {!WebInspector.TimelineRecordListRow} */ (record.getUserObject("WebInspector.TimelineRecordListRow")); |
| listRow.renderAsSelected(true); |
| var graphRow = /** @type {!WebInspector.TimelineRecordListRow} */ (record.getUserObject("WebInspector.TimelineRecordGraphRow")); |
| graphRow.renderAsSelected(true); |
| |
| record.generatePopupContent(showCallback.bind(this)); |
| |
| /** |
| * @param {!DocumentFragment} element |
| */ |
| function showCallback(element) |
| { |
| this._detailsView.setContent(record.title, element); |
| } |
| }, |
| |
| _updateSelectionDetails: function() |
| { |
| var startTime = this._overviewPane.windowStartTime() * 1000; |
| var endTime = this._overviewPane.windowEndTime() * 1000; |
| // Return early in case 0 selection window. |
| if (startTime < 0) |
| return; |
| |
| var aggregatedStats = {}; |
| |
| /** |
| * @param {number} value |
| * @param {!TimelineAgent.TimelineEvent} task |
| * @return {number} |
| */ |
| function compareEndTime(value, task) |
| { |
| return value < task.endTime ? -1 : 1; |
| } |
| |
| /** |
| * @param {!TimelineAgent.TimelineEvent} rawRecord |
| */ |
| function aggregateTimeForRecordWithinWindow(rawRecord) |
| { |
| if (!rawRecord.endTime || rawRecord.endTime < startTime || rawRecord.startTime > endTime) |
| return; |
| |
| var childrenTime = 0; |
| var children = rawRecord.children || []; |
| for (var i = 0; i < children.length; ++i) { |
| var child = children[i]; |
| if (!child.endTime || child.endTime < startTime || child.startTime > endTime) |
| continue; |
| childrenTime += Math.min(endTime, child.endTime) - Math.max(startTime, child.startTime); |
| aggregateTimeForRecordWithinWindow(child); |
| } |
| var categoryName = WebInspector.TimelinePresentationModel.categoryForRecord(rawRecord).name; |
| var ownTime = Math.min(endTime, rawRecord.endTime) - Math.max(startTime, rawRecord.startTime) - childrenTime; |
| aggregatedStats[categoryName] = (aggregatedStats[categoryName] || 0) + ownTime / 1000; |
| } |
| |
| var taskIndex = insertionIndexForObjectInListSortedByFunction(startTime, this._mainThreadTasks, compareEndTime); |
| for (; taskIndex < this._mainThreadTasks.length; ++taskIndex) { |
| var task = this._mainThreadTasks[taskIndex]; |
| if (task.startTime > endTime) |
| break; |
| aggregateTimeForRecordWithinWindow(task); |
| } |
| |
| var aggregatedTotal = 0; |
| for (var categoryName in aggregatedStats) |
| aggregatedTotal += aggregatedStats[categoryName]; |
| aggregatedStats["idle"] = Math.max(0, (endTime - startTime) / 1000 - aggregatedTotal); |
| |
| var fragment = document.createDocumentFragment(); |
| var pie = WebInspector.TimelinePresentationModel.generatePieChart(aggregatedStats); |
| fragment.appendChild(pie.element); |
| |
| if (this._frameMode && this._lastFrameStatistics) { |
| var title = WebInspector.UIString("%s \u2013 %s (%d frames)", Number.secondsToString(this._lastFrameStatistics.startOffset, true), Number.secondsToString(this._lastFrameStatistics.endOffset, true), this._lastFrameStatistics.frameCount); |
| fragment.appendChild(WebInspector.TimelinePresentationModel.generatePopupContentForFrameStatistics(this._lastFrameStatistics)); |
| } else { |
| var title = WebInspector.UIString("%s \u2013 %s", this._calculator.formatTime(0, true), this._calculator.formatTime(this._calculator.boundarySpan(), true)); |
| } |
| this._detailsView.setContent(title, fragment); |
| }, |
| |
| _windowChanged: function() |
| { |
| this._invalidateAndScheduleRefresh(false, true); |
| this._selectRecord(null); |
| }, |
| |
| /** |
| * @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); |
| this._refreshAllUtilizationBars(); |
| } |
| if (this._presentationModeSetting.get() === WebInspector.TimelinePanel.Mode.Memory) |
| 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, true); |
| }, |
| |
| /** |
| * @param {boolean=} highlight |
| */ |
| _revealRecord: function(recordToReveal, highlight) |
| { |
| // 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); |
| if (highlight) |
| this._recordToHighlight = recordToReveal; |
| |
| var itemOffset = index * WebInspector.TimelinePanel.rowHeight; |
| var visibleTop = this._scrollTop - WebInspector.TimelinePanel.headerHeight; |
| var visibleBottom = visibleTop + this._containerElementHeight - WebInspector.TimelinePanel.rowHeight; |
| if (itemOffset < visibleTop) |
| this._containerElement.scrollTop = itemOffset; |
| else if (itemOffset > visibleBottom) |
| this._containerElement.scrollTop = itemOffset - this._containerElementHeight + WebInspector.TimelinePanel.headerHeight + WebInspector.TimelinePanel.rowHeight; |
| else if (highlight) |
| this._refresh(); |
| }, |
| |
| _refreshRecords: function() |
| { |
| var recordsInWindow = this._presentationModel.filteredRecords(); |
| |
| // Calculate the visible area. |
| var visibleTop = this._scrollTop; |
| var visibleBottom = visibleTop + this._containerElementHeight; |
| |
| var rowHeight = WebInspector.TimelinePanel.rowHeight; |
| var 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 - headerHeight) / rowHeight), recordsInWindow.length - 1)); |
| var endIndex = Math.min(recordsInWindow.length, Math.ceil(visibleBottom / rowHeight)); |
| var lastVisibleLine = Math.max(0, Math.floor((visibleBottom - headerHeight) / rowHeight)); |
| 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._sidebarView.sidebarElement.firstChild.style.flexBasis = (startIndex * rowHeight + headerHeight) + "px"; |
| this._bottomGapElement.style.height = (recordsInWindow.length - endIndex) * rowHeight + "px"; |
| var rowsHeight = headerHeight + recordsInWindow.length * rowHeight; |
| var totalHeight = Math.max(this._containerElementHeight, rowsHeight); |
| |
| this._sidebarView.firstElement().style.height = totalHeight + "px"; |
| this._sidebarView.secondElement().style.height = totalHeight + "px"; |
| this._sidebarView.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); |
| var selectRecordCallback = this._selectRecord.bind(this); |
| 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]; |
| |
| 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(selectRecordCallback, scheduleRefreshCallback).element; |
| this._sidebarListElement.appendChild(listRowElement); |
| } |
| if (!graphRowElement) { |
| graphRowElement = new WebInspector.TimelineRecordGraphRow(this._itemsGraphsElement, selectRecordCallback, scheduleRefreshCallback).element; |
| this._graphRowsElement.appendChild(graphRowElement); |
| } |
| |
| if (highlightedRecord === record) { |
| highlightedListRowElement = listRowElement; |
| highlightedGraphRowElement = graphRowElement; |
| } |
| |
| listRowElement.row.update(record, visibleTop); |
| graphRowElement.row.update(record, this._calculator, this._expandOffset, i); |
| if (this._lastSelectedRecord === record) { |
| listRowElement.row.renderAsSelected(true); |
| graphRowElement.row.renderAsSelected(true); |
| } |
| |
| 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 * rowHeight + headerHeight); |
| this._updateSearchHighlight(false, true); |
| |
| if (highlightedListRowElement) { |
| highlightedListRowElement.classList.add("highlighted-timeline-record"); |
| highlightedGraphRowElement.classList.add("highlighted-timeline-record"); |
| } |
| |
| return recordsInWindow.length; |
| }, |
| |
| _refreshAllUtilizationBars: function() |
| { |
| this._refreshUtilizationBars(WebInspector.UIString("CPU"), this._mainThreadTasks, this._cpuBarsElement); |
| if (WebInspector.experimentsSettings.gpuTimeline.isEnabled()) |
| this._refreshUtilizationBars(WebInspector.UIString("GPU"), this._gpuTasks, this._gpuBarsElement); |
| }, |
| |
| /** |
| * @param {string} name |
| * @param {!Array.<!TimelineAgent.TimelineEvent>} tasks |
| * @param {?Element} container |
| */ |
| _refreshUtilizationBars: function(name, tasks, container) |
| { |
| if (!container) |
| return; |
| |
| 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) * 1000; |
| var endTime = startTime + width * scale * 1000; |
| |
| /** |
| * @param {number} value |
| * @param {!TimelineAgent.TimelineEvent} task |
| * @return {number} |
| */ |
| function compareEndTime(value, task) |
| { |
| return value < task.endTime ? -1 : 1; |
| } |
| |
| var taskIndex = insertionIndexForObjectInListSortedByFunction(startTime, tasks, compareEndTime); |
| |
| var foreignStyle = "gpu-task-foreign"; |
| 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(WebInspector.TimelineModel.startTimeInSeconds(task)) + barOffset - widthAdjustment); |
| var right = Math.min(width, this._calculator.computePosition(WebInspector.TimelineModel.endTimeInSeconds(task)) + barOffset + widthAdjustment); |
| |
| if (lastElement) { |
| var gap = Math.floor(left) - Math.ceil(lastRight); |
| if (gap < minGap) { |
| if (!task.data["foreign"]) |
| lastElement.classList.remove(foreignStyle); |
| 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 = {name: name, tasks: tasks, firstTaskIndex: taskIndex, lastTaskIndex: taskIndex}; |
| if (task.data["foreign"]) |
| element.classList.add(foreignStyle); |
| 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; |
| } |
| }, |
| |
| _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) |
| { |
| var anchor = element.enclosingNodeOrSelfWithClass("timeline-graph-bar"); |
| if (anchor && anchor._tasksInfo) |
| return anchor; |
| return element.enclosingNodeOrSelfWithClass("timeline-frame-strip"); |
| }, |
| |
| _mouseOut: function() |
| { |
| this._hideQuadHighlight(); |
| }, |
| |
| /** |
| * @param {?Event} e |
| */ |
| _mouseMove: function(e) |
| { |
| var rowElement = e.target.enclosingNodeOrSelfWithClass("timeline-tree-item"); |
| if (rowElement && rowElement.row && rowElement.row._record.highlightQuad && !this._recordingInProgress()) |
| this._highlightQuad(rowElement.row._record.highlightQuad); |
| else |
| this._hideQuadHighlight(); |
| |
| var taskBarElement = e.target.enclosingNodeOrSelfWithClass("timeline-graph-bar"); |
| if (taskBarElement && taskBarElement._tasksInfo) { |
| var offset = taskBarElement.offsetLeft; |
| this._timelineGrid.showCurtains(offset >= 0 ? offset : 0, taskBarElement.offsetWidth); |
| } else |
| this._timelineGrid.hideCurtains(); |
| }, |
| |
| /** |
| * @param {?Event} event |
| */ |
| _keyDown: function(event) |
| { |
| if (!this._lastSelectedRecord || event.shiftKey || event.metaKey || event.ctrlKey) |
| return; |
| |
| var record = this._lastSelectedRecord; |
| var recordsInWindow = this._presentationModel.filteredRecords(); |
| var index = recordsInWindow.indexOf(record); |
| if (index === -1) |
| index = 0; |
| |
| switch (event.keyIdentifier) { |
| case "Left": |
| if (record.parent) { |
| if ((!record.expandable || record.collapsed) && record.parent !== this._presentationModel.rootRecord()) { |
| this._selectRecord(record.parent); |
| } else { |
| record.collapsed = true; |
| record.clicked = true; |
| this._invalidateAndScheduleRefresh(true, true); |
| } |
| } |
| event.consume(true); |
| break; |
| case "Up": |
| if (--index < 0) |
| break; |
| this._selectRecord(recordsInWindow[index]); |
| event.consume(true); |
| break; |
| case "Right": |
| if (record.expandable && record.collapsed) { |
| record.collapsed = false; |
| record.clicked = true; |
| this._invalidateAndScheduleRefresh(true, true); |
| } else { |
| if (++index >= recordsInWindow.length) |
| break; |
| this._selectRecord(recordsInWindow[index]); |
| } |
| event.consume(true); |
| break; |
| case "Down": |
| if (++index >= recordsInWindow.length) |
| break; |
| this._selectRecord(recordsInWindow[index]); |
| event.consume(true); |
| break; |
| } |
| }, |
| |
| /** |
| * @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.classList.contains("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]; |
| this._searchableView.updateCurrentMatchIndex(index); |
| }, |
| |
| _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, true); |
| }, |
| |
| _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; |
| this._searchableView.updateSearchMatchesCount(matchesCount); |
| |
| var selectedIndex = matches.indexOf(this._selectedSearchResult); |
| if (shouldJump && selectedIndex === -1) |
| selectedIndex = 0; |
| this._selectSearchResult(selectedIndex); |
| } else { |
| this._searchableView.updateSearchMatchesCount(0); |
| 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); |
| }, |
| |
| /** |
| * @param {!WebInspector.Event} event |
| */ |
| _willReloadPage: function(event) |
| { |
| if (this._operationInProgress || this._userInitiatedRecording || !this.isShowing()) |
| return; |
| this._startRecording(false); |
| }, |
| |
| /** |
| * @param {!WebInspector.Event} event |
| */ |
| _loadEventFired: function(event) |
| { |
| if (!this._recordingInProgress() || this._userInitiatedRecording) |
| return; |
| this._stopRecording(); |
| }, |
| |
| __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; |
| }, |
| |
| /** |
| * @param {number} value |
| * @param {boolean=} hires |
| * @return {string} |
| */ |
| formatTime: function(value, hires) |
| { |
| return Number.secondsToString(value + this._minimumBoundary - this._model.minimumRecordTime(), hires); |
| }, |
| |
| maximumBoundary: function() |
| { |
| return this._maximumBoundary; |
| }, |
| |
| minimumBoundary: function() |
| { |
| return this._minimumBoundary; |
| }, |
| |
| zeroTime: function() |
| { |
| return this._model.minimumRecordTime(); |
| }, |
| |
| boundarySpan: function() |
| { |
| return this._maximumBoundary - this._minimumBoundary; |
| } |
| } |
| |
| /** |
| * @constructor |
| * @param {function(!WebInspector.TimelinePresentationModel.Record)} selectRecord |
| * @param {function()} scheduleRefresh |
| */ |
| WebInspector.TimelineRecordListRow = function(selectRecord, scheduleRefresh) |
| { |
| this.element = document.createElement("div"); |
| this.element.row = this; |
| this.element.style.cursor = "pointer"; |
| this.element.addEventListener("click", this._onClick.bind(this), false); |
| this.element.addEventListener("mouseover", this._onMouseOver.bind(this), false); |
| this.element.addEventListener("mouseout", this._onMouseOut.bind(this), false); |
| |
| // Warning is float right block, it goes first. |
| this._warningElement = this.element.createChild("div", "timeline-tree-item-warning hidden"); |
| |
| this._expandArrowElement = this.element.createChild("div", "timeline-tree-item-expand-arrow"); |
| this._expandArrowElement.addEventListener("click", this._onExpandClick.bind(this), false); |
| var iconElement = this.element.createChild("span", "timeline-tree-icon"); |
| this._typeElement = this.element.createChild("span", "type"); |
| |
| this._dataElement = this.element.createChild("span", "data dimmed"); |
| this._scheduleRefresh = scheduleRefresh; |
| this._selectRecord = selectRecord; |
| } |
| |
| WebInspector.TimelineRecordListRow.prototype = { |
| update: function(record, offset) |
| { |
| this._record = record; |
| this._offset = offset; |
| |
| this.element.className = "timeline-tree-item timeline-category-" + record.category.name; |
| var paddingLeft = 5; |
| var step = -3; |
| for (var currentRecord = record.parent ? record.parent.parent : null; currentRecord; currentRecord = currentRecord.parent) |
| paddingLeft += 12 / (Math.max(1, step++)); |
| this.element.style.paddingLeft = paddingLeft + "px"; |
| if (record.isBackground) |
| this.element.classList.add("background"); |
| |
| this._typeElement.textContent = record.title; |
| |
| if (this._dataElement.firstChild) |
| this._dataElement.removeChildren(); |
| |
| this._warningElement.enableStyleClass("hidden", !record.hasWarnings() && !record.childHasWarnings()); |
| this._warningElement.enableStyleClass("timeline-tree-item-child-warning", record.childHasWarnings() && !record.hasWarnings()); |
| |
| if (record.detailsNode()) |
| this._dataElement.appendChild(record.detailsNode()); |
| this._expandArrowElement.enableStyleClass("parent", record.children && record.children.length); |
| this._expandArrowElement.enableStyleClass("expanded", record.visibleChildrenCount); |
| this._record.setUserObject("WebInspector.TimelineRecordListRow", this); |
| }, |
| |
| 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 {!Event} event |
| */ |
| _onExpandClick: function(event) |
| { |
| this._record.collapsed = !this._record.collapsed; |
| this._record.clicked = true; |
| this._scheduleRefresh(); |
| event.consume(true); |
| }, |
| |
| /** |
| * @param {?Event} event |
| */ |
| _onClick: function(event) |
| { |
| this._selectRecord(this._record); |
| }, |
| |
| /** |
| * @param {boolean} selected |
| */ |
| renderAsSelected: function(selected) |
| { |
| this.element.enableStyleClass("selected", selected); |
| }, |
| |
| /** |
| * @param {?Event} event |
| */ |
| _onMouseOver: function(event) |
| { |
| this.element.classList.add("hovered"); |
| var graphRow = /** @type {!WebInspector.TimelineRecordGraphRow} */ (this._record.getUserObject("WebInspector.TimelineRecordGraphRow")); |
| graphRow.element.classList.add("hovered"); |
| }, |
| |
| /** |
| * @param {?Event} event |
| */ |
| _onMouseOut: function(event) |
| { |
| this.element.classList.remove("hovered"); |
| var graphRow = /** @type {!WebInspector.TimelineRecordGraphRow} */ (this._record.getUserObject("WebInspector.TimelineRecordGraphRow")); |
| graphRow.element.classList.remove("hovered"); |
| } |
| } |
| |
| /** |
| * @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 |
| * @param {function(!WebInspector.TimelinePresentationModel.Record)} selectRecord |
| * @param {function()} scheduleRefresh |
| */ |
| WebInspector.TimelineRecordGraphRow = function(graphContainer, selectRecord, scheduleRefresh) |
| { |
| this.element = document.createElement("div"); |
| this.element.row = this; |
| this.element.addEventListener("mouseover", this._onMouseOver.bind(this), false); |
| this.element.addEventListener("mouseout", this._onMouseOut.bind(this), false); |
| this.element.addEventListener("click", this._onClick.bind(this), false); |
| |
| 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._selectRecord = selectRecord; |
| this._scheduleRefresh = scheduleRefresh; |
| } |
| |
| WebInspector.TimelineRecordGraphRow.prototype = { |
| update: function(record, calculator, expandOffset, index) |
| { |
| this._record = record; |
| this.element.className = "timeline-graph-side timeline-category-" + record.category.name; |
| if (record.isBackground) |
| this.element.classList.add("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); |
| |
| this._record.setUserObject("WebInspector.TimelineRecordGraphRow", this); |
| }, |
| |
| /** |
| * @param {?Event} event |
| */ |
| _onClick: function(event) |
| { |
| // check if we click arrow and expand if yes. |
| if (this._expandElement._arrow.containsEventPoint(event)) |
| this._expand(); |
| this._selectRecord(this._record); |
| }, |
| |
| /** |
| * @param {boolean} selected |
| */ |
| renderAsSelected: function(selected) |
| { |
| this.element.enableStyleClass("selected", selected); |
| }, |
| |
| _expand: function() |
| { |
| this._record.collapsed = !this._record.collapsed; |
| this._record.clicked = true; |
| this._scheduleRefresh(); |
| }, |
| |
| /** |
| * @param {?Event} event |
| */ |
| _onMouseOver: function(event) |
| { |
| this.element.classList.add("hovered"); |
| var listRow = /** @type {!WebInspector.TimelineRecordListRow} */ (this._record.getUserObject("WebInspector.TimelineRecordListRow")); |
| listRow.element.classList.add("hovered"); |
| }, |
| |
| /** |
| * @param {?Event} event |
| */ |
| _onMouseOut: function(event) |
| { |
| this.element.classList.remove("hovered"); |
| var listRow = /** @type {!WebInspector.TimelineRecordListRow} */ (this._record.getUserObject("WebInspector.TimelineRecordListRow")); |
| listRow.element.classList.remove("hovered"); |
| }, |
| |
| 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._arrow = 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.classList.add("timeline-expandable-expanded"); |
| this._element.classList.remove("timeline-expandable-collapsed"); |
| } else { |
| this._element.style.height = rowHeight + "px"; |
| this._element.classList.add("timeline-expandable-collapsed"); |
| this._element.classList.remove("timeline-expandable-expanded"); |
| } |
| this._element.classList.remove("hidden"); |
| } else |
| this._element.classList.add("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); |
| } |
| } |
| |
| /** |
| * @constructor |
| * @extends {WebInspector.View} |
| */ |
| WebInspector.TimelineDetailsView = function() |
| { |
| WebInspector.View.call(this); |
| this.element = document.createElement("div"); |
| this.element.className = "timeline-details-view fill vbox"; |
| this._titleElement = this.element.createChild("div", "timeline-details-view-title"); |
| this._titleElement.textContent = WebInspector.UIString("DETAILS"); |
| this._contentElement = this.element.createChild("div", "timeline-details-view-body"); |
| } |
| |
| WebInspector.TimelineDetailsView.prototype = { |
| /** |
| * @return {!Element} |
| */ |
| titleElement: function() |
| { |
| return this._titleElement; |
| }, |
| |
| /** |
| * @param {string} title |
| * @param {!Node} node |
| */ |
| setContent: function(title, node) |
| { |
| this._titleElement.textContent = WebInspector.UIString("DETAILS: %s", title); |
| this._contentElement.removeChildren(); |
| this._contentElement.appendChild(node); |
| }, |
| |
| /** |
| * @param {boolean} vertical |
| */ |
| setVertical: function(vertical) |
| { |
| this._contentElement.enableStyleClass("hbox", !vertical); |
| this._contentElement.enableStyleClass("vbox", vertical); |
| }, |
| |
| __proto__: WebInspector.View.prototype |
| } |