Title: [172094] trunk/Source/WebInspectorUI
Revision
172094
Author
b...@cs.washington.edu
Date
2014-08-05 15:32:58 -0700 (Tue, 05 Aug 2014)

Log Message

Web Inspector: support storing multiple timeline recordings in the manager
https://bugs.webkit.org/show_bug.cgi?id=132875

Reviewed by Timothy Hatcher.

This patch adds support for capturing multiple timeline recordings and switching
between them in the user interface using hierarchical path components.

* Localizations/en.lproj/localizedStrings.js:
* UserInterface/Base/Main.js:
(WebInspector.contentLoaded): Remove hard-coded priming of the timeline sidebar panel.
Instead, load the first recording in the timeline manager after the initial load.

(WebInspector._revealAndSelectRepresentedObjectInNavigationSidebar): Don't suppress
onselect events when selecting the tree element for a newly shown content view. This
allows the sidebar to sync the current content view and timeline tree element selection
with what is displayed in the content browser.

* UserInterface/Controllers/TimelineManager.js: Add two new events, RecordingCreated and
RecordingLoaded. A recording is considered active when any new records recieved will be
appended to that recording. The user interface is not necessarily viewing the active
recording.

(WebInspector.TimelineManager.delayedWork):
(WebInspector.TimelineManager): Keep a list of recordings, and load the first recording
asynchronously so that everyone can add an event listener for it.

(WebInspector.TimelineManager.prototype.get activeRecording):
(WebInspector.TimelineManager.prototype.get recordings):
(WebInspector.TimelineManager.prototype.startCapturing):
(WebInspector.TimelineManager.prototype.stopCapturing): Use promises to make the iOS 7
fallback path better match the async semantics of the non-fallback path.

(WebInspector.TimelineManager.prototype.unloadRecording):
(WebInspector.TimelineManager.prototype._loadNewRecording): Stop capturing and unload
any existing recording before creating and loading a new recording.

(WebInspector.TimelineManager.prototype._startAutoCapturing): Create a new recording
rather than resetting the current recording.

* UserInterface/Models/NetworkTimeline.js:
(WebInspector.NetworkTimeline):
* UserInterface/Models/Timeline.js:
(WebInspector.Timeline):
(WebInspector.Timeline.prototype.get type): Each timeline stores its TimelineRecord.Type
so that other code can create type-specific views using the Timeline as a representedObject.

* UserInterface/Models/TimelineRecording.js: For each recording, add new state for a unique identifier,
display string, and an isWritable flag. Once a recording is unloaded, it becomes read-only.
(WebInspector.TimelineRecording.prototype.get displayName):
(WebInspector.TimelineRecording.prototype.get identifier):
(WebInspector.TimelineRecording.prototype.isWritable):
(WebInspector.TimelineRecording.prototype.unloaded):
(WebInspector.TimelineRecording.prototype.reset): A recording can only be reset if it is writable.

* UserInterface/Protocol/InspectorFrontendAPI.js:
(InspectorFrontendAPI.setTimelineProfilingEnabled): Don't make redundant start/stop capturing calls.

* UserInterface/Views/LayoutTimelineOverviewGraph.js: Use a timeline as the representedObject for all
timeline-specific graphs and views. Otherwise, use the recording.
(WebInspector.LayoutTimelineOverviewGraph):
* UserInterface/Views/LayoutTimelineView.js:
(WebInspector.LayoutTimelineView):
(WebInspector.LayoutTimelineView.prototype._treeElementSelected):
* UserInterface/Views/NetworkTimelineOverviewGraph.js:
(WebInspector.NetworkTimelineOverviewGraph):
* UserInterface/Views/NetworkTimelineView.js:
(WebInspector.NetworkTimelineView):
* UserInterface/Views/OverviewTimelineView.js:
(WebInspector.OverviewTimelineView.prototype._networkTimelineRecordAdded):
* UserInterface/Views/ScriptTimelineOverviewGraph.js:
(WebInspector.ScriptTimelineOverviewGraph):
* UserInterface/Views/ScriptTimelineView.js:
(WebInspector.ScriptTimelineView):
(WebInspector.ScriptTimelineView.prototype._treeElementSelected):

* UserInterface/Views/TimelineContentView.js: Iterate over timeline objects when setting up maps. Use timelines
as keys rather than their type identifiers.
(WebInspector.TimelineContentView.prototype.showTimelineViewForTimeline): Renamed from showTimelineView. This
function takes a Timeline instance rather than an identifier, since the conten view is specific to one recording.
(WebInspector.TimelineContentView.prototype.get selectionPathComponents): Match types against the currently
visible timeline's representedObject.
(WebInspector.TimelineContentView.prototype.get currentTimelineView): Used by the sidebar panel to sync timeline
tree element selections to TimelineView shown by the TimelineContentView.
(WebInspector.TimelineContentView.prototype.shown): Sync enablement of the "Clear Timelines" button to recording
read-only state.

(WebInspector.TimelineContentView.prototype.saveToCookie):
(WebInspector.TimelineContentView.prototype.restoreFromCookie): Added. Only handle saving/restoring the subview.

(WebInspector.TimelineContentView.prototype._pathComponentSelected):
(WebInspector.TimelineContentView.prototype._showTimelineView): Relax the early return so that timeline views
and content tree outlines are reattached when re-navigating to the same timeline view via back-forward entries.
(WebInspector.TimelineContentView.prototype.showTimelineView): Deleted.

* UserInterface/Views/TimelineOverviewGraph.js:
(WebInspector.TimelineOverviewGraph):

* UserInterface/Views/TimelineSidebarPanel.js:
(WebInspector.TimelineSidebarPanel): Keep a tree outline and tree element map for storing available recordings.
(WebInspector.TimelineSidebarPanel.createTimelineTreeElement):
(WebInspector.TimelineSidebarPanel.prototype.shown): Added.
(WebInspector.TimelineSidebarPanel.prototype.showDefaultContentView): Add a guard.
(WebInspector.TimelineSidebarPanel.prototype.get hasSelectedElement): Added. Selected recording tree elements
should be considered when deciding whether a represented object has been selected in the sidebar panel.

(WebInspector.TimelineSidebarPanel.prototype.treeElementForRepresentedObject.looselyCompareRepresentedObjects):
(WebInspector.TimelineSidebarPanel.prototype.treeElementForRepresentedObject.get if):
(WebInspector.TimelineSidebarPanel.prototype.treeElementForRepresentedObject):
(WebInspector.TimelineSidebarPanel.prototype.showTimelineOverview):
(WebInspector.TimelineSidebarPanel.prototype.showTimelineViewForType): Renamed to explicit take a type identifier.
Delegate the actual showing of the timeline view to the onselect handler for the timelines tree outline.

(WebInspector.TimelineSidebarPanel.prototype.matchTreeElementAgainstCustomFilters):
(WebInspector.TimelineSidebarPanel.prototype.saveStateToCookie): Fix a typo.
(WebInspector.TimelineSidebarPanel.prototype.restoreStateFromCookie): Fix a typo.
(WebInspector.TimelineSidebarPanel.prototype._recordingsTreeElementSelected): Sync the currently displayed
recording object and content view, and sync the selected tree element to the displayed timeline subview.

(WebInspector.TimelineSidebarPanel.prototype._timelinesTreeElementSelected): If this is a user action, show the timeline.
(WebInspector.TimelineSidebarPanel.prototype._contentBrowserCurrentContentViewDidChange): Use classList.toggle().
(WebInspector.TimelineSidebarPanel.prototype._recordingCreated): Dynamically add new recordings to the interface.
(WebInspector.TimelineSidebarPanel.prototype._recordingLoaded): Automatically show recordings when they are loaded.
(WebInspector.TimelineSidebarPanel.prototype._recordGlyphClicked): Shift+click will force-create a new recording.
(WebInspector.TimelineSidebarPanel.prototype.initialize): Deleted.
* UserInterface/Views/TimelineView.js:
(WebInspector.TimelineView):
(WebInspector.TimelineView.prototype.get representedObject):

Modified Paths

Diff

Modified: trunk/Source/WebInspectorUI/ChangeLog (172093 => 172094)


--- trunk/Source/WebInspectorUI/ChangeLog	2014-08-05 22:28:19 UTC (rev 172093)
+++ trunk/Source/WebInspectorUI/ChangeLog	2014-08-05 22:32:58 UTC (rev 172094)
@@ -1,5 +1,136 @@
 2014-08-05  Brian J. Burg  <b...@cs.washington.edu>
 
+        Web Inspector: support storing multiple timeline recordings in the manager
+        https://bugs.webkit.org/show_bug.cgi?id=132875
+
+        Reviewed by Timothy Hatcher.
+
+        This patch adds support for capturing multiple timeline recordings and switching
+        between them in the user interface using hierarchical path components.
+
+        * Localizations/en.lproj/localizedStrings.js:
+        * UserInterface/Base/Main.js:
+        (WebInspector.contentLoaded): Remove hard-coded priming of the timeline sidebar panel.
+        Instead, load the first recording in the timeline manager after the initial load.
+
+        (WebInspector._revealAndSelectRepresentedObjectInNavigationSidebar): Don't suppress
+        onselect events when selecting the tree element for a newly shown content view. This
+        allows the sidebar to sync the current content view and timeline tree element selection
+        with what is displayed in the content browser.
+
+        * UserInterface/Controllers/TimelineManager.js: Add two new events, RecordingCreated and
+        RecordingLoaded. A recording is considered active when any new records recieved will be
+        appended to that recording. The user interface is not necessarily viewing the active
+        recording.
+
+        (WebInspector.TimelineManager.delayedWork):
+        (WebInspector.TimelineManager): Keep a list of recordings, and load the first recording
+        asynchronously so that everyone can add an event listener for it.
+
+        (WebInspector.TimelineManager.prototype.get activeRecording):
+        (WebInspector.TimelineManager.prototype.get recordings):
+        (WebInspector.TimelineManager.prototype.startCapturing):
+        (WebInspector.TimelineManager.prototype.stopCapturing): Use promises to make the iOS 7
+        fallback path better match the async semantics of the non-fallback path.
+
+        (WebInspector.TimelineManager.prototype.unloadRecording):
+        (WebInspector.TimelineManager.prototype._loadNewRecording): Stop capturing and unload
+        any existing recording before creating and loading a new recording.
+
+        (WebInspector.TimelineManager.prototype._startAutoCapturing): Create a new recording
+        rather than resetting the current recording.
+
+        * UserInterface/Models/NetworkTimeline.js:
+        (WebInspector.NetworkTimeline):
+        * UserInterface/Models/Timeline.js:
+        (WebInspector.Timeline):
+        (WebInspector.Timeline.prototype.get type): Each timeline stores its TimelineRecord.Type
+        so that other code can create type-specific views using the Timeline as a representedObject.
+
+        * UserInterface/Models/TimelineRecording.js: For each recording, add new state for a unique identifier,
+        display string, and an isWritable flag. Once a recording is unloaded, it becomes read-only.
+        (WebInspector.TimelineRecording.prototype.get displayName):
+        (WebInspector.TimelineRecording.prototype.get identifier):
+        (WebInspector.TimelineRecording.prototype.isWritable):
+        (WebInspector.TimelineRecording.prototype.unloaded):
+        (WebInspector.TimelineRecording.prototype.reset): A recording can only be reset if it is writable.
+
+        * UserInterface/Protocol/InspectorFrontendAPI.js:
+        (InspectorFrontendAPI.setTimelineProfilingEnabled): Don't make redundant start/stop capturing calls.
+
+        * UserInterface/Views/LayoutTimelineOverviewGraph.js: Use a timeline as the representedObject for all
+        timeline-specific graphs and views. Otherwise, use the recording.
+        (WebInspector.LayoutTimelineOverviewGraph):
+        * UserInterface/Views/LayoutTimelineView.js:
+        (WebInspector.LayoutTimelineView):
+        (WebInspector.LayoutTimelineView.prototype._treeElementSelected):
+        * UserInterface/Views/NetworkTimelineOverviewGraph.js:
+        (WebInspector.NetworkTimelineOverviewGraph):
+        * UserInterface/Views/NetworkTimelineView.js:
+        (WebInspector.NetworkTimelineView):
+        * UserInterface/Views/OverviewTimelineView.js:
+        (WebInspector.OverviewTimelineView.prototype._networkTimelineRecordAdded):
+        * UserInterface/Views/ScriptTimelineOverviewGraph.js:
+        (WebInspector.ScriptTimelineOverviewGraph):
+        * UserInterface/Views/ScriptTimelineView.js:
+        (WebInspector.ScriptTimelineView):
+        (WebInspector.ScriptTimelineView.prototype._treeElementSelected):
+
+        * UserInterface/Views/TimelineContentView.js: Iterate over timeline objects when setting up maps. Use timelines
+        as keys rather than their type identifiers.
+        (WebInspector.TimelineContentView.prototype.showTimelineViewForTimeline): Renamed from showTimelineView. This
+        function takes a Timeline instance rather than an identifier, since the conten view is specific to one recording.
+        (WebInspector.TimelineContentView.prototype.get selectionPathComponents): Match types against the currently
+        visible timeline's representedObject.
+        (WebInspector.TimelineContentView.prototype.get currentTimelineView): Used by the sidebar panel to sync timeline
+        tree element selections to TimelineView shown by the TimelineContentView.
+        (WebInspector.TimelineContentView.prototype.shown): Sync enablement of the "Clear Timelines" button to recording
+        read-only state.
+
+        (WebInspector.TimelineContentView.prototype.saveToCookie):
+        (WebInspector.TimelineContentView.prototype.restoreFromCookie): Added. Only handle saving/restoring the subview.
+
+        (WebInspector.TimelineContentView.prototype._pathComponentSelected):
+        (WebInspector.TimelineContentView.prototype._showTimelineView): Relax the early return so that timeline views
+        and content tree outlines are reattached when re-navigating to the same timeline view via back-forward entries.
+        (WebInspector.TimelineContentView.prototype.showTimelineView): Deleted.
+
+        * UserInterface/Views/TimelineOverviewGraph.js:
+        (WebInspector.TimelineOverviewGraph):
+
+        * UserInterface/Views/TimelineSidebarPanel.js:
+        (WebInspector.TimelineSidebarPanel): Keep a tree outline and tree element map for storing available recordings.
+        (WebInspector.TimelineSidebarPanel.createTimelineTreeElement):
+        (WebInspector.TimelineSidebarPanel.prototype.shown): Added.
+        (WebInspector.TimelineSidebarPanel.prototype.showDefaultContentView): Add a guard.
+        (WebInspector.TimelineSidebarPanel.prototype.get hasSelectedElement): Added. Selected recording tree elements
+        should be considered when deciding whether a represented object has been selected in the sidebar panel.
+
+        (WebInspector.TimelineSidebarPanel.prototype.treeElementForRepresentedObject.looselyCompareRepresentedObjects):
+        (WebInspector.TimelineSidebarPanel.prototype.treeElementForRepresentedObject.get if):
+        (WebInspector.TimelineSidebarPanel.prototype.treeElementForRepresentedObject):
+        (WebInspector.TimelineSidebarPanel.prototype.showTimelineOverview):
+        (WebInspector.TimelineSidebarPanel.prototype.showTimelineViewForType): Renamed to explicit take a type identifier.
+        Delegate the actual showing of the timeline view to the onselect handler for the timelines tree outline.
+
+        (WebInspector.TimelineSidebarPanel.prototype.matchTreeElementAgainstCustomFilters):
+        (WebInspector.TimelineSidebarPanel.prototype.saveStateToCookie): Fix a typo.
+        (WebInspector.TimelineSidebarPanel.prototype.restoreStateFromCookie): Fix a typo.
+        (WebInspector.TimelineSidebarPanel.prototype._recordingsTreeElementSelected): Sync the currently displayed
+        recording object and content view, and sync the selected tree element to the displayed timeline subview.
+
+        (WebInspector.TimelineSidebarPanel.prototype._timelinesTreeElementSelected): If this is a user action, show the timeline.
+        (WebInspector.TimelineSidebarPanel.prototype._contentBrowserCurrentContentViewDidChange): Use classList.toggle().
+        (WebInspector.TimelineSidebarPanel.prototype._recordingCreated): Dynamically add new recordings to the interface.
+        (WebInspector.TimelineSidebarPanel.prototype._recordingLoaded): Automatically show recordings when they are loaded.
+        (WebInspector.TimelineSidebarPanel.prototype._recordGlyphClicked): Shift+click will force-create a new recording.
+        (WebInspector.TimelineSidebarPanel.prototype.initialize): Deleted.
+        * UserInterface/Views/TimelineView.js:
+        (WebInspector.TimelineView):
+        (WebInspector.TimelineView.prototype.get representedObject):
+
+2014-08-05  Brian J. Burg  <b...@cs.washington.edu>
+
         Web Inspector: ReplayManager shouldn't assume replay status when the inspector is opened
         https://bugs.webkit.org/show_bug.cgi?id=135212
 

Modified: trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js (172093 => 172094)


--- trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js	2014-08-05 22:28:19 UTC (rev 172093)
+++ trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js	2014-08-05 22:32:58 UTC (rev 172094)
@@ -431,6 +431,7 @@
 localizedStrings["The  %s \ntable is empty."] = "The  %s \ntable is empty.";
 localizedStrings["Time until the load event fired, click to show the Network Requests timeline"] = "Time until the load event fired, click to show the Network Requests timeline";
 localizedStrings["Timeline Events"] = "Timeline Events";
+localizedStrings["Timeline Recording %d"] = "Timeline Recording %d";
 localizedStrings["Timelines"] = "Timelines";
 localizedStrings["Timer %s Fired"] = "Timer %s Fired";
 localizedStrings["Timer %s Installed"] = "Timer %s Installed";

Modified: trunk/Source/WebInspectorUI/UserInterface/Base/Main.js (172093 => 172094)


--- trunk/Source/WebInspectorUI/UserInterface/Base/Main.js	2014-08-05 22:28:19 UTC (rev 172093)
+++ trunk/Source/WebInspectorUI/UserInterface/Base/Main.js	2014-08-05 22:32:58 UTC (rev 172094)
@@ -257,8 +257,6 @@
     this.timelineSidebarPanel = new WebInspector.TimelineSidebarPanel;
     this.debuggerSidebarPanel = new WebInspector.DebuggerSidebarPanel;
 
-    this.timelineSidebarPanel.initialize();
-
     this.navigationSidebar.addSidebarPanel(this.resourceSidebarPanel);
     // FIXME: Enable timelines panel for _javascript_ inspection.
     if (this.debuggableType !== WebInspector.DebuggableType._javascript_)
@@ -911,7 +909,7 @@
     var treeElement = selectedSidebarPanel.treeElementForRepresentedObject(representedObject);
 
     if (treeElement)
-        treeElement.revealAndSelect(true, false, true, true);
+        treeElement.revealAndSelect(true, false, false, true);
     else if (selectedSidebarPanel.contentTreeOutline.selectedTreeElement)
         selectedSidebarPanel.contentTreeOutline.selectedTreeElement.deselect(true);
 

Modified: trunk/Source/WebInspectorUI/UserInterface/Controllers/TimelineManager.js (172093 => 172094)


--- trunk/Source/WebInspectorUI/UserInterface/Controllers/TimelineManager.js	2014-08-05 22:28:19 UTC (rev 172093)
+++ trunk/Source/WebInspectorUI/UserInterface/Controllers/TimelineManager.js	2014-08-05 22:32:58 UTC (rev 172094)
@@ -31,11 +31,24 @@
     WebInspector.Frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
     WebInspector.Frame.addEventListener(WebInspector.Frame.Event.ResourceWasAdded, this._resourceWasAdded, this);
 
-    this._activeRecording = new WebInspector.TimelineRecording;
+    this._recordings = [];
+    this._activeRecording = null;
     this._isCapturing = false;
+
+    this._nextRecordingIdentifier = 1;
+
+    function delayedWork()
+    {
+        this._loadNewRecording();
+    }
+
+    // Allow other code to set up listeners before firing the initial RecordingLoaded event.
+    setTimeout(delayedWork.bind(this), 0);
 };
 
 WebInspector.TimelineManager.Event = {
+    RecordingCreated: "timeline-manager-recording-created",
+    RecordingLoaded: "timeline-manager-recording-loaded",
     CapturingStarted: "timeline-manager-capturing-started",
     CapturingStopped: "timeline-manager-capturing-stopped"
 };
@@ -49,27 +62,44 @@
 
     // Public
 
+    // The current recording that new timeline records will be appended to, if any.
     get activeRecording()
     {
+        console.assert(this._activeRecording || !this._isCapturing);
         return this._activeRecording;
     },
 
+    get recordings()
+    {
+        return this._recordings.slice();
+    },
+
     isCapturing: function()
     {
         return this._isCapturing;
     },
 
-    startCapturing: function()
+    startCapturing: function(shouldCreateRecording)
     {
-        TimelineAgent.start();
+        console.assert(!this._isCapturing, "TimelineManager is already capturing.");
 
+        if (!this._activeRecording || shouldCreateRecording)
+            this._loadNewRecording();
+
+        var result = TimelineAgent.start.promise();
+
         // COMPATIBILITY (iOS 7): recordingStarted event did not exist yet. Start explicitly.
-        if (!TimelineAgent.hasEvent("recordingStarted"))
-            this.capturingStarted();
+        if (!TimelineAgent.hasEvent("recordingStarted")) {
+            result.then(function() {
+                WebInspector.timelineManager.capturingStarted();
+            });
+        }
     },
 
     stopCapturing: function()
     {
+        console.assert(this._isCapturing, "TimelineManager is not capturing.");
+
         TimelineAgent.stop();
 
         // NOTE: Always stop immediately instead of waiting for a Timeline.recordingStopped event.
@@ -77,6 +107,20 @@
         this.capturingStopped();
     },
 
+    unloadRecording: function()
+    {
+        if (!this._activeRecording)
+            return;
+
+        if (this._isCapturing)
+            this.stopCapturing();
+
+        this._activeRecording.unloaded();
+        this._activeRecording = null;
+    },
+
+    // Protected
+
     capturingStarted: function()
     {
         if (this._isCapturing)
@@ -352,6 +396,26 @@
 
     // Private
 
+    _loadNewRecording: function()
+    {
+        var identifier = this._nextRecordingIdentifier++;
+        var newRecording = new WebInspector.TimelineRecording(identifier, WebInspector.UIString("Timeline Recording %d").format(identifier));
+        this._recordings.push(newRecording);
+        this.dispatchEventToListeners(WebInspector.TimelineManager.Event.RecordingCreated, {recording: newRecording});
+
+        console.assert(newRecording.isWritable());
+
+        if (this._isCapturing)
+            this.stopCapturing();
+
+        var oldRecording = this._activeRecording;
+        if (oldRecording)
+            oldRecording.unloaded();
+
+        this._activeRecording = newRecording;
+        this.dispatchEventToListeners(WebInspector.TimelineManager.Event.RecordingLoaded, {oldRecording: oldRecording});
+    },
+
     _callFramesFromPayload: function(payload)
     {
         if (!payload)
@@ -401,11 +465,12 @@
         if (mainResource === this._autoCapturingMainResource)
             return false;
 
-        this.stopCapturing();
+        if (this._isCapturing)
+            this.stopCapturing();
 
         this._autoCapturingMainResource = mainResource;
 
-        this._activeRecording.reset();
+        this._loadNewRecording();
 
         this.startCapturing();
 

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/NetworkTimeline.js (172093 => 172094)


--- trunk/Source/WebInspectorUI/UserInterface/Models/NetworkTimeline.js	2014-08-05 22:28:19 UTC (rev 172093)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/NetworkTimeline.js	2014-08-05 22:32:58 UTC (rev 172094)
@@ -23,9 +23,9 @@
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-WebInspector.NetworkTimeline = function()
+WebInspector.NetworkTimeline = function(type)
 {
-    WebInspector.Timeline.call(this);
+    WebInspector.Timeline.call(this, type);
 };
 
 WebInspector.NetworkTimeline.prototype = {

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/Timeline.js (172093 => 172094)


--- trunk/Source/WebInspectorUI/UserInterface/Models/Timeline.js	2014-08-05 22:28:19 UTC (rev 172093)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/Timeline.js	2014-08-05 22:32:58 UTC (rev 172094)
@@ -23,10 +23,18 @@
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-WebInspector.Timeline = function()
+WebInspector.Timeline = function(type)
 {
+    if (this.constructor === WebInspector.Timeline) {
+        // When instantiated directly, potentially return an instance of a concrete subclass.
+        if (type === WebInspector.TimelineRecord.Type.Network)
+            return new WebInspector.NetworkTimeline(type);
+    }
+
     WebInspector.Object.call(this);
 
+    this._type = type;
+
     this.reset(true);
 };
 
@@ -57,6 +65,11 @@
         return this._records;
     },
 
+    get type()
+    {
+        return this._type;
+    },
+
     reset: function(suppressEvents)
     {
         this._records = [];

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/TimelineRecording.js (172093 => 172094)


--- trunk/Source/WebInspectorUI/UserInterface/Models/TimelineRecording.js	2014-08-05 22:28:19 UTC (rev 172093)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/TimelineRecording.js	2014-08-05 22:32:58 UTC (rev 172094)
@@ -23,17 +23,21 @@
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-WebInspector.TimelineRecording = function()
+WebInspector.TimelineRecording = function(identifier, displayName)
 {
     WebInspector.Object.call(this);
 
+    this._identifier = identifier;
     this._timelines = new Map;
-    this._timelines.set(WebInspector.TimelineRecord.Type.Network, new WebInspector.NetworkTimeline);
-    this._timelines.set(WebInspector.TimelineRecord.Type.Script, new WebInspector.Timeline);
-    this._timelines.set(WebInspector.TimelineRecord.Type.Layout, new WebInspector.Timeline);
+    this._displayName = displayName;
+    this._isWritable = true;
 
-    for (var timeline of this._timelines.values())
+    for (var key of Object.keys(WebInspector.TimelineRecord.Type)) {
+        var type = WebInspector.TimelineRecord.Type[key];
+        var timeline = new WebInspector.Timeline(type);
+        this._timelines.set(type, timeline);
         timeline.addEventListener(WebInspector.Timeline.Event.TimesUpdated, this._timelineTimesUpdated, this);
+    }
 
     this.reset(true);
 };
@@ -50,6 +54,16 @@
 
     // Public
 
+    get displayName()
+    {
+        return this._displayName;
+    },
+
+    get identifier()
+    {
+        return this._identifier;
+    },
+
     get timelines()
     {
         return this._timelines;
@@ -65,8 +79,20 @@
         return this._endTime;
     },
 
+    isWritable: function()
+    {
+        return this._isWritable;
+    },
+
+    unloaded: function()
+    {
+        this._isWritable = false;
+    },
+
     reset: function(suppressEvents)
     {
+        console.assert(this._isWritable, "Can't reset a read-only recording.");
+
         this._sourceCodeTimelinesMap = new Map;
         this._eventMarkers = [];
         this._startTime = NaN;

Modified: trunk/Source/WebInspectorUI/UserInterface/Protocol/InspectorFrontendAPI.js (172093 => 172094)


--- trunk/Source/WebInspectorUI/UserInterface/Protocol/InspectorFrontendAPI.js	2014-08-05 22:28:19 UTC (rev 172093)
+++ trunk/Source/WebInspectorUI/UserInterface/Protocol/InspectorFrontendAPI.js	2014-08-05 22:32:58 UTC (rev 172094)
@@ -56,6 +56,9 @@
 
     setTimelineProfilingEnabled: function(enabled)
     {
+        if (WebInspector.timelineManager.isCapturing() !== enabled)
+            return;
+
         if (enabled) {
             WebInspector.navigationSidebar.selectedSidebarPanel = WebInspector.timelineSidebarPanel;
             WebInspector.timelineSidebarPanel.showTimelineOverview();

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/LayoutTimelineOverviewGraph.js (172093 => 172094)


--- trunk/Source/WebInspectorUI/UserInterface/Views/LayoutTimelineOverviewGraph.js	2014-08-05 22:28:19 UTC (rev 172093)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/LayoutTimelineOverviewGraph.js	2014-08-05 22:32:58 UTC (rev 172094)
@@ -23,13 +23,13 @@
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-WebInspector.LayoutTimelineOverviewGraph = function(recording)
+WebInspector.LayoutTimelineOverviewGraph = function(timeline)
 {
-    WebInspector.TimelineOverviewGraph.call(this, recording);
+    WebInspector.TimelineOverviewGraph.call(this, timeline);
 
     this.element.classList.add(WebInspector.LayoutTimelineOverviewGraph.StyleClassName);
 
-    this._layoutTimeline = recording.timelines.get(WebInspector.TimelineRecord.Type.Layout);
+    this._layoutTimeline = timeline;
     this._layoutTimeline.addEventListener(WebInspector.Timeline.Event.RecordAdded, this._layoutTimelineRecordAdded, this);
 
     this._timelineRecordBars = [];

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/LayoutTimelineView.js (172093 => 172094)


--- trunk/Source/WebInspectorUI/UserInterface/Views/LayoutTimelineView.js	2014-08-05 22:28:19 UTC (rev 172093)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/LayoutTimelineView.js	2014-08-05 22:32:58 UTC (rev 172094)
@@ -23,10 +23,12 @@
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-WebInspector.LayoutTimelineView = function(recording)
+WebInspector.LayoutTimelineView = function(timeline)
 {
-    WebInspector.TimelineView.call(this);
+    WebInspector.TimelineView.call(this, timeline);
 
+    console.assert(timeline.type === WebInspector.TimelineRecord.Type.Layout);
+
     this.navigationSidebarTreeOutline._onselect_ = this._treeElementSelected.bind(this);
     this.navigationSidebarTreeOutline.element.classList.add(WebInspector.NavigationSidebarPanel.HideDisclosureButtonsStyleClassName);
     this.navigationSidebarTreeOutline.element.classList.add(WebInspector.LayoutTimelineView.TreeOutlineStyleClassName);
@@ -68,8 +70,7 @@
     this.element.classList.add(WebInspector.LayoutTimelineView.StyleClassName);
     this.element.appendChild(this._dataGrid.element);
 
-    var layoutTimeline = recording.timelines.get(WebInspector.TimelineRecord.Type.Layout);
-    layoutTimeline.addEventListener(WebInspector.Timeline.Event.RecordAdded, this._layoutTimelineRecordAdded, this);
+    timeline.addEventListener(WebInspector.Timeline.Event.RecordAdded, this._layoutTimelineRecordAdded, this);
 
     this._pendingRecords = [];
 };
@@ -187,7 +188,7 @@
         }
 
         if (!treeElement.record.sourceCodeLocation) {
-            WebInspector.timelineSidebarPanel.showTimelineView(WebInspector.TimelineRecord.Type.Layout);
+            WebInspector.timelineSidebarPanel.showTimelineViewForType(WebInspector.TimelineRecord.Type.Layout);
             return;
         }
 

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/NetworkTimelineOverviewGraph.js (172093 => 172094)


--- trunk/Source/WebInspectorUI/UserInterface/Views/NetworkTimelineOverviewGraph.js	2014-08-05 22:28:19 UTC (rev 172093)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/NetworkTimelineOverviewGraph.js	2014-08-05 22:32:58 UTC (rev 172094)
@@ -24,15 +24,14 @@
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-WebInspector.NetworkTimelineOverviewGraph = function(recording)
+WebInspector.NetworkTimelineOverviewGraph = function(timeline)
 {
-    WebInspector.TimelineOverviewGraph.call(this, recording);
+    WebInspector.TimelineOverviewGraph.call(this, timeline);
 
     this.element.classList.add(WebInspector.NetworkTimelineOverviewGraph.StyleClassName);
 
-    var networkTimeline = recording.timelines.get(WebInspector.TimelineRecord.Type.Network);
-    networkTimeline.addEventListener(WebInspector.Timeline.Event.RecordAdded, this._networkTimelineRecordAdded, this);
-    networkTimeline.addEventListener(WebInspector.Timeline.Event.TimesUpdated, this.needsLayout, this);
+    timeline.addEventListener(WebInspector.Timeline.Event.RecordAdded, this._networkTimelineRecordAdded, this);
+    timeline.addEventListener(WebInspector.Timeline.Event.TimesUpdated, this.needsLayout, this);
 
     this.reset();
 };

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/NetworkTimelineView.js (172093 => 172094)


--- trunk/Source/WebInspectorUI/UserInterface/Views/NetworkTimelineView.js	2014-08-05 22:28:19 UTC (rev 172093)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/NetworkTimelineView.js	2014-08-05 22:32:58 UTC (rev 172094)
@@ -23,10 +23,12 @@
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-WebInspector.NetworkTimelineView = function(recording)
+WebInspector.NetworkTimelineView = function(timeline)
 {
-    WebInspector.TimelineView.call(this);
+    WebInspector.TimelineView.call(this, timeline);
 
+    console.assert(timeline.type === WebInspector.TimelineRecord.Type.Network);
+
     this.navigationSidebarTreeOutline._onselect_ = this._treeElementSelected.bind(this);
     this.navigationSidebarTreeOutline.element.classList.add(WebInspector.NavigationSidebarPanel.HideDisclosureButtonsStyleClassName);
     this.navigationSidebarTreeOutline.element.classList.add(WebInspector.NetworkTimelineView.TreeOutlineStyleClassName);
@@ -84,8 +86,7 @@
     this.element.classList.add(WebInspector.NetworkTimelineView.StyleClassName);
     this.element.appendChild(this._dataGrid.element);
 
-    var networkTimeline = recording.timelines.get(WebInspector.TimelineRecord.Type.Network);
-    networkTimeline.addEventListener(WebInspector.Timeline.Event.RecordAdded, this._networkTimelineRecordAdded, this);
+    timeline.addEventListener(WebInspector.Timeline.Event.RecordAdded, this._networkTimelineRecordAdded, this);
 
     this._pendingRecords = [];
 };

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/OverviewTimelineView.js (172093 => 172094)


--- trunk/Source/WebInspectorUI/UserInterface/Views/OverviewTimelineView.js	2014-08-05 22:28:19 UTC (rev 172093)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/OverviewTimelineView.js	2014-08-05 22:32:58 UTC (rev 172094)
@@ -25,12 +25,12 @@
 
 WebInspector.OverviewTimelineView = function(recording)
 {
-    WebInspector.TimelineView.call(this);
+    WebInspector.TimelineView.call(this, recording);
 
+    this.navigationSidebarTreeOutline._onselect_ = this._treeElementSelected.bind(this);
+
     this._recording = recording;
 
-    this.navigationSidebarTreeOutline._onselect_ = this._treeElementSelected.bind(this);
-
     var columns = {"graph": {width: "100%"}};
 
     this._dataGrid = new WebInspector.DataGrid(columns);

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/ScriptTimelineOverviewGraph.js (172093 => 172094)


--- trunk/Source/WebInspectorUI/UserInterface/Views/ScriptTimelineOverviewGraph.js	2014-08-05 22:28:19 UTC (rev 172093)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ScriptTimelineOverviewGraph.js	2014-08-05 22:32:58 UTC (rev 172094)
@@ -23,13 +23,13 @@
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-WebInspector.ScriptTimelineOverviewGraph = function(recording)
+WebInspector.ScriptTimelineOverviewGraph = function(timeline)
 {
-    WebInspector.TimelineOverviewGraph.call(this, recording);
+    WebInspector.TimelineOverviewGraph.call(this, timeline);
 
     this.element.classList.add(WebInspector.ScriptTimelineOverviewGraph.StyleClassName);
 
-    this._scriptTimeline = recording.timelines.get(WebInspector.TimelineRecord.Type.Script);
+    this._scriptTimeline = timeline;
     this._scriptTimeline.addEventListener(WebInspector.Timeline.Event.RecordAdded, this._scriptTimelineRecordAdded, this);
 
     this._timelineRecordBars = [];

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/ScriptTimelineView.js (172093 => 172094)


--- trunk/Source/WebInspectorUI/UserInterface/Views/ScriptTimelineView.js	2014-08-05 22:28:19 UTC (rev 172093)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ScriptTimelineView.js	2014-08-05 22:32:58 UTC (rev 172094)
@@ -23,10 +23,12 @@
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-WebInspector.ScriptTimelineView = function(recording)
+WebInspector.ScriptTimelineView = function(timeline)
 {
-    WebInspector.TimelineView.call(this);
+    WebInspector.TimelineView.call(this, timeline);
 
+    console.assert(timeline.type === WebInspector.TimelineRecord.Type.Script);
+
     this.navigationSidebarTreeOutline._onselect_ = this._treeElementSelected.bind(this);
     this.navigationSidebarTreeOutline.element.classList.add(WebInspector.ScriptTimelineView.TreeOutlineStyleClassName);
 
@@ -67,8 +69,7 @@
     this.element.classList.add(WebInspector.ScriptTimelineView.StyleClassName);
     this.element.appendChild(this._dataGrid.element);
 
-    var scriptTimeline = recording.timelines.get(WebInspector.TimelineRecord.Type.Script);
-    scriptTimeline.addEventListener(WebInspector.Timeline.Event.RecordAdded, this._scriptTimelineRecordAdded, this);
+    timeline.addEventListener(WebInspector.Timeline.Event.RecordAdded, this._scriptTimelineRecordAdded, this);
 
     this._pendingRecords = [];
 };
@@ -271,7 +272,7 @@
             console.error("Unknown tree element selected.");
 
         if (!sourceCodeLocation) {
-            WebInspector.timelineSidebarPanel.showTimelineView(WebInspector.TimelineRecord.Type.Script);
+            WebInspector.timelineSidebarPanel.showTimelineViewForType(WebInspector.TimelineRecord.Type.Script);
             return;
         }
 

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/TimelineContentView.js (172093 => 172094)


--- trunk/Source/WebInspectorUI/UserInterface/Views/TimelineContentView.js	2014-08-05 22:28:19 UTC (rev 172093)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/TimelineContentView.js	2014-08-05 22:32:58 UTC (rev 172094)
@@ -32,9 +32,8 @@
     this.element.classList.add(WebInspector.TimelineContentView.StyleClassName);
 
     this._discreteTimelineOverviewGraphMap = new Map;
-    this._discreteTimelineOverviewGraphMap.set(WebInspector.TimelineRecord.Type.Network, new WebInspector.NetworkTimelineOverviewGraph(recording));
-    this._discreteTimelineOverviewGraphMap.set(WebInspector.TimelineRecord.Type.Layout, new WebInspector.LayoutTimelineOverviewGraph(recording));
-    this._discreteTimelineOverviewGraphMap.set(WebInspector.TimelineRecord.Type.Script, new WebInspector.ScriptTimelineOverviewGraph(recording));
+    for (var [identifier, timeline] of recording.timelines)
+        this._discreteTimelineOverviewGraphMap.set(timeline, new WebInspector.TimelineOverviewGraph(timeline));
 
     this._timelineOverview = new WebInspector.TimelineOverview(this._discreteTimelineOverviewGraphMap);
     this._timelineOverview.addEventListener(WebInspector.TimelineOverview.Event.TimeRangeSelectionChanged, this._timeRangeSelectionChanged, this);
@@ -50,9 +49,8 @@
     this._overviewTimelineView = new WebInspector.OverviewTimelineView(recording);
 
     this._discreteTimelineViewMap = new Map;
-    this._discreteTimelineViewMap.set(WebInspector.TimelineRecord.Type.Network, new WebInspector.NetworkTimelineView(recording));
-    this._discreteTimelineViewMap.set(WebInspector.TimelineRecord.Type.Layout, new WebInspector.LayoutTimelineView(recording));
-    this._discreteTimelineViewMap.set(WebInspector.TimelineRecord.Type.Script, new WebInspector.ScriptTimelineView(recording));
+    for (var [identifier, timeline] of recording.timelines)
+        this._discreteTimelineViewMap.set(timeline, new WebInspector.TimelineView(timeline));
 
     function createPathComponent(displayName, className, representedObject)
     {
@@ -61,10 +59,14 @@
         return pathComponent;
     }
 
+    var networkTimeline = recording.timelines.get(WebInspector.TimelineRecord.Type.Network);
+    var layoutTimeline = recording.timelines.get(WebInspector.TimelineRecord.Type.Layout);
+    var scriptTimeline = recording.timelines.get(WebInspector.TimelineRecord.Type.Script);
+
     this._pathComponentMap = new Map;
-    this._pathComponentMap.set(WebInspector.TimelineRecord.Type.Network, createPathComponent.call(this, WebInspector.UIString("Network Requests"), WebInspector.TimelineSidebarPanel.NetworkIconStyleClass, WebInspector.TimelineRecord.Type.Network));
-    this._pathComponentMap.set(WebInspector.TimelineRecord.Type.Layout, createPathComponent.call(this, WebInspector.UIString("Layout & Rendering"), WebInspector.TimelineSidebarPanel.ColorsIconStyleClass, WebInspector.TimelineRecord.Type.Layout));
-    this._pathComponentMap.set(WebInspector.TimelineRecord.Type.Script, createPathComponent.call(this, WebInspector.UIString("_javascript_ & Events"), WebInspector.TimelineSidebarPanel.ScriptIconStyleClass, WebInspector.TimelineRecord.Type.Script));
+    this._pathComponentMap.set(networkTimeline, createPathComponent.call(this, WebInspector.UIString("Network Requests"), WebInspector.TimelineSidebarPanel.NetworkIconStyleClass, networkTimeline));
+    this._pathComponentMap.set(layoutTimeline, createPathComponent.call(this, WebInspector.UIString("Layout & Rendering"), WebInspector.TimelineSidebarPanel.ColorsIconStyleClass, layoutTimeline));
+    this._pathComponentMap.set(scriptTimeline, createPathComponent.call(this, WebInspector.UIString("_javascript_ & Events"), WebInspector.TimelineSidebarPanel.ScriptIconStyleClass, scriptTimeline));
 
     var previousPathComponent = null;
     for (var pathComponent of this._pathComponentMap.values()) {
@@ -95,6 +97,9 @@
 WebInspector.TimelineContentView.StyleClassName = "timeline";
 WebInspector.TimelineContentView.ViewContainerStyleClassName = "view-container";
 
+WebInspector.TimelineContentView.SelectedTimelineTypeCookieKey = "timeline-content-view-selected-timeline-type";
+WebInspector.TimelineContentView.OverviewTimelineViewCookieValue = "timeline-content-view-overview-timeline-view";
+
 WebInspector.TimelineContentView.prototype = {
     constructor: WebInspector.TimelineContentView,
     __proto__: WebInspector.ContentView.prototype,
@@ -106,13 +111,14 @@
         this._showTimelineView(this._overviewTimelineView);
     },
 
-    showTimelineView: function(identifier)
+    showTimelineViewForTimeline: function(timeline)
     {
-        console.assert(this._discreteTimelineViewMap.has(identifier));
-        if (!this._discreteTimelineViewMap.has(identifier))
+        console.assert(timeline instanceof WebInspector.Timeline, timeline);
+        console.assert(this._discreteTimelineViewMap.has(timeline), timeline);
+        if (!this._discreteTimelineViewMap.has(timeline))
             return;
 
-        this._showTimelineView(this._discreteTimelineViewMap.get(identifier), identifier);
+        this._showTimelineView(this._discreteTimelineViewMap.get(timeline));
     },
 
     get allowedNavigationSidebarPanels()
@@ -128,7 +134,9 @@
 
     get selectionPathComponents()
     {
-        var pathComponents = this._currentTimelineViewIdentifier ? [this._pathComponentMap.get(this._currentTimelineViewIdentifier)] : [];
+        var pathComponents = [];
+        if (this._currentTimelineView.representedObject instanceof WebInspector.Timeline)
+            pathComponents.push(this._pathComponentMap.get(this._currentTimelineView.representedObject));
         pathComponents = pathComponents.concat(this._currentTimelineView.selectionPathComponents || []);
         return pathComponents;
     },
@@ -138,12 +146,18 @@
         return [this._clearTimelineNavigationItem];
     },
 
+    get currentTimelineView()
+    {
+        return this._currentTimelineView;
+    },
+
     shown: function()
     {
         if (!this._currentTimelineView)
             return;
 
         this._currentTimelineView.shown();
+        this._clearTimelineNavigationItem.enabled = this._recording.isWritable();
     },
 
     hidden: function()
@@ -164,6 +178,26 @@
         this._currentTimelineView.updateLayout();
     },
 
+    saveToCookie: function(cookie)
+    {
+        cookie.type = WebInspector.ContentViewCookieType.Timelines;
+
+        if (!this._currentTimelineView || this._currentTimelineView === this._overviewTimelineView)
+            cookie[WebInspector.TimelineContentView.SelectedTimelineTypeCookieKey] = WebInspector.TimelineContentView.OverviewTimelineViewCookieValue;
+        else
+            cookie[WebInspector.TimelineContentView.SelectedTimelineTypeCookieKey] = this._currentTimelineView.representedObject.type;
+    },
+
+    restoreFromCookie: function(cookie)
+    {
+        var timelineType = cookie[WebInspector.TimelineContentView.SelectedTimelineTypeCookieKey];
+
+        if (timelineType === WebInspector.TimelineContentView.OverviewTimelineViewCookieValue)
+            this.showOverviewTimelineView();
+        else
+            this.showTimelineViewForTimeline(this.representedObject.timelines.get(timelineType));
+    },
+
     matchTreeElementAgainstCustomFilters: function(treeElement)
     {
         if (this._currentTimelineView && !this._currentTimelineView.matchTreeElementAgainstCustomFilters(treeElement))
@@ -224,7 +258,7 @@
 
     _pathComponentSelected: function(event)
     {
-        WebInspector.timelineSidebarPanel.showTimelineView(event.data.pathComponent.representedObject);
+        WebInspector.timelineSidebarPanel.showTimelineViewForType(event.data.pathComponent.representedObject.type);
     },
 
     _timelineViewSelectionPathComponentsDidChange: function()
@@ -232,11 +266,12 @@
         this.dispatchEventToListeners(WebInspector.ContentView.Event.SelectionPathComponentsDidChange);
     },
 
-    _showTimelineView: function(timelineView, identifier)
+    _showTimelineView: function(timelineView)
     {
         console.assert(timelineView instanceof WebInspector.TimelineView);
 
-        if (this._currentTimelineView === timelineView)
+        // If the content view is shown and then hidden, we must reattach the content tree outline and timeline view.
+        if (timelineView.visible && timelineView === this._currentTimelineView)
             return;
 
         if (this._currentTimelineView) {
@@ -247,7 +282,6 @@
         }
 
         this._currentTimelineView = timelineView;
-        this._currentTimelineViewIdentifier = identifier || null;
 
         WebInspector.timelineSidebarPanel.contentTreeOutline = timelineView && timelineView.navigationSidebarTreeOutline;
         WebInspector.timelineSidebarPanel.contentTreeOutlineLabel = timelineView && timelineView.navigationSidebarTreeOutlineLabel;

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/TimelineOverviewGraph.js (172093 => 172094)


--- trunk/Source/WebInspectorUI/UserInterface/Views/TimelineOverviewGraph.js	2014-08-05 22:28:19 UTC (rev 172093)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/TimelineOverviewGraph.js	2014-08-05 22:32:58 UTC (rev 172094)
@@ -23,8 +23,29 @@
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-WebInspector.TimelineOverviewGraph = function(recording)
+WebInspector.TimelineOverviewGraph = function(timeline)
 {
+    if (this.constructor === WebInspector.TimelineOverviewGraph) {
+        // When instantiated directly return an instance of a type-based concrete subclass.
+
+        console.assert(timeline && timeline instanceof WebInspector.Timeline);
+
+        var timelineType = timeline.type;
+        if (timelineType === WebInspector.TimelineRecord.Type.Network)
+            return new WebInspector.NetworkTimelineOverviewGraph(timeline);
+
+        if (timelineType === WebInspector.TimelineRecord.Type.Layout)
+            return new WebInspector.LayoutTimelineOverviewGraph(timeline);
+
+        if (timelineType === WebInspector.TimelineRecord.Type.Script)
+            return new WebInspector.ScriptTimelineOverviewGraph(timeline);
+
+        throw Error("Can't make a graph for an unknown timeline.");
+    }
+
+    // Concrete object instantiation.
+    console.assert(this.constructor !== WebInspector.TimelineOverviewGraph && this instanceof WebInspector.TimelineOverviewGraph);
+
     WebInspector.Object.call(this);
 
     this.element = document.createElement("div");

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/TimelineSidebarPanel.js (172093 => 172094)


--- trunk/Source/WebInspectorUI/UserInterface/Views/TimelineSidebarPanel.js	2014-08-05 22:28:19 UTC (rev 172093)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/TimelineSidebarPanel.js	2014-08-05 22:32:58 UTC (rev 172094)
@@ -38,11 +38,40 @@
     this._timelinesContentContainer.classList.add(WebInspector.TimelineSidebarPanel.TimelinesContentContainerStyleClass);
     this.element.insertBefore(this._timelinesContentContainer, this.element.firstChild);
 
+    // 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.element.classList.add(WebInspector.NavigationSidebarPanel.HideDisclosureButtonsStyleClassName);
+    this._recordingsTreeOutline.element.classList.add(WebInspector.NavigationSidebarPanel.ContentTreeOutlineElementHiddenStyleClassName);
+    this._recordingsTreeOutline._onselect_ = this._recordingsTreeElementSelected.bind(this);
+    this._timelinesContentContainer.appendChild(this._recordingsTreeOutline.element);
+
     this._timelinesTreeOutline = this.createContentTreeOutline(true, true);
     this._timelinesTreeOutline.element.classList.add(WebInspector.NavigationSidebarPanel.HideDisclosureButtonsStyleClassName);
     this._timelinesTreeOutline._onselect_ = this._timelinesTreeElementSelected.bind(this);
     this._timelinesContentContainer.appendChild(this._timelinesTreeOutline.element);
 
+    function createTimelineTreeElement(label, iconClass, identifier)
+    {
+        var treeElement = new WebInspector.GeneralTreeElement([iconClass, WebInspector.TimelineSidebarPanel.LargeIconStyleClass], label, null, identifier);
+        var closeButton = document.createElement("img");
+        closeButton.classList.add(WebInspector.TimelineSidebarPanel.CloseButtonStyleClass);
+        closeButton.addEventListener("click", this.showTimelineOverview.bind(this));
+        treeElement.status = closeButton;
+        return treeElement;
+    }
+
+    // Timeline elements are reused; clicking them displays a TimelineView
+    // for the relevant timeline of the active recording.
+    this._timelineTreeElementMap = new Map;
+    this._timelineTreeElementMap.set(WebInspector.TimelineRecord.Type.Network, createTimelineTreeElement.call(this, WebInspector.UIString("Network Requests"), WebInspector.TimelineSidebarPanel.NetworkIconStyleClass, WebInspector.TimelineRecord.Type.Network));
+    this._timelineTreeElementMap.set(WebInspector.TimelineRecord.Type.Layout, createTimelineTreeElement.call(this, WebInspector.UIString("Layout & Rendering"), WebInspector.TimelineSidebarPanel.ColorsIconStyleClass, WebInspector.TimelineRecord.Type.Layout));
+    this._timelineTreeElementMap.set(WebInspector.TimelineRecord.Type.Script, createTimelineTreeElement.call(this, WebInspector.UIString("_javascript_ & Events"), WebInspector.TimelineSidebarPanel.ScriptIconStyleClass, WebInspector.TimelineRecord.Type.Script));
+
+    for (var timelineTreeElement of this._timelineTreeElementMap.values())
+        this._timelinesTreeOutline.appendChild(timelineTreeElement);
+
     var timelinesTitleBarElement = document.createElement("div");
     timelinesTitleBarElement.textContent = WebInspector.UIString("Timelines");
     timelinesTitleBarElement.classList.add(WebInspector.TimelineSidebarPanel.TitleBarStyleClass);
@@ -91,27 +120,9 @@
     this._navigationBar.element._oncontextmenu_ = this._contextMenuNavigationBarOrStatusBar.bind(this);
     this._updateReplayInterfaceVisibility();
 
-    function createTimelineTreeElement(label, iconClass, identifier)
-    {
-        var treeElement = new WebInspector.GeneralTreeElement([iconClass, WebInspector.TimelineSidebarPanel.LargeIconStyleClass], label, null, identifier);
-        var closeButton = document.createElement("img");
-        closeButton.classList.add(WebInspector.TimelineSidebarPanel.CloseButtonStyleClass);
-        closeButton.addEventListener("click", this.showTimelineOverview.bind(this));
-        treeElement.status = closeButton;
-        return treeElement;
-    }
+    WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.RecordingCreated, this._recordingCreated, this);
+    WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.RecordingLoaded, this._recordingLoaded, this);
 
-    this._timelineTreeElementMap = new Map;
-    this._timelineTreeElementMap.set(WebInspector.TimelineRecord.Type.Network, createTimelineTreeElement.call(this, WebInspector.UIString("Network Requests"), WebInspector.TimelineSidebarPanel.NetworkIconStyleClass, WebInspector.TimelineRecord.Type.Network));
-    this._timelineTreeElementMap.set(WebInspector.TimelineRecord.Type.Layout, createTimelineTreeElement.call(this, WebInspector.UIString("Layout & Rendering"), WebInspector.TimelineSidebarPanel.ColorsIconStyleClass, WebInspector.TimelineRecord.Type.Layout));
-    this._timelineTreeElementMap.set(WebInspector.TimelineRecord.Type.Script, createTimelineTreeElement.call(this, WebInspector.UIString("_javascript_ & Events"), WebInspector.TimelineSidebarPanel.ScriptIconStyleClass, WebInspector.TimelineRecord.Type.Script));
-
-    for (var timelineTreeElement of this._timelineTreeElementMap.values())
-        this._timelinesTreeOutline.appendChild(timelineTreeElement);
-
-    this._timelineOverviewTreeElement = new WebInspector.GeneralTreeElement(WebInspector.TimelineSidebarPanel.StopwatchIconStyleClass, WebInspector.UIString("Timelines"), null, WebInspector.timelineManager.activeRecording);
-    this._timelineOverviewTreeElement.addEventListener(WebInspector.HierarchicalPathComponent.Event.SiblingWasSelected, this.showTimelineOverview, this);
-
     this._stripeBackgroundElement = document.createElement("div");
     this._stripeBackgroundElement.className = WebInspector.TimelineSidebarPanel.StripeBackgroundStyleClass;
     this.contentElement.insertBefore(this._stripeBackgroundElement, this.contentElement.firstChild);
@@ -139,6 +150,7 @@
 WebInspector.TimelineSidebarPanel.ColorsIconStyleClass = "colors-icon";
 WebInspector.TimelineSidebarPanel.ScriptIconStyleClass = "script-icon";
 WebInspector.TimelineSidebarPanel.TimelineContentViewShowingStyleClass = "timeline-content-view-showing";
+
 WebInspector.TimelineSidebarPanel.ShowingTimelineContentViewCookieKey = "timeline-sidebar-panel-showing-timeline-content-view";
 WebInspector.TimelineSidebarPanel.SelectedTimelineViewIdentifierCookieKey = "timeline-sidebar-panel-selected-timeline-view-identifier";
 WebInspector.TimelineSidebarPanel.OverviewTimelineIdentifierCookieValue = "overview";
@@ -149,21 +161,29 @@
 
     // Public
 
-    initialize: function()
+    shown: function()
     {
-        // Prime the creation of the singleton TimelineContentView since it needs to listen for events.
-        this._timelineContentView = WebInspector.contentBrowser.contentViewForRepresentedObject(WebInspector.timelineManager.activeRecording);
+        WebInspector.NavigationSidebarPanel.prototype.shown.call(this);
+
+        if (this._activeContentView)
+            WebInspector.contentBrowser.showContentView(this._activeContentView);
     },
 
     showDefaultContentView: function()
     {
-        WebInspector.contentBrowser.showContentView(this._timelineContentView);
+        if (this._activeContentView)
+            this.showTimelineOverview();
     },
 
+    get hasSelectedElement()
+    {
+        return !!this._contentTreeOutline.selectedTreeElement || !!this._recordingsTreeOutline.selectedTreeElement;
+    },
+
     treeElementForRepresentedObject: function(representedObject)
     {
         if (representedObject instanceof WebInspector.TimelineRecording)
-            return this._timelineOverviewTreeElement;
+            return this._recordingTreeElementMap.get(representedObject);
 
         // The main resource is used as the representedObject instead of Frame in our tree.
         if (representedObject instanceof WebInspector.Frame)
@@ -200,7 +220,7 @@
             if (candidateRepresentedObject instanceof WebInspector.ProfileNode)
                 return false;
 
-            console.error("Unknown TreeElement");
+            console.error("Unknown TreeElement", candidateTreeElement);
             return false;
         }
 
@@ -237,20 +257,20 @@
         if (this._timelinesTreeOutline.selectedTreeElement)
             this._timelinesTreeOutline.selectedTreeElement.deselect();
 
-        this._timelineContentView.showOverviewTimelineView();
-        WebInspector.contentBrowser.showContentView(this._timelineContentView);
+        this._activeContentView.showOverviewTimelineView();
+        WebInspector.contentBrowser.showContentView(this._activeContentView);
     },
 
-    showTimelineView: function(identifier)
+    showTimelineViewForType: function(timelineType)
     {
-        console.assert(this._timelineTreeElementMap.has(identifier));
-        if (!this._timelineTreeElementMap.has(identifier))
+        console.assert(this._timelineTreeElementMap.has(timelineType), timelineType);
+        if (!this._timelineTreeElementMap.has(timelineType))
             return;
 
-        this._timelineTreeElementMap.get(identifier).select(true, false, true, true);
-
-        this._timelineContentView.showTimelineView(identifier);
-        WebInspector.contentBrowser.showContentView(this._timelineContentView);
+        // Defer showing the relevant timeline to the onselect handler of the timelines tree element.
+        const wasSelectedByUser = true;
+        const shouldSuppressOnSelect = false;
+        this._timelineTreeElementMap.get(timelineType).select(true, wasSelectedByUser, shouldSuppressOnSelect, true);
     },
 
     // Protected
@@ -273,10 +293,10 @@
 
     matchTreeElementAgainstCustomFilters: function(treeElement)
     {
-        if (!this._timelineContentView)
+        if (!this._activeContentView)
             return true;
 
-        return this._timelineContentView.matchTreeElementAgainstCustomFilters(treeElement);
+        return this._activeContentView.matchTreeElementAgainstCustomFilters(treeElement);
     },
 
     canShowDifferentContentView: function()
@@ -288,11 +308,11 @@
     {
         console.assert(cookie);
 
-        cookie[WebInspector.timelineSidebarPanel.ShowingTimelineContentViewCookieKey] = WebInspector.contentBrowser.currentContentView instanceof WebInspector.TimelineContentView;
+        cookie[WebInspector.TimelineSidebarPanel.ShowingTimelineContentViewCookieKey] = WebInspector.contentBrowser.currentContentView instanceof WebInspector.TimelineContentView;
 
         var selectedTreeElement = this._timelinesTreeOutline.selectedTreeElement;
         if (selectedTreeElement)
-            cookie[WebInspector.TimelineSidebarPanel.SelectedTimelineViewIdentifierCookieKey] = selectedTreeElement.representedObject;
+            cookie[WebInspector.TimelineSidebarPanel.SelectedTimelineViewIdentifierCookieKey] = selectedTreeElement.representedObject.type;
         else
             cookie[WebInspector.TimelineSidebarPanel.SelectedTimelineViewIdentifierCookieKey] = WebInspector.TimelineSidebarPanel.OverviewTimelineIdentifierCookieValue;
 
@@ -303,38 +323,63 @@
     {
         console.assert(cookie);
 
-        // The _timelineContentView is not ready on initial load, so delay the restore.
+        // The _activeContentView is not ready on initial load, so delay the restore.
         // This matches the delayed work in the WebInspector.TimelineSidebarPanel constructor.
-        if (!this._timelineContentView) {
+        if (!this._activeContentView) {
             setTimeout(this.restoreStateFromCookie.bind(this, cookie, relaxedMatchDelay), 0);
             return;
         }
 
-        this._restoredShowingTimelineContentView = cookie[WebInspector.timelineSidebarPanel.ShowingTimelineContentViewCookieKey];
+        this._restoredShowingTimelineContentView = cookie[WebInspector.TimelineSidebarPanel.ShowingTimelineContentViewCookieKey];
 
         var selectedTimelineViewIdentifier = cookie[WebInspector.TimelineSidebarPanel.SelectedTimelineViewIdentifierCookieKey];
         if (selectedTimelineViewIdentifier === WebInspector.TimelineSidebarPanel.OverviewTimelineIdentifierCookieValue)
             this.showTimelineOverview();
         else
-            this.showTimelineView(selectedTimelineViewIdentifier);
+            this.showTimelineViewForType(selectedTimelineViewIdentifier);
 
         WebInspector.NavigationSidebarPanel.prototype.restoreStateFromCookie.call(this, cookie, relaxedMatchDelay);
     },
 
     // Private
 
+    _recordingsTreeElementSelected: function(treeElement, selectedByUser)
+    {
+        console.assert(treeElement.representedObject instanceof WebInspector.TimelineRecording);
+        console.assert(!selectedByUser, "Recording tree elements should be hidden and only programmatically selectable.")
+
+        this._activeContentView = WebInspector.contentBrowser.contentViewForRepresentedObject(treeElement.representedObject);
+
+        // Deselect or re-select the timeline tree element for the timeline view being displayed.
+        var currentTimelineView = this._activeContentView.currentTimelineView;
+        if (currentTimelineView && currentTimelineView.representedObject instanceof WebInspector.Timeline) {
+            const wasSelectedByUser = false; // This is a simulated selection.
+            const shouldSuppressOnSelect = false;
+            this._timelineTreeElementMap.get(currentTimelineView.representedObject.type).select(true, wasSelectedByUser, shouldSuppressOnSelect, true);
+        } else if (this._timelinesTreeOutline.selectedTreeElement)
+            this._timelinesTreeOutline.selectedTreeElement.deselect();
+    },
+
     _timelinesTreeElementSelected: function(treeElement, selectedByUser)
     {
-        console.assert(this._timelineTreeElementMap.get(treeElement.representedObject) === treeElement);
-        this.showTimelineView(treeElement.representedObject);
+        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.
+        if (!selectedByUser) {
+            console.assert(this._activeContentView.currentTimelineView.representedObject.type === treeElement.representedObject);
+            return;
+        }
+
+        var timelineType = treeElement.representedObject;
+        var timeline = this._activeContentView.representedObject.timelines.get(timelineType);
+        this._activeContentView.showTimelineViewForTimeline(timeline);
+        WebInspector.contentBrowser.showContentView(this._activeContentView);
     },
 
     _contentBrowserCurrentContentViewDidChange: function(event)
     {
-        if (WebInspector.contentBrowser.currentContentView instanceof WebInspector.TimelineContentView)
-            this.element.classList.add(WebInspector.TimelineSidebarPanel.TimelineContentViewShowingStyleClass);
-        else
-            this.element.classList.remove(WebInspector.TimelineSidebarPanel.TimelineContentViewShowingStyleClass);
+        var didShowTimelineContentView = WebInspector.contentBrowser.currentContentView instanceof WebInspector.TimelineContentView;
+        this.element.classList.toggle(WebInspector.TimelineSidebarPanel.TimelineContentViewShowingStyleClass, didShowTimelineContentView);
     },
 
     _capturingStarted: function(event)
@@ -349,6 +394,32 @@
         this._recordGlyphElement.classList.remove(WebInspector.TimelineSidebarPanel.RecordGlyphRecordingStyleClass);
     },
 
+    _recordingCreated: function(event)
+    {
+        var recording = event.data.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);
+
+        var previousTreeElement = null;
+        for (var treeElement of this._recordingTreeElementMap.values()) {
+            if (previousTreeElement) {
+                previousTreeElement.nextSibling = treeElement;
+                treeElement.previousSibling = previousTreeElement;
+            }
+
+            previousTreeElement = treeElement;
+        }
+    },
+
+    _recordingLoaded: function()
+    {
+        this._activeContentView = WebInspector.contentBrowser.contentViewForRepresentedObject(WebInspector.timelineManager.activeRecording);
+        WebInspector.contentBrowser.showContentView(this._activeContentView);
+    },
+
     _recordGlyphMousedOver: function(event)
     {
         this._recordGlyphElement.classList.remove(WebInspector.TimelineSidebarPanel.RecordGlyphRecordingForcedStyleClass);
@@ -374,10 +445,15 @@
         // Add forced class to prevent the glyph from showing a confusing status after click.
         this._recordGlyphElement.classList.add(WebInspector.TimelineSidebarPanel.RecordGlyphRecordingForcedStyleClass);
 
+        var shouldCreateRecording = event.shiftKey;
+
         if (WebInspector.timelineManager.isCapturing())
             WebInspector.timelineManager.stopCapturing();
-        else
-            WebInspector.timelineManager.startCapturing();
+        else {
+            WebInspector.timelineManager.startCapturing(shouldCreateRecording);
+            // Show the timeline to which events will be appended.
+            this._recordingLoaded();
+        }
     },
 
     // These methods are only used when ReplayAgent is available.

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/TimelineView.js (172093 => 172094)


--- trunk/Source/WebInspectorUI/UserInterface/Views/TimelineView.js	2014-08-05 22:28:19 UTC (rev 172093)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/TimelineView.js	2014-08-05 22:32:58 UTC (rev 172094)
@@ -23,10 +23,34 @@
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-WebInspector.TimelineView = function()
+WebInspector.TimelineView = function(representedObject)
 {
+    if (this.constructor === WebInspector.TimelineView) {
+        // When instantiated directly return an instance of a type-based concrete subclass.
+
+        console.assert(representedObject && representedObject instanceof WebInspector.Timeline);
+
+        var timelineType = representedObject.type;
+        if (timelineType === WebInspector.TimelineRecord.Type.Network)
+            return new WebInspector.NetworkTimelineView(representedObject);
+
+        if (timelineType === WebInspector.TimelineRecord.Type.Layout)
+            return new WebInspector.LayoutTimelineView(representedObject);
+
+        if (timelineType === WebInspector.TimelineRecord.Type.Script)
+            return new WebInspector.ScriptTimelineView(representedObject);
+
+        throw Error("Can't make a Timeline for an unknown representedObject.");
+    }
+
+    // Concrete object instantiation.
+    console.assert(this.constructor !== WebInspector.TimelineView && this instanceof WebInspector.TimelineView);
+
     WebInspector.Object.call(this);
 
+    console.assert(representedObject instanceof WebInspector.Timeline || representedObject instanceof WebInspector.TimelineRecording);
+    this._representedObject = representedObject;
+
     this._contentTreeOutline = WebInspector.timelineSidebarPanel.createContentTreeOutline();
 
     this.element = document.createElement("div");
@@ -50,6 +74,11 @@
 
     // Public
 
+    get representedObject()
+    {
+        return this._representedObject;
+    },
+
     get navigationSidebarTreeOutline()
     {
         return this._contentTreeOutline;
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to