diff options
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Views/TimelineTabContentView.js')
-rw-r--r-- | Source/WebInspectorUI/UserInterface/Views/TimelineTabContentView.js | 571 |
1 files changed, 571 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Views/TimelineTabContentView.js b/Source/WebInspectorUI/UserInterface/Views/TimelineTabContentView.js new file mode 100644 index 000000000..1c56ca625 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Views/TimelineTabContentView.js @@ -0,0 +1,571 @@ +/* + * Copyright (C) 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.TimelineTabContentView = class TimelineTabContentView extends WebInspector.ContentBrowserTabContentView +{ + constructor(identifier) + { + let {image, title} = WebInspector.TimelineTabContentView.tabInfo(); + let tabBarItem = new WebInspector.GeneralTabBarItem(image, title); + let detailsSidebarPanels = [WebInspector.resourceDetailsSidebarPanel, WebInspector.probeDetailsSidebarPanel]; + + super(identifier || "timeline", "timeline", tabBarItem, null, detailsSidebarPanels); + + // 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 = new WebInspector.TreeOutline; + this._recordingsTreeOutline.addEventListener(WebInspector.TreeOutline.Event.SelectionDidChange, this._recordingsTreeSelectionDidChange, this); + + 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; + + 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.contentBrowser.navigationBar.insertNavigationItem(this._recordButton, 0); + + if (WebInspector.FPSInstrument.supported()) { + let timelinesNavigationItem = new WebInspector.RadioButtonNavigationItem(WebInspector.TimelineOverview.ViewMode.Timelines, WebInspector.UIString("Events")); + let renderingFramesNavigationItem = new WebInspector.RadioButtonNavigationItem(WebInspector.TimelineOverview.ViewMode.RenderingFrames, WebInspector.UIString("Frames")); + + this.contentBrowser.navigationBar.insertNavigationItem(timelinesNavigationItem, 1); + this.contentBrowser.navigationBar.insertNavigationItem(renderingFramesNavigationItem, 2); + + this.contentBrowser.navigationBar.addEventListener(WebInspector.NavigationBar.Event.NavigationItemSelected, this._viewModeSelected, this); + } + + WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.RecordingCreated, this._recordingCreated, this); + WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.RecordingLoaded, this._recordingLoaded, this); + + WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.CapturingStarted, this._capturingStartedOrStopped, this); + WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.CapturingStopped, this._capturingStartedOrStopped, this); + + WebInspector.notifications.addEventListener(WebInspector.Notification.VisibilityStateDidChange, this._inspectorVisibilityChanged, this); + + this._displayedRecording = null; + this._displayedContentView = null; + this._viewMode = null; + this._previousSelectedTimelineType = null; + + const selectedByUser = false; + this._changeViewMode(WebInspector.TimelineOverview.ViewMode.Timelines, selectedByUser); + + for (let recording of WebInspector.timelineManager.recordings) + this._addRecording(recording); + + this._recordingCountChanged(); + + if (WebInspector.timelineManager.activeRecording) + this._recordingLoaded(); + + // Explicitly update the path for the navigation bar to prevent it from showing up as blank. + this.contentBrowser.updateHierarchicalPathForCurrentContentView(); + } + + // Static + + static tabInfo() + { + return { + image: "Images/Timeline.svg", + title: WebInspector.UIString("Timelines"), + }; + } + + static isTabAllowed() + { + return !!window.TimelineAgent || !!window.ScriptProfilerAgent; + } + + static displayNameForTimelineType(timelineType) + { + switch (timelineType) { + case WebInspector.TimelineRecord.Type.Network: + return WebInspector.UIString("Network Requests"); + case WebInspector.TimelineRecord.Type.Layout: + return WebInspector.UIString("Layout & Rendering"); + case WebInspector.TimelineRecord.Type.Script: + return WebInspector.UIString("JavaScript & Events"); + case WebInspector.TimelineRecord.Type.RenderingFrame: + return WebInspector.UIString("Rendering Frames"); + case WebInspector.TimelineRecord.Type.Memory: + return WebInspector.UIString("Memory"); + case WebInspector.TimelineRecord.Type.HeapAllocations: + return WebInspector.UIString("JavaScript Allocations"); + default: + console.error("Unknown Timeline type:", timelineType); + } + + return null; + } + + static iconClassNameForTimelineType(timelineType) + { + switch (timelineType) { + case WebInspector.TimelineRecord.Type.Network: + return "network-icon"; + case WebInspector.TimelineRecord.Type.Layout: + return "layout-icon"; + case WebInspector.TimelineRecord.Type.Memory: + return "memory-icon"; + case WebInspector.TimelineRecord.Type.HeapAllocations: + return "heap-allocations-icon"; + case WebInspector.TimelineRecord.Type.Script: + return "script-icon"; + case WebInspector.TimelineRecord.Type.RenderingFrame: + return "rendering-frame-icon"; + default: + console.error("Unknown Timeline type:", timelineType); + } + + return null; + } + + static genericClassNameForTimelineType(timelineType) + { + switch (timelineType) { + case WebInspector.TimelineRecord.Type.Network: + return "network"; + case WebInspector.TimelineRecord.Type.Layout: + return "colors"; + case WebInspector.TimelineRecord.Type.Memory: + return "memory"; + case WebInspector.TimelineRecord.Type.HeapAllocations: + return "heap-allocations"; + case WebInspector.TimelineRecord.Type.Script: + return "script"; + case WebInspector.TimelineRecord.Type.RenderingFrame: + return "rendering-frame"; + default: + console.error("Unknown Timeline type:", timelineType); + } + + return null; + } + + static iconClassNameForRecord(timelineRecord) + { + switch (timelineRecord.type) { + case WebInspector.TimelineRecord.Type.Layout: + switch (timelineRecord.eventType) { + case WebInspector.LayoutTimelineRecord.EventType.InvalidateStyles: + case WebInspector.LayoutTimelineRecord.EventType.RecalculateStyles: + return WebInspector.TimelineRecordTreeElement.StyleRecordIconStyleClass; + case WebInspector.LayoutTimelineRecord.EventType.InvalidateLayout: + case WebInspector.LayoutTimelineRecord.EventType.ForcedLayout: + case WebInspector.LayoutTimelineRecord.EventType.Layout: + return WebInspector.TimelineRecordTreeElement.LayoutRecordIconStyleClass; + case WebInspector.LayoutTimelineRecord.EventType.Paint: + return WebInspector.TimelineRecordTreeElement.PaintRecordIconStyleClass; + case WebInspector.LayoutTimelineRecord.EventType.Composite: + return WebInspector.TimelineRecordTreeElement.CompositeRecordIconStyleClass; + default: + console.error("Unknown LayoutTimelineRecord eventType: " + timelineRecord.eventType, timelineRecord); + } + + break; + + case WebInspector.TimelineRecord.Type.Script: + switch (timelineRecord.eventType) { + case WebInspector.ScriptTimelineRecord.EventType.APIScriptEvaluated: + return WebInspector.TimelineRecordTreeElement.APIRecordIconStyleClass; + case WebInspector.ScriptTimelineRecord.EventType.ScriptEvaluated: + return WebInspector.TimelineRecordTreeElement.EvaluatedRecordIconStyleClass; + case WebInspector.ScriptTimelineRecord.EventType.MicrotaskDispatched: + case WebInspector.ScriptTimelineRecord.EventType.EventDispatched: + return WebInspector.TimelineRecordTreeElement.EventRecordIconStyleClass; + case WebInspector.ScriptTimelineRecord.EventType.ProbeSampleRecorded: + return WebInspector.TimelineRecordTreeElement.ProbeRecordIconStyleClass; + case WebInspector.ScriptTimelineRecord.EventType.ConsoleProfileRecorded: + return WebInspector.TimelineRecordTreeElement.ConsoleProfileIconStyleClass; + case WebInspector.ScriptTimelineRecord.EventType.GarbageCollected: + return WebInspector.TimelineRecordTreeElement.GarbageCollectionIconStyleClass; + case WebInspector.ScriptTimelineRecord.EventType.TimerInstalled: + return WebInspector.TimelineRecordTreeElement.TimerRecordIconStyleClass; + case WebInspector.ScriptTimelineRecord.EventType.TimerFired: + case WebInspector.ScriptTimelineRecord.EventType.TimerRemoved: + return WebInspector.TimelineRecordTreeElement.TimerRecordIconStyleClass; + case WebInspector.ScriptTimelineRecord.EventType.AnimationFrameFired: + case WebInspector.ScriptTimelineRecord.EventType.AnimationFrameRequested: + case WebInspector.ScriptTimelineRecord.EventType.AnimationFrameCanceled: + return WebInspector.TimelineRecordTreeElement.AnimationRecordIconStyleClass; + default: + console.error("Unknown ScriptTimelineRecord eventType: " + timelineRecord.eventType, timelineRecord); + } + + break; + + case WebInspector.TimelineRecord.Type.RenderingFrame: + return WebInspector.TimelineRecordTreeElement.RenderingFrameRecordIconStyleClass; + + case WebInspector.TimelineRecord.Type.HeapAllocations: + return "heap-snapshot-record"; + + case WebInspector.TimelineRecord.Type.Memory: + // Not used. Fall through to error just in case. + + default: + console.error("Unknown TimelineRecord type: " + timelineRecord.type, timelineRecord); + } + + return null; + } + + static displayNameForRecord(timelineRecord, includeDetailsInMainTitle) + { + switch (timelineRecord.type) { + case WebInspector.TimelineRecord.Type.Network: + return WebInspector.displayNameForURL(timelineRecord.resource.url, timelineRecord.resource.urlComponents); + case WebInspector.TimelineRecord.Type.Layout: + return WebInspector.LayoutTimelineRecord.displayNameForEventType(timelineRecord.eventType); + case WebInspector.TimelineRecord.Type.Script: + return WebInspector.ScriptTimelineRecord.EventType.displayName(timelineRecord.eventType, timelineRecord.details, includeDetailsInMainTitle); + case WebInspector.TimelineRecord.Type.RenderingFrame: + return WebInspector.UIString("Frame %d").format(timelineRecord.frameNumber); + case WebInspector.TimelineRecord.Type.HeapAllocations: + if (timelineRecord.heapSnapshot.title) + return WebInspector.UIString("Snapshot %d \u2014 %s").format(timelineRecord.heapSnapshot.identifier, timelineRecord.heapSnapshot.title); + return WebInspector.UIString("Snapshot %d").format(timelineRecord.heapSnapshot.identifier); + case WebInspector.TimelineRecord.Type.Memory: + // Not used. Fall through to error just in case. + default: + console.error("Unknown TimelineRecord type: " + timelineRecord.type, timelineRecord); + } + + return null; + } + + // Public + + get type() + { + return WebInspector.TimelineTabContentView.Type; + } + + shown() + { + super.shown(); + + this._toggleRecordingShortcut.disabled = false; + this._toggleNewRecordingShortcut.disabled = false; + + if (WebInspector.visible) + WebInspector.timelineManager.autoCaptureOnPageLoad = true; + } + + hidden() + { + super.hidden(); + + this._toggleRecordingShortcut.disabled = true; + this._toggleNewRecordingShortcut.disabled = true; + + WebInspector.timelineManager.autoCaptureOnPageLoad = false; + } + + closed() + { + super.closed(); + + if (WebInspector.FPSInstrument.supported()) + this.contentBrowser.navigationBar.removeEventListener(null, null, this); + + WebInspector.timelineManager.removeEventListener(null, null, this); + WebInspector.notifications.removeEventListener(null, null, this); + } + + canShowRepresentedObject(representedObject) + { + return representedObject instanceof WebInspector.TimelineRecording; + } + + // Protected + + restoreFromCookie(cookie) + { + console.assert(cookie); + console.assert(this._displayedContentView); + + this._restoredShowingTimelineRecordingContentView = cookie[WebInspector.TimelineTabContentView.ShowingTimelineRecordingContentViewCookieKey]; + if (!this._restoredShowingTimelineRecordingContentView) { + if (!this.contentBrowser.currentContentView) + this._showTimelineViewForType(WebInspector.TimelineTabContentView.OverviewTimelineIdentifierCookieValue); + return; + } + + let selectedTimelineViewIdentifier = cookie[WebInspector.TimelineTabContentView.SelectedTimelineViewIdentifierCookieKey]; + if (selectedTimelineViewIdentifier === WebInspector.TimelineRecord.Type.RenderingFrame && !WebInspector.FPSInstrument.supported()) + selectedTimelineViewIdentifier = null; + + this._showTimelineViewForType(selectedTimelineViewIdentifier); + + super.restoreFromCookie(cookie); + } + + saveToCookie(cookie) + { + console.assert(cookie); + + cookie[WebInspector.TimelineTabContentView.ShowingTimelineRecordingContentViewCookieKey] = this.contentBrowser.currentContentView instanceof WebInspector.TimelineRecordingContentView; + + if (this._viewMode === WebInspector.TimelineOverview.ViewMode.RenderingFrames) + cookie[WebInspector.TimelineTabContentView.SelectedTimelineViewIdentifierCookieKey] = WebInspector.TimelineRecord.Type.RenderingFrame; + else { + let selectedTimeline = this._getTimelineForCurrentContentView(); + if (selectedTimeline) + cookie[WebInspector.TimelineTabContentView.SelectedTimelineViewIdentifierCookieKey] = selectedTimeline.type; + else + cookie[WebInspector.TimelineTabContentView.SelectedTimelineViewIdentifierCookieKey] = WebInspector.TimelineTabContentView.OverviewTimelineIdentifierCookieValue; + } + + super.saveToCookie(cookie); + } + + treeElementForRepresentedObject(representedObject) + { + // This can be called by the base class constructor before the map is created. + if (!this._recordingTreeElementMap) + return null; + + if (representedObject instanceof WebInspector.TimelineRecording) + return this._recordingTreeElementMap.get(representedObject); + + return null; + } + + // Private + + _capturingStartedOrStopped(event) + { + let isCapturing = WebInspector.timelineManager.isCapturing(); + this._recordButton.toggled = isCapturing; + } + + _inspectorVisibilityChanged(event) + { + WebInspector.timelineManager.autoCaptureOnPageLoad = !!this.visible && !!WebInspector.visible; + } + + _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) + { + let isCapturing = WebInspector.timelineManager.isCapturing(); + this._recordButton.toggled = isCapturing; + + if (isCapturing) + WebInspector.timelineManager.stopCapturing(); + else { + WebInspector.timelineManager.startCapturing(shouldCreateRecording); + // Show the timeline to which events will be appended. + this._recordingLoaded(); + } + } + + _recordButtonClicked(event) + { + let shouldCreateNewRecording = window.event ? window.event.shiftKey : false; + this._recordButton.toggled = !WebInspector.timelineManager.isCapturing(); + this._toggleRecording(shouldCreateNewRecording); + } + + _recordingsTreeSelectionDidChange(event) + { + let treeElement = event.data.selectedElement; + if (!treeElement) + return; + + console.assert(treeElement.representedObject instanceof WebInspector.TimelineRecording); + + this._recordingSelected(treeElement.representedObject); + } + + _recordingCreated(event) + { + this._addRecording(event.data.recording); + this._recordingCountChanged(); + } + + _addRecording(recording) + { + console.assert(recording instanceof WebInspector.TimelineRecording, recording); + + let recordingTreeElement = new WebInspector.GeneralTreeElement(WebInspector.TimelineTabContentView.StopwatchIconStyleClass, recording.displayName, null, recording); + this._recordingTreeElementMap.set(recording, recordingTreeElement); + this._recordingsTreeOutline.appendChild(recordingTreeElement); + } + + _recordingCountChanged() + { + let previousTreeElement = null; + for (let treeElement of this._recordingTreeElementMap.values()) { + if (previousTreeElement) { + previousTreeElement.nextSibling = treeElement; + treeElement.previousSibling = previousTreeElement; + } + + previousTreeElement = treeElement; + } + } + + _recordingSelected(recording) + { + console.assert(recording instanceof WebInspector.TimelineRecording, recording); + + this._displayedRecording = recording; + + // Save the current state incase we need to restore it to a new recording. + let cookie = {}; + this.saveToCookie(cookie); + + if (this._displayedContentView) + this._displayedContentView.removeEventListener(WebInspector.ContentView.Event.NavigationItemsDidChange, this._displayedContentViewNavigationItemsDidChange, this); + + // Try to get the recording content view if it exists already, if it does we don't want to restore the cookie. + let onlyExisting = true; + this._displayedContentView = this.contentBrowser.contentViewForRepresentedObject(this._displayedRecording, onlyExisting); + if (this._displayedContentView) { + this._displayedContentView.addEventListener(WebInspector.ContentView.Event.NavigationItemsDidChange, this._displayedContentViewNavigationItemsDidChange, this); + + // Show the timeline that was being shown to update the sidebar tree state. + let currentTimelineView = this._displayedContentView.currentTimelineView; + let timelineType = currentTimelineView && currentTimelineView.representedObject instanceof WebInspector.Timeline ? currentTimelineView.representedObject.type : null; + this._showTimelineViewForType(timelineType); + + return; + } + + onlyExisting = false; + this._displayedContentView = this.contentBrowser.contentViewForRepresentedObject(this._displayedRecording, onlyExisting); + if (this._displayedContentView) + this._displayedContentView.addEventListener(WebInspector.ContentView.Event.NavigationItemsDidChange, this._displayedContentViewNavigationItemsDidChange, this); + + // Restore the cookie to carry over the previous recording view state to the new recording. + this.restoreFromCookie(cookie); + } + + _recordingLoaded(event) + { + this._recordingSelected(WebInspector.timelineManager.activeRecording); + } + + _viewModeSelected(event) + { + let selectedNavigationItem = event.target.selectedNavigationItem; + console.assert(selectedNavigationItem); + if (!selectedNavigationItem) + return; + + const selectedByUser = true; + this._changeViewMode(selectedNavigationItem.identifier, selectedByUser); + } + + _changeViewMode(mode, selectedByUser) + { + if (this._viewMode === mode) + return; + + this._viewMode = mode; + this.contentBrowser.navigationBar.selectedNavigationItem = this._viewMode; + + if (!selectedByUser) + return; + + let timelineType = this._previousSelectedTimelineType; + if (this._viewMode === WebInspector.TimelineOverview.ViewMode.RenderingFrames) { + let timeline = this._getTimelineForCurrentContentView(); + this._previousSelectedTimelineType = timeline ? timeline.type : null; + timelineType = WebInspector.TimelineRecord.Type.RenderingFrame; + } + + this._showTimelineViewForType(timelineType); + } + + _showTimelineViewForType(timelineType) + { + let timeline = timelineType ? this._displayedRecording.timelines.get(timelineType) : null; + if (timeline) + this._displayedContentView.showTimelineViewForTimeline(timeline); + else + this._displayedContentView.showOverviewTimelineView(); + + if (this.contentBrowser.currentContentView !== this._displayedContentView) + this.contentBrowser.showContentView(this._displayedContentView); + } + + _displayedContentViewNavigationItemsDidChange(event) + { + let timeline = this._getTimelineForCurrentContentView(); + let newViewMode = WebInspector.TimelineOverview.ViewMode.Timelines; + if (timeline && timeline.type === WebInspector.TimelineRecord.Type.RenderingFrame) + newViewMode = WebInspector.TimelineOverview.ViewMode.RenderingFrames; + + const selectedByUser = false; + this._changeViewMode(newViewMode, selectedByUser); + } + + _getTimelineForCurrentContentView() + { + let currentContentView = this.contentBrowser.currentContentView; + if (!(currentContentView instanceof WebInspector.TimelineRecordingContentView)) + return null; + + let timelineView = currentContentView.currentTimelineView; + return (timelineView && timelineView.representedObject instanceof WebInspector.Timeline) ? timelineView.representedObject : null; + } +}; + +WebInspector.TimelineTabContentView.Type = "timeline"; + +WebInspector.TimelineTabContentView.ShowingTimelineRecordingContentViewCookieKey = "timeline-sidebar-panel-showing-timeline-recording-content-view"; +WebInspector.TimelineTabContentView.SelectedTimelineViewIdentifierCookieKey = "timeline-sidebar-panel-selected-timeline-view-identifier"; +WebInspector.TimelineTabContentView.OverviewTimelineIdentifierCookieValue = "overview"; +WebInspector.TimelineTabContentView.StopwatchIconStyleClass = "stopwatch-icon"; |