/* * Copyright (C) 2013, 2015 Apple 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: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. 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. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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. */ WebInspector.TimelineSidebarPanel = class TimelineSidebarPanel extends WebInspector.NavigationSidebarPanel { constructor(contentBrowser) { super("timeline", WebInspector.UIString("Timelines")); this.contentBrowser = contentBrowser; this._timelineEventsTitleBarContainer = document.createElement("div"); this._timelineEventsTitleBarContainer.classList.add(WebInspector.TimelineSidebarPanel.TitleBarStyleClass); this._timelineEventsTitleBarContainer.classList.add(WebInspector.TimelineSidebarPanel.TimelineEventsTitleBarStyleClass); this._timelineEventsTitleBarElement = document.createElement("div"); this._timelineEventsTitleBarElement.classList.add(WebInspector.TimelineSidebarPanel.TitleBarTextStyleClass); this._timelineEventsTitleBarContainer.appendChild(this._timelineEventsTitleBarElement); this._timelineEventsTitleBarScopeContainer = document.createElement("div"); this._timelineEventsTitleBarScopeContainer.classList.add(WebInspector.TimelineSidebarPanel.TitleBarScopeBarStyleClass); this._timelineEventsTitleBarContainer.appendChild(this._timelineEventsTitleBarScopeContainer); this.element.insertBefore(this._timelineEventsTitleBarContainer, this.element.firstChild); this.contentTreeOutlineLabel = ""; this._timelinesContentContainerElement = document.createElement("div"); this._timelinesContentContainerElement.classList.add(WebInspector.TimelineSidebarPanel.TimelinesContentContainerStyleClass); this.element.insertBefore(this._timelinesContentContainerElement, this.element.firstChild); this._displayedRecording = null; this._displayedContentView = null; this._viewMode = null; this._previousSelectedTimelineType = null; // Maintain an invisible tree outline containing tree elements for all recordings. // The visible recording's tree element is selected when the content view changes. this._recordingTreeElementMap = new Map; this._recordingsTreeOutline = this.createContentTreeOutline(true, true); this._recordingsTreeOutline.disclosureButtons = false; this._recordingsTreeOutline.hidden = true; this._recordingsTreeOutline.addEventListener(WebInspector.TreeOutline.Event.SelectionDidChange, this._recordingsTreeSelectionDidChange, this); this._timelinesContentContainerElement.appendChild(this._recordingsTreeOutline.element); // Maintain a tree outline with tree elements for each timeline of the selected recording. this._timelinesTreeOutline = this.createContentTreeOutline(true, true); this._timelinesTreeOutline.disclosureButtons = false; this._timelinesTreeOutline.large = true; this._timelinesTreeOutline.addEventListener(WebInspector.TreeOutline.Event.SelectionDidChange, this._timelinesTreeSelectionDidChange, this); this._timelinesContentContainerElement.appendChild(this._timelinesTreeOutline.element); this._timelineTreeElementMap = new Map; this._basicTitleBar = document.createElement("div"); this._basicTitleBar.textContent = WebInspector.UIString("Timelines"); this._basicTitleBar.classList.add(WebInspector.TimelineSidebarPanel.TitleBarStyleClass); this._basicTitleBar.classList.add(WebInspector.TimelineSidebarPanel.TimelinesTitleBarStyleClass); this.element.insertBefore(this._basicTitleBar, this.element.firstChild); if (WebInspector.FPSInstrument.supported()) { let timelinesNavigationItem = new WebInspector.RadioButtonNavigationItem(WebInspector.TimelineOverview.ViewMode.Timelines, WebInspector.UIString("Timelines")) let renderingFramesNavigationItem = new WebInspector.RadioButtonNavigationItem(WebInspector.TimelineOverview.ViewMode.RenderingFrames, WebInspector.UIString("Rendering Frames")) this._viewModeNavigationBar = new WebInspector.NavigationBar(null, [timelinesNavigationItem, renderingFramesNavigationItem], "tablist"); this._viewModeNavigationBar.addEventListener(WebInspector.NavigationBar.Event.NavigationItemSelected, this._viewModeSelected, this); this._renderingFramesTitleBar = document.createElement("div"); this._renderingFramesTitleBar.className = "navigation-bar-container"; this._renderingFramesTitleBar.appendChild(this._viewModeNavigationBar.element); this._renderingFramesTitleBar.hidden = true; this.element.insertBefore(this._renderingFramesTitleBar, this.element.firstChild); this._chartColors = new Map; this._chartColors.set(WebInspector.RenderingFrameTimelineRecord.TaskType.Script, "rgb(153, 113, 185)"); this._chartColors.set(WebInspector.RenderingFrameTimelineRecord.TaskType.Layout, "rgb(212, 108, 108)"); this._chartColors.set(WebInspector.RenderingFrameTimelineRecord.TaskType.Paint, "rgb(152, 188, 77)"); this._chartColors.set(WebInspector.RenderingFrameTimelineRecord.TaskType.Other, "rgb(221, 221, 221)"); this._frameSelectionChartRow = new WebInspector.ChartDetailsSectionRow(this, 74, 0.5); this._frameSelectionChartRow.addEventListener(WebInspector.ChartDetailsSectionRow.Event.LegendItemChecked, this._frameSelectionLegendItemChecked, this); for (let key in WebInspector.RenderingFrameTimelineRecord.TaskType) { let taskType = WebInspector.RenderingFrameTimelineRecord.TaskType[key]; let label = WebInspector.RenderingFrameTimelineRecord.displayNameForTaskType(taskType); let color = this._chartColors.get(taskType); let checkbox = taskType !== WebInspector.RenderingFrameTimelineRecord.TaskType.Other; this._frameSelectionChartRow.addItem(taskType, label, 0, color, checkbox, true); } this._renderingFrameTaskFilter = new Set; var chartGroup = new WebInspector.DetailsSectionGroup([this._frameSelectionChartRow]); this._frameSelectionChartSection = new WebInspector.DetailsSection("frames-selection-chart", WebInspector.UIString("Selected Frames"), [chartGroup], null, true); this._timelinesContentContainerElement.appendChild(this._frameSelectionChartSection.element); } this._toggleRecordingShortcut = new WebInspector.KeyboardShortcut(null, WebInspector.KeyboardShortcut.Key.Space, this._toggleRecordingOnSpacebar.bind(this)); this._toggleRecordingShortcut.implicitlyPreventsDefault = false; this._toggleRecordingShortcut.disabled = true; this._toggleNewRecordingShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.Shift, WebInspector.KeyboardShortcut.Key.Space, this._toggleNewRecordingOnSpacebar.bind(this)); this._toggleNewRecordingShortcut.implicitlyPreventsDefault = false; this._toggleNewRecordingShortcut.disabled = true; this._recordingNavigationBar = new WebInspector.NavigationBar; this.element.insertBefore(this._recordingNavigationBar.element, this.element.firstChild); let toolTip = WebInspector.UIString("Start recording (%s)\nCreate new recording (%s)").format(this._toggleRecordingShortcut.displayName, this._toggleNewRecordingShortcut.displayName); let altToolTip = WebInspector.UIString("Stop recording (%s)").format(this._toggleRecordingShortcut.displayName); this._recordButton = new WebInspector.ToggleButtonNavigationItem("record-start-stop", toolTip, altToolTip, "Images/Record.svg", "Images/Stop.svg", 13, 13); this._recordButton.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._recordButtonClicked, this); this._recordButton.element.addEventListener("mouseover", this._recordButtonMousedOver.bind(this)); this._recordButton.element.addEventListener("mouseout", this._recordButtonMousedOut.bind(this)); this._recordingNavigationBar.addNavigationItem(this._recordButton); this._recordingStatusItem = new WebInspector.FlexibleSpaceNavigationItem; this._recordingNavigationBar.addNavigationItem(this._recordingStatusItem); WebInspector.showReplayInterfaceSetting.addEventListener(WebInspector.Setting.Event.Changed, this._updateReplayInterfaceVisibility, this); // We always create a replay navigation bar; its visibility is controlled by WebInspector.showReplayInterfaceSetting. this._replayNavigationBar = new WebInspector.NavigationBar; this.element.appendChild(this._replayNavigationBar.element); toolTip = WebInspector.UIString("Begin Capturing"); altToolTip = WebInspector.UIString("End Capturing"); this._replayCaptureButtonItem = new WebInspector.ActivateButtonNavigationItem("replay-capture", toolTip, altToolTip, "Images/Circle.svg", 16, 16); this._replayCaptureButtonItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._replayCaptureButtonClicked, this); this._replayCaptureButtonItem.enabled = true; this._replayNavigationBar.addNavigationItem(this._replayCaptureButtonItem); toolTip = WebInspector.UIString("Start Playback"); altToolTip = WebInspector.UIString("Pause Playback"); this._replayPauseResumeButtonItem = new WebInspector.ToggleButtonNavigationItem("replay-pause-resume", toolTip, altToolTip, "Images/Resume.svg", "Images/Pause.svg", 15, 15); this._replayPauseResumeButtonItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._replayPauseResumeButtonClicked, this); this._replayPauseResumeButtonItem.enabled = false; this._replayNavigationBar.addNavigationItem(this._replayPauseResumeButtonItem); WebInspector.replayManager.addEventListener(WebInspector.ReplayManager.Event.CaptureStarted, this._captureStarted, this); WebInspector.replayManager.addEventListener(WebInspector.ReplayManager.Event.CaptureStopped, this._captureStopped, this); this._recordingNavigationBar.element.oncontextmenu = this._contextMenuNavigationBarOrStatusBar.bind(this); this._replayNavigationBar.element.oncontextmenu = this._contextMenuNavigationBarOrStatusBar.bind(this); this._updateReplayInterfaceVisibility(); WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.RecordingCreated, this._recordingCreated, this); WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.RecordingLoaded, this._recordingLoaded, this); this.contentBrowser.addEventListener(WebInspector.ContentBrowser.Event.CurrentContentViewDidChange, this._contentBrowserCurrentContentViewDidChange, this); WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.CapturingStarted, this._capturingStartedOrStopped, this); WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.CapturingStopped, this._capturingStartedOrStopped, this); for (var recording of WebInspector.timelineManager.recordings) this._addRecording(recording); this._recordingCountChanged(); if (WebInspector.timelineManager.activeRecording) this._recordingLoaded(); } // Public get minimumWidth() { if (WebInspector.FPSInstrument.supported()) return Math.max(this._replayNavigationBar.minimumWidth, this._viewModeNavigationBar.minimumWidth); return this._replayNavigationBar.minimumWidth; } shown() { super.shown(); if (this._displayedContentView) this.contentBrowser.showContentView(this._displayedContentView); this._toggleRecordingShortcut.disabled = false; this._toggleNewRecordingShortcut.disabled = false; this._updateTimelineOverviewHeight(); } hidden() { super.hidden(); this._toggleRecordingShortcut.disabled = true; this._toggleNewRecordingShortcut.disabled = true; } closed() { super.closed(); WebInspector.showReplayInterfaceSetting.removeEventListener(null, null, this); WebInspector.replayManager.removeEventListener(null, null, this); WebInspector.timelineManager.removeEventListener(null, null, this); WebInspector.timelineManager.reset(); } showDefaultContentView() { if (this._displayedContentView) this.showTimelineOverview(); } treeElementForRepresentedObject(representedObject) { if (representedObject instanceof WebInspector.TimelineRecording) return this._recordingTreeElementMap.get(representedObject); // This fails if the timeline does not belong to the selected recording. if (representedObject instanceof WebInspector.Timeline) { var foundTreeElement = this._timelineTreeElementMap.get(representedObject); if (foundTreeElement) return foundTreeElement; } // The main resource is used as the representedObject instead of Frame in our tree. if (representedObject instanceof WebInspector.Frame) representedObject = representedObject.mainResource; var foundTreeElement = this.contentTreeOutline.getCachedTreeElement(representedObject); if (foundTreeElement) return foundTreeElement; // Look for TreeElements loosely based on represented objects that can contain the represented // object we are really looking for. This allows a SourceCodeTimelineTreeElement or a // TimelineRecordTreeElement to stay selected when the Resource it represents is showing. function looselyCompareRepresentedObjects(candidateTreeElement) { if (!candidateTreeElement) return false; var candidateRepresentedObject = candidateTreeElement.representedObject; if (candidateRepresentedObject instanceof WebInspector.SourceCodeTimeline) { if (candidateRepresentedObject.sourceCode === representedObject) return true; return false; } else if (candidateRepresentedObject instanceof WebInspector.Timeline && representedObject instanceof WebInspector.Timeline) { // Reopen to the same timeline, even if a different parent recording is currently shown. if (candidateRepresentedObject.type === representedObject.type) return true; return false; } if (candidateRepresentedObject instanceof WebInspector.TimelineRecord) { if (!candidateRepresentedObject.sourceCodeLocation) return false; if (candidateRepresentedObject.sourceCodeLocation.sourceCode === representedObject) return true; return false; } if (candidateRepresentedObject instanceof WebInspector.ProfileNode) return false; console.error("Unknown TreeElement", candidateTreeElement); return false; } // Check the selected tree element first so we don't need to do a longer search and it is // likely to be the best candidate for the current view. if (looselyCompareRepresentedObjects(this.contentTreeOutline.selectedTreeElement)) return this.contentTreeOutline.selectedTreeElement; var currentTreeElement = this._contentTreeOutline.children[0]; while (currentTreeElement && !currentTreeElement.root) { if (looselyCompareRepresentedObjects(currentTreeElement)) return currentTreeElement; currentTreeElement = currentTreeElement.traverseNextTreeElement(false, null, false); } return null; } get contentTreeOutlineLabel() { return this._timelineEventsTitleBarElement.textContent; } set contentTreeOutlineLabel(label) { label = label || WebInspector.UIString("Timeline Events"); this._timelineEventsTitleBarElement.textContent = label; this.filterBar.placeholder = WebInspector.UIString("Filter %s").format(label); } get contentTreeOutlineScopeBar() { return this._timelineEventsTitleBarScopeContainer.children; } set contentTreeOutlineScopeBar(scopeBar) { this._timelineEventsTitleBarScopeContainer.removeChildren(); if (!scopeBar || !scopeBar.element) return; this._timelineEventsTitleBarScopeContainer.appendChild(scopeBar.element); } showTimelineOverview() { if (this._timelinesTreeOutline.selectedTreeElement) this._timelinesTreeOutline.selectedTreeElement.deselect(); this._displayedContentView.showOverviewTimelineView(); this.contentBrowser.showContentView(this._displayedContentView); } showTimelineViewForTimeline(timeline) { console.assert(timeline instanceof WebInspector.Timeline, timeline); console.assert(this._displayedRecording.timelines.has(timeline.type), "Cannot show timeline because it does not belong to the shown recording.", timeline.type); if (this._timelineTreeElementMap.has(timeline)) { // Defer showing the relevant timeline to the onselect handler of the timelines tree element. var wasSelectedByUser = true; var shouldSuppressOnSelect = false; this._timelineTreeElementMap.get(timeline).select(true, wasSelectedByUser, shouldSuppressOnSelect, true); } else { this._displayedContentView.showTimelineViewForTimeline(timeline); this.contentBrowser.showContentView(this._displayedContentView); } } updateFrameSelection(startFrameIndex, endFrameIndex) { console.assert(startFrameIndex <= endFrameIndex, startFrameIndex, endFrameIndex); if (this._startFrameIndex === startFrameIndex && this._endFrameIndex === endFrameIndex) return; this._startFrameIndex = startFrameIndex; this._endFrameIndex = endFrameIndex; this._refreshFrameSelectionChart(); } formatChartValue(value) { return this._frameSelectionChartRow.total === 0 ? "" : Number.secondsToString(value); } // Protected representedObjectWasFiltered(representedObject, filtered) { super.representedObjectWasFiltered(representedObject, filtered); if (representedObject instanceof WebInspector.TimelineRecord) this._displayedContentView.recordWasFiltered(representedObject, filtered); } updateFilter() { super.updateFilter(); this._displayedContentView.filterDidChange(); } shouldFilterPopulate() { return false; } hasCustomFilters() { return true; } matchTreeElementAgainstCustomFilters(treeElement) { if (!this._displayedContentView) return true; if (this._viewMode === WebInspector.TimelineOverview.ViewMode.RenderingFrames && this._renderingFrameTaskFilter.size) { while (treeElement && !(treeElement.record instanceof WebInspector.TimelineRecord)) treeElement = treeElement.parent; console.assert(treeElement, "Cannot apply task filter: no TimelineRecord found."); if (!treeElement) return false; let records; if (treeElement.record instanceof WebInspector.RenderingFrameTimelineRecord) records = treeElement.record.children; else records = [treeElement.record]; const filtered = records.every(function(record) { var taskType = WebInspector.RenderingFrameTimelineRecord.taskTypeForTimelineRecord(record); return this._renderingFrameTaskFilter.has(taskType); }, this); if (filtered) return false; } return this._displayedContentView.matchTreeElementAgainstCustomFilters(treeElement); } treeElementAddedOrChanged(treeElement) { if (treeElement.status) return; if (!treeElement.treeOutline || typeof treeElement.treeOutline.__canShowContentViewForTreeElement !== "function") return; if (!treeElement.treeOutline.__canShowContentViewForTreeElement(treeElement)) return; var fragment = document.createDocumentFragment(); var closeButton = new WebInspector.TreeElementStatusButton(useSVGSymbol("Images/Close.svg", null, WebInspector.UIString("Close resource view"))); closeButton.element.classList.add("close"); closeButton.addEventListener(WebInspector.TreeElementStatusButton.Event.Clicked, this._treeElementCloseButtonClicked, this); fragment.appendChild(closeButton.element); var goToButton = new WebInspector.TreeElementStatusButton(WebInspector.createGoToArrowButton()); goToButton.__treeElement = treeElement; goToButton.addEventListener(WebInspector.TreeElementStatusButton.Event.Clicked, this._treeElementGoToArrowWasClicked, this); fragment.appendChild(goToButton.element); treeElement.status = fragment; } canShowDifferentContentView() { if (this._clickedTreeElementGoToArrow) return true; if (this.contentBrowser.currentContentView instanceof WebInspector.TimelineRecordingContentView) return false; return !this.restoringState || !this._restoredShowingTimelineRecordingContentView; } saveStateToCookie(cookie) { console.assert(cookie); cookie[WebInspector.TimelineSidebarPanel.ShowingTimelineRecordingContentViewCookieKey] = this.contentBrowser.currentContentView instanceof WebInspector.TimelineRecordingContentView; if (this._viewMode === WebInspector.TimelineOverview.ViewMode.RenderingFrames) cookie[WebInspector.TimelineSidebarPanel.SelectedTimelineViewIdentifierCookieKey] = WebInspector.TimelineRecord.Type.RenderingFrame; else { var selectedTreeElement = this._timelinesTreeOutline.selectedTreeElement; if (selectedTreeElement) cookie[WebInspector.TimelineSidebarPanel.SelectedTimelineViewIdentifierCookieKey] = selectedTreeElement.representedObject.type; else cookie[WebInspector.TimelineSidebarPanel.SelectedTimelineViewIdentifierCookieKey] = WebInspector.TimelineSidebarPanel.OverviewTimelineIdentifierCookieValue; } super.saveStateToCookie(cookie); } restoreStateFromCookie(cookie, relaxedMatchDelay) { console.assert(cookie); // The _displayedContentView is not ready on initial load, so delay the restore. // This matches the delayed work in the WebInspector.TimelineSidebarPanel constructor. if (!this._displayedContentView) { setTimeout(this.restoreStateFromCookie.bind(this, cookie, relaxedMatchDelay), 0); return; } this._restoredShowingTimelineRecordingContentView = cookie[WebInspector.TimelineSidebarPanel.ShowingTimelineRecordingContentViewCookieKey]; var selectedTimelineViewIdentifier = cookie[WebInspector.TimelineSidebarPanel.SelectedTimelineViewIdentifierCookieKey]; if (selectedTimelineViewIdentifier === WebInspector.TimelineRecord.Type.RenderingFrame && !WebInspector.FPSInstrument.supported()) selectedTimelineViewIdentifier = null; if (selectedTimelineViewIdentifier && this._displayedRecording.timelines.has(selectedTimelineViewIdentifier)) this.showTimelineViewForTimeline(this._displayedRecording.timelines.get(selectedTimelineViewIdentifier)); else this.showTimelineOverview(); // Don't call NavigationSidebarPanel.restoreStateFromCookie, because it tries to match based // on type only as a last resort. This would cause the first recording to be reselected on reload. } // Private _toggleRecordingOnSpacebar(event) { if (WebInspector.isEventTargetAnEditableField(event)) return; this._toggleRecording(); event.preventDefault(); } _toggleNewRecordingOnSpacebar(event) { if (WebInspector.isEventTargetAnEditableField(event)) return; this._toggleRecording(true); event.preventDefault(); } _toggleRecording(shouldCreateRecording) { if (WebInspector.timelineManager.isCapturing()) { WebInspector.timelineManager.stopCapturing(); } else { WebInspector.timelineManager.startCapturing(shouldCreateRecording); // Show the timeline to which events will be appended. this._recordingLoaded(); } } _treeElementGoToArrowWasClicked(event) { this._clickedTreeElementGoToArrow = true; var treeElement = event.target.__treeElement; console.assert(treeElement instanceof WebInspector.TreeElement); treeElement.select(true, true); this._clickedTreeElementGoToArrow = false; } _treeElementCloseButtonClicked(event) { var currentTimelineView = this._displayedContentView ? this._displayedContentView.currentTimelineView : null; if (currentTimelineView && currentTimelineView.representedObject instanceof WebInspector.Timeline) this.showTimelineViewForTimeline(currentTimelineView.representedObject); else this.showTimelineOverview(); } _recordingsTreeSelectionDidChange(event) { let treeElement = event.data.selectedElement; if (!treeElement) return; console.assert(treeElement.representedObject instanceof WebInspector.TimelineRecording); this._recordingSelected(treeElement.representedObject); } _renderingFrameTimelineTimesUpdated(event) { if (this._viewMode === WebInspector.TimelineOverview.ViewMode.RenderingFrames) this._refreshFrameSelectionChart(); } _timelinesTreeSelectionDidChange(event) { let treeElement = event.data.selectedElement; if (!treeElement) return; console.assert(this._timelineTreeElementMap.get(treeElement.representedObject) === treeElement, treeElement); // If not selected by user, then this selection merely synced the tree element with the content view's contents. let selectedByUser = event.data.selectedByUser; if (!selectedByUser) { console.assert(this._displayedContentView.currentTimelineView.representedObject === treeElement.representedObject); return; } let timeline = treeElement.representedObject; console.assert(timeline instanceof WebInspector.Timeline, timeline); console.assert(this._displayedRecording.timelines.get(timeline.type) === timeline, timeline); this._previousSelectedTimelineType = timeline.type; this._displayedContentView.showTimelineViewForTimeline(timeline); this.contentBrowser.showContentView(this._displayedContentView); } _contentBrowserCurrentContentViewDidChange(event) { let didShowTimelineRecordingContentView = this.contentBrowser.currentContentView instanceof WebInspector.TimelineRecordingContentView; this.element.classList.toggle(WebInspector.TimelineSidebarPanel.TimelineRecordingContentViewShowingStyleClass, didShowTimelineRecordingContentView); if (!didShowTimelineRecordingContentView) return; this._updateViewModeIfNeeded(); this._timelineCountChanged(); this.updateFilter(); let timelineView = this.contentBrowser.currentContentView.currentTimelineView; if (timelineView.representedObject.type === WebInspector.TimelineRecord.Type.RenderingFrame) this._refreshFrameSelectionChart(); } _capturingStartedOrStopped(event) { let isCapturing = WebInspector.timelineManager.isCapturing(); this._updateRecordButton(isCapturing); } _recordingCreated(event) { this._addRecording(event.data.recording) this._recordingCountChanged(); } _addRecording(recording) { console.assert(recording instanceof WebInspector.TimelineRecording, recording); var recordingTreeElement = new WebInspector.GeneralTreeElement(WebInspector.TimelineSidebarPanel.StopwatchIconStyleClass, recording.displayName, null, recording); this._recordingTreeElementMap.set(recording, recordingTreeElement); this._recordingsTreeOutline.appendChild(recordingTreeElement); } _recordingCountChanged() { var previousTreeElement = null; for (var treeElement of this._recordingTreeElementMap.values()) { if (previousTreeElement) { previousTreeElement.nextSibling = treeElement; treeElement.previousSibling = previousTreeElement; } previousTreeElement = treeElement; } } _recordingSelected(recording) { console.assert(recording instanceof WebInspector.TimelineRecording, recording); var oldRecording = this._displayedRecording || null; if (oldRecording) { oldRecording.removeEventListener(WebInspector.TimelineRecording.Event.InstrumentAdded, this._instrumentAdded, this); oldRecording.removeEventListener(WebInspector.TimelineRecording.Event.InstrumentRemoved, this._instrumentRemoved, this); // Destroy tree elements in one operation to avoid unnecessary fixups. this._timelinesTreeOutline.removeChildren(); this._timelineTreeElementMap.clear(); } this._clearInstruments(); this._displayedRecording = recording; this._displayedRecording.addEventListener(WebInspector.TimelineRecording.Event.InstrumentAdded, this._instrumentAdded, this); this._displayedRecording.addEventListener(WebInspector.TimelineRecording.Event.InstrumentRemoved, this._instrumentRemoved, this); for (let instrument of recording.instruments) this._instrumentAdded(instrument); // Save the current state incase we need to restore it to a new recording. var cookie = {}; this.saveStateToCookie(cookie); if (this._displayedContentView) this._displayedContentView.removeEventListener(WebInspector.ContentView.Event.NavigationItemsDidChange, this._updateViewModeIfNeeded, this); // Try to get the recording content view if it exists already, if it does we don't want to restore the cookie. var onlyExisting = true; this._displayedContentView = this.contentBrowser.contentViewForRepresentedObject(this._displayedRecording, onlyExisting, {timelineSidebarPanel: this}); if (this._displayedContentView) { this._displayedContentView.addEventListener(WebInspector.ContentView.Event.NavigationItemsDidChange, this._updateViewModeIfNeeded, this); // Show the timeline that was being shown to update the sidebar tree state. var currentTimelineView = this._displayedContentView.currentTimelineView; if (currentTimelineView && currentTimelineView.representedObject instanceof WebInspector.Timeline) this.showTimelineViewForTimeline(currentTimelineView.representedObject); else this.showTimelineOverview(); this.updateFilter(); return; } onlyExisting = false; this._displayedContentView = this.contentBrowser.contentViewForRepresentedObject(this._displayedRecording, onlyExisting, {timelineSidebarPanel: this}); if (this._displayedContentView) this._displayedContentView.addEventListener(WebInspector.ContentView.Event.NavigationItemsDidChange, this._updateViewModeIfNeeded, this); // Restore the cookie to carry over the previous recording view state to the new recording. this.restoreStateFromCookie(cookie); this.updateFilter(); } _recordingLoaded(event) { this._recordingSelected(WebInspector.timelineManager.activeRecording); } _clearInstruments() { this._timelineTreeElementMap.clear(); if (WebInspector.FPSInstrument.supported()) this._removedFPSInstrument(); } _instrumentAdded(instrumentOrEvent) { let instrument = instrumentOrEvent instanceof WebInspector.Instrument ? instrumentOrEvent : instrumentOrEvent.data.instrument; console.assert(instrument instanceof WebInspector.Instrument, instrument); let timeline = this._displayedRecording.timelineForInstrument(instrument); console.assert(!this._timelineTreeElementMap.has(timeline), timeline); if (timeline.type === WebInspector.TimelineRecord.Type.RenderingFrame) { this._addedFPSInstrument(); timeline.addEventListener(WebInspector.Timeline.Event.TimesUpdated, this._renderingFrameTimelineTimesUpdated, this); return; } let displayName = WebInspector.TimelineTabContentView.displayNameForTimeline(timeline); let iconClassName = WebInspector.TimelineTabContentView.iconClassNameForTimeline(timeline); let genericClassName = WebInspector.TimelineTabContentView.genericClassNameForTimeline(timeline); let timelineTreeElement = new WebInspector.GeneralTreeElement([iconClassName, genericClassName, WebInspector.TimelineSidebarPanel.LargeIconStyleClass], displayName, null, timeline); let tooltip = WebInspector.UIString("Close %s timeline view").format(displayName); let button = new WebInspector.TreeElementStatusButton(useSVGSymbol("Images/CloseLarge.svg", "close-button", tooltip)); button.addEventListener(WebInspector.TreeElementStatusButton.Event.Clicked, this.showTimelineOverview, this); timelineTreeElement.status = button.element; this._timelinesTreeOutline.appendChild(timelineTreeElement); this._timelineTreeElementMap.set(timeline, timelineTreeElement); this._timelineCountChanged(); } _instrumentRemoved(event) { let instrument = event.data.instrument; console.assert(instrument instanceof WebInspector.Instrument, instrument); let timeline = this._displayedRecording.timelineForInstrument(instrument); if (timeline.type === WebInspector.TimelineRecord.Type.RenderingFrame) { this._removedFPSInstrument(); timeline.removeEventListener(WebInspector.Timeline.Event.TimesUpdated, this._renderingFrameTimelineTimesUpdated, this); return; } console.assert(this._timelineTreeElementMap.has(timeline), timeline); let timelineTreeElement = this._timelineTreeElementMap.take(timeline); let shouldSuppressOnDeselect = false; let shouldSuppressSelectSibling = true; this._timelinesTreeOutline.removeChild(timelineTreeElement, shouldSuppressOnDeselect, shouldSuppressSelectSibling); this._timelineTreeElementMap.delete(timeline); this._timelineCountChanged(); } _addedFPSInstrument() { this._basicTitleBar.hidden = true; this._renderingFramesTitleBar.hidden = false; } _removedFPSInstrument() { this._basicTitleBar.hidden = false; this._renderingFramesTitleBar.hidden = true; } _timelineCountChanged() { var previousTreeElement = null; for (var treeElement of this._timelineTreeElementMap.values()) { if (previousTreeElement) { previousTreeElement.nextSibling = treeElement; treeElement.previousSibling = previousTreeElement; } previousTreeElement = treeElement; } this._updateTimelineOverviewHeight(); } _updateTimelineOverviewHeight() { const eventTitleBarOffset = 58; const contentElementOffset = 81; if (!this._displayedContentView) return; let overviewHeight = this._displayedContentView.timelineOverviewHeight; this._timelineEventsTitleBarContainer.style.top = (overviewHeight + eventTitleBarOffset) + "px"; this.contentView.element.style.top = (overviewHeight + contentElementOffset) + "px"; } _recordButtonClicked(event) { let isCapturing = !WebInspector.timelineManager.isCapturing(); this._updateRecordButton(isCapturing); this._toggleRecording(event.shiftKey); } _recordButtonMousedOver(event) { if (WebInspector.timelineManager.isCapturing()) this._recordingStatusItem.element.textContent = WebInspector.UIString("Stop Recording"); else this._recordingStatusItem.element.textContent = WebInspector.UIString("Start Recording"); } _recordButtonMousedOut(event) { if (WebInspector.timelineManager.isCapturing()) this._recordingStatusItem.element.textContent = WebInspector.UIString("Recording"); else this._recordingStatusItem.element.textContent = ""; } _updateRecordButton(isCapturing) { this._recordingStatusItem.element.textContent = isCapturing ? WebInspector.UIString("Recording") : ""; this._recordButton.toggled = isCapturing; } _viewModeSelected(event) { console.assert(event.target.selectedNavigationItem); if (!event.target.selectedNavigationItem) return; let selectedNavigationItem = event.target.selectedNavigationItem; let newViewMode = selectedNavigationItem.identifier; if (this._viewMode === newViewMode) return; let timelineType = this._previousSelectedTimelineType; if (newViewMode === WebInspector.TimelineOverview.ViewMode.RenderingFrames) { this._previousSelectedTimelineType = this._timelinesTreeOutline.selectedTreeElement ? this._timelinesTreeOutline.selectedTreeElement.representedObject.type : null; timelineType = WebInspector.TimelineRecord.Type.RenderingFrame; } if (timelineType) { console.assert(this._displayedRecording.timelines.has(timelineType), timelineType); this.showTimelineViewForTimeline(this._displayedRecording.timelines.get(timelineType)); return; } this.showTimelineOverview(); } _viewModeForTimeline(timeline) { if (timeline && timeline.type === WebInspector.TimelineRecord.Type.RenderingFrame) return WebInspector.TimelineOverview.ViewMode.RenderingFrames; return WebInspector.TimelineOverview.ViewMode.Timelines; } _frameSelectionLegendItemChecked(event) { if (event.data.checked) this._renderingFrameTaskFilter.delete(event.data.id); else this._renderingFrameTaskFilter.add(event.data.id); this.updateFilter(); } _refreshFrameSelectionChart() { console.assert(WebInspector.FPSInstrument.supported()); if (!WebInspector.FPSInstrument.supported()) return; if (!this.visible) return; function getSelectedRecords() { console.assert(this._displayedRecording); console.assert(this._displayedRecording.timelines.has(WebInspector.TimelineRecord.Type.RenderingFrame), "Cannot find rendering frames timeline in displayed recording"); var timeline = this._displayedRecording.timelines.get(WebInspector.TimelineRecord.Type.RenderingFrame); var selectedRecords = []; for (var record of timeline.records) { console.assert(record instanceof WebInspector.RenderingFrameTimelineRecord); // If this frame is completely before the bounds of the graph, skip this record. if (record.frameIndex < this._startFrameIndex) continue; // If this record is completely after the end time, break out now. // Records are sorted, so all records after this will be beyond the end time too. if (record.frameIndex > this._endFrameIndex) break; selectedRecords.push(record); } return selectedRecords; } let records = getSelectedRecords.call(this); for (let key in WebInspector.RenderingFrameTimelineRecord.TaskType) { let taskType = WebInspector.RenderingFrameTimelineRecord.TaskType[key]; let value = records.reduce(function(previousValue, currentValue) { return previousValue + currentValue.durationForTask(taskType); }, 0); this._frameSelectionChartRow.setItemValue(taskType, value); } if (!records.length) { this._frameSelectionChartRow.title = WebInspector.UIString("Frames: None Selected"); return; } var firstRecord = records[0]; var lastRecord = records.lastValue; if (records.length > 1) { this._frameSelectionChartRow.title = WebInspector.UIString("Frames: %d \u2013 %d (%s \u2013 %s)").format(firstRecord.frameNumber, lastRecord.frameNumber, Number.secondsToString(firstRecord.startTime), Number.secondsToString(lastRecord.endTime)); } else { this._frameSelectionChartRow.title = WebInspector.UIString("Frame: %d (%s \u2013 %s)").format(firstRecord.frameNumber, Number.secondsToString(firstRecord.startTime), Number.secondsToString(lastRecord.endTime)); } } // These methods are only used when ReplayAgent is available. _updateReplayInterfaceVisibility() { var shouldShowReplayInterface = !!(window.ReplayAgent && WebInspector.showReplayInterfaceSetting.value); this._recordingNavigationBar.element.classList.toggle(WebInspector.TimelineSidebarPanel.HiddenStyleClassName, shouldShowReplayInterface); this._replayNavigationBar.element.classList.toggle(WebInspector.TimelineSidebarPanel.HiddenStyleClassName, !shouldShowReplayInterface); } _contextMenuNavigationBarOrStatusBar() { if (!window.ReplayAgent) return; let toggleReplayInterface = () => { WebInspector.showReplayInterfaceSetting.value = !WebInspector.showReplayInterfaceSetting.value; }; let contextMenu = WebInspector.ContextMenu.createFromEvent(event); if (WebInspector.showReplayInterfaceSetting.value) contextMenu.appendItem(WebInspector.UIString("Hide Replay Controls"), toggleReplayInterface); else contextMenu.appendItem(WebInspector.UIString("Show Replay Controls"), toggleReplayInterface); } _replayCaptureButtonClicked() { if (!this._replayCaptureButtonItem.activated) { WebInspector.replayManager.startCapturing(); WebInspector.timelineManager.startCapturing(); // De-bounce further presses until the backend has begun capturing. this._replayCaptureButtonItem.activated = true; this._replayCaptureButtonItem.enabled = false; } else { WebInspector.replayManager.stopCapturing(); WebInspector.timelineManager.stopCapturing(); this._replayCaptureButtonItem.enabled = false; } } _replayPauseResumeButtonClicked() { if (this._replayPauseResumeButtonItem.toggled) WebInspector.replayManager.pausePlayback(); else WebInspector.replayManager.replayToCompletion(); } _captureStarted() { this._replayCaptureButtonItem.enabled = true; } _captureStopped() { this._replayCaptureButtonItem.activated = false; this._replayPauseResumeButtonItem.enabled = true; } _playbackStarted() { this._replayPauseResumeButtonItem.toggled = true; } _playbackPaused() { this._replayPauseResumeButtonItem.toggled = false; } _updateViewModeIfNeeded() { let timelineView = this.contentBrowser.currentContentView.currentTimelineView; let newViewMode = WebInspector.TimelineOverview.ViewMode.Timelines; if (timelineView.representedObject instanceof WebInspector.Timeline && timelineView.representedObject.type === WebInspector.TimelineRecord.Type.RenderingFrame) newViewMode = WebInspector.TimelineOverview.ViewMode.RenderingFrames; if (newViewMode === this._viewMode) return; this._viewMode = newViewMode; let isShowingTimelines = this._viewMode === WebInspector.TimelineOverview.ViewMode.Timelines; this._timelinesTreeOutline.hidden = !isShowingTimelines; if (WebInspector.FPSInstrument.supported()) { this._frameSelectionChartSection.collapsed = isShowingTimelines; this._viewModeNavigationBar.selectedNavigationItem = this._viewMode; } } }; WebInspector.TimelineSidebarPanel.HiddenStyleClassName = "hidden"; WebInspector.TimelineSidebarPanel.TitleBarStyleClass = "title-bar"; WebInspector.TimelineSidebarPanel.TitleBarTextStyleClass = "title-bar-text"; WebInspector.TimelineSidebarPanel.TitleBarScopeBarStyleClass = "title-bar-scope-bar"; WebInspector.TimelineSidebarPanel.TimelinesTitleBarStyleClass = "timelines"; WebInspector.TimelineSidebarPanel.TimelineEventsTitleBarStyleClass = "timeline-events"; WebInspector.TimelineSidebarPanel.TimelinesContentContainerStyleClass = "timelines-content"; WebInspector.TimelineSidebarPanel.LargeIconStyleClass = "large"; WebInspector.TimelineSidebarPanel.StopwatchIconStyleClass = "stopwatch-icon"; WebInspector.TimelineSidebarPanel.TimelineRecordingContentViewShowingStyleClass = "timeline-recording-content-view-showing"; WebInspector.TimelineSidebarPanel.ShowingTimelineRecordingContentViewCookieKey = "timeline-sidebar-panel-showing-timeline-recording-content-view"; WebInspector.TimelineSidebarPanel.SelectedTimelineViewIdentifierCookieKey = "timeline-sidebar-panel-selected-timeline-view-identifier"; WebInspector.TimelineSidebarPanel.OverviewTimelineIdentifierCookieValue = "overview";