Web Inspector: drag/drop over the sidebar should load an imported file in Canvas...
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / TimelineTabContentView.js
1 /*
2  * Copyright (C) 2015 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 WI.TimelineTabContentView = class TimelineTabContentView extends WI.ContentBrowserTabContentView
27 {
28     constructor(identifier)
29     {
30         let tabBarItem = WI.GeneralTabBarItem.fromTabInfo(WI.TimelineTabContentView.tabInfo());
31         let detailsSidebarPanelConstructors = [WI.ResourceDetailsSidebarPanel, WI.ProbeDetailsSidebarPanel];
32
33         super(identifier || "timeline", "timeline", tabBarItem, null, detailsSidebarPanelConstructors);
34
35         // Maintain an invisible tree outline containing tree elements for all recordings.
36         // The visible recording's tree element is selected when the content view changes.
37         this._recordingTreeElementMap = new Map;
38         this._recordingsTreeOutline = new WI.TreeOutline;
39         this._recordingsTreeOutline.addEventListener(WI.TreeOutline.Event.SelectionDidChange, this._recordingsTreeSelectionDidChange, this);
40
41         this._toggleRecordingShortcut = new WI.KeyboardShortcut(null, WI.KeyboardShortcut.Key.Space, this._toggleRecordingOnSpacebar.bind(this));
42         this._toggleRecordingShortcut.implicitlyPreventsDefault = false;
43         this._toggleRecordingShortcut.disabled = true;
44
45         this._toggleNewRecordingShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.Shift, WI.KeyboardShortcut.Key.Space, this._toggleNewRecordingOnSpacebar.bind(this));
46         this._toggleNewRecordingShortcut.implicitlyPreventsDefault = false;
47         this._toggleNewRecordingShortcut.disabled = true;
48
49         let toolTip = WI.UIString("Start recording (%s)\nCreate new recording (%s)").format(this._toggleRecordingShortcut.displayName, this._toggleNewRecordingShortcut.displayName);
50         let altToolTip = WI.UIString("Stop recording (%s)").format(this._toggleRecordingShortcut.displayName);
51         this._recordButton = new WI.ToggleButtonNavigationItem("record-start-stop", toolTip, altToolTip, "Images/Record.svg", "Images/Stop.svg", 13, 13);
52         this._recordButton.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
53         this._recordButton.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._recordButtonClicked, this);
54
55         this._continueButton = new WI.ButtonNavigationItem("record-continue", WI.UIString("Continue without automatically stopping"), "Images/Resume.svg", 13, 13);
56         this._continueButton.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
57         this._continueButton.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._continueButtonClicked, this);
58         this._continueButton.hidden = true;
59
60         this.contentBrowser.navigationBar.insertNavigationItem(this._recordButton, 0);
61         this.contentBrowser.navigationBar.insertNavigationItem(this._continueButton, 1);
62
63         if (WI.FPSInstrument.supported()) {
64             let timelinesNavigationItem = new WI.RadioButtonNavigationItem(WI.TimelineOverview.ViewMode.Timelines, WI.UIString("Events"));
65             let renderingFramesNavigationItem = new WI.RadioButtonNavigationItem(WI.TimelineOverview.ViewMode.RenderingFrames, WI.UIString("Frames"));
66
67             let viewModeGroup = new WI.GroupNavigationItem([timelinesNavigationItem, renderingFramesNavigationItem]);
68             viewModeGroup.visibilityPriority = WI.NavigationItem.VisibilityPriority.High;
69
70             this.contentBrowser.navigationBar.insertNavigationItem(viewModeGroup, 2);
71             this.contentBrowser.navigationBar.addEventListener(WI.NavigationBar.Event.NavigationItemSelected, this._viewModeSelected, this);
72         }
73
74         WI.timelineManager.addEventListener(WI.TimelineManager.Event.CapturingStateChanged, this._handleTimelineCapturingStateChanged, this);
75         WI.timelineManager.addEventListener(WI.TimelineManager.Event.RecordingCreated, this._recordingCreated, this);
76         WI.timelineManager.addEventListener(WI.TimelineManager.Event.RecordingLoaded, this._recordingLoaded, this);
77
78         WI.notifications.addEventListener(WI.Notification.VisibilityStateDidChange, this._inspectorVisibilityChanged, this);
79         WI.notifications.addEventListener(WI.Notification.GlobalModifierKeysDidChange, this._globalModifierKeysDidChange, this);
80
81         this._displayedRecording = null;
82         this._displayedContentView = null;
83         this._viewMode = null;
84         this._previousSelectedTimelineType = null;
85
86         const selectedByUser = false;
87         this._changeViewMode(WI.TimelineOverview.ViewMode.Timelines, selectedByUser);
88
89         for (let recording of WI.timelineManager.recordings)
90             this._addRecording(recording);
91
92         this._recordingCountChanged();
93
94         // Explicitly update the path for the navigation bar to prevent it from showing up as blank.
95         this.contentBrowser.updateHierarchicalPathForCurrentContentView();
96     }
97
98     // Static
99
100     static tabInfo()
101     {
102         return {
103             image: "Images/Timeline.svg",
104             title: WI.UIString("Timelines"),
105         };
106     }
107
108     static isTabAllowed()
109     {
110         return !!window.TimelineAgent || !!window.ScriptProfilerAgent;
111     }
112
113     static displayNameForTimelineType(timelineType)
114     {
115         switch (timelineType) {
116         case WI.TimelineRecord.Type.Network:
117             return WI.UIString("Network Requests");
118         case WI.TimelineRecord.Type.Layout:
119             return WI.UIString("Layout & Rendering");
120         case WI.TimelineRecord.Type.Script:
121             return WI.UIString("JavaScript & Events");
122         case WI.TimelineRecord.Type.RenderingFrame:
123             return WI.UIString("Rendering Frames");
124         case WI.TimelineRecord.Type.CPU:
125             return WI.UIString("CPU");
126         case WI.TimelineRecord.Type.Memory:
127             return WI.UIString("Memory");
128         case WI.TimelineRecord.Type.HeapAllocations:
129             return WI.UIString("JavaScript Allocations");
130         case WI.TimelineRecord.Type.Media:
131             return WI.UIString("Media");
132         default:
133             console.error("Unknown Timeline type:", timelineType);
134         }
135
136         return null;
137     }
138
139     static iconClassNameForTimelineType(timelineType)
140     {
141         switch (timelineType) {
142         case WI.TimelineRecord.Type.Network:
143             return "network-icon";
144         case WI.TimelineRecord.Type.Layout:
145             return "layout-icon";
146         case WI.TimelineRecord.Type.CPU:
147             return "cpu-icon";
148         case WI.TimelineRecord.Type.Memory:
149             return "memory-icon";
150         case WI.TimelineRecord.Type.HeapAllocations:
151             return "heap-allocations-icon";
152         case WI.TimelineRecord.Type.Script:
153             return "script-icon";
154         case WI.TimelineRecord.Type.RenderingFrame:
155             return "rendering-frame-icon";
156         case WI.TimelineRecord.Type.Media:
157             return "media-icon";
158         default:
159             console.error("Unknown Timeline type:", timelineType);
160         }
161
162         return null;
163     }
164
165     static genericClassNameForTimelineType(timelineType)
166     {
167         switch (timelineType) {
168         case WI.TimelineRecord.Type.Network:
169             return "network";
170         case WI.TimelineRecord.Type.Layout:
171             return "colors";
172         case WI.TimelineRecord.Type.CPU:
173             return "cpu";
174         case WI.TimelineRecord.Type.Memory:
175             return "memory";
176         case WI.TimelineRecord.Type.HeapAllocations:
177             return "heap-allocations";
178         case WI.TimelineRecord.Type.Script:
179             return "script";
180         case WI.TimelineRecord.Type.RenderingFrame:
181             return "rendering-frame";
182         case WI.TimelineRecord.Type.Media:
183             return "media";
184         default:
185             console.error("Unknown Timeline type:", timelineType);
186         }
187
188         return null;
189     }
190
191     static iconClassNameForRecord(timelineRecord)
192     {
193         switch (timelineRecord.type) {
194         case WI.TimelineRecord.Type.Layout:
195             switch (timelineRecord.eventType) {
196             case WI.LayoutTimelineRecord.EventType.InvalidateStyles:
197             case WI.LayoutTimelineRecord.EventType.RecalculateStyles:
198                 return WI.TimelineRecordTreeElement.StyleRecordIconStyleClass;
199             case WI.LayoutTimelineRecord.EventType.InvalidateLayout:
200             case WI.LayoutTimelineRecord.EventType.ForcedLayout:
201             case WI.LayoutTimelineRecord.EventType.Layout:
202                 return WI.TimelineRecordTreeElement.LayoutRecordIconStyleClass;
203             case WI.LayoutTimelineRecord.EventType.Paint:
204                 return WI.TimelineRecordTreeElement.PaintRecordIconStyleClass;
205             case WI.LayoutTimelineRecord.EventType.Composite:
206                 return WI.TimelineRecordTreeElement.CompositeRecordIconStyleClass;
207             default:
208                 console.error("Unknown LayoutTimelineRecord eventType: " + timelineRecord.eventType, timelineRecord);
209             }
210
211             break;
212
213         case WI.TimelineRecord.Type.Script:
214             switch (timelineRecord.eventType) {
215             case WI.ScriptTimelineRecord.EventType.APIScriptEvaluated:
216                 return WI.TimelineRecordTreeElement.APIRecordIconStyleClass;
217             case WI.ScriptTimelineRecord.EventType.ScriptEvaluated:
218                 return WI.TimelineRecordTreeElement.EvaluatedRecordIconStyleClass;
219             case WI.ScriptTimelineRecord.EventType.MicrotaskDispatched:
220             case WI.ScriptTimelineRecord.EventType.EventDispatched:
221             case WI.ScriptTimelineRecord.EventType.ObserverCallback:
222                 return WI.TimelineRecordTreeElement.EventRecordIconStyleClass;
223             case WI.ScriptTimelineRecord.EventType.ProbeSampleRecorded:
224                 return WI.TimelineRecordTreeElement.ProbeRecordIconStyleClass;
225             case WI.ScriptTimelineRecord.EventType.ConsoleProfileRecorded:
226                 return WI.TimelineRecordTreeElement.ConsoleProfileIconStyleClass;
227             case WI.ScriptTimelineRecord.EventType.GarbageCollected:
228                 return WI.TimelineRecordTreeElement.GarbageCollectionIconStyleClass;
229             case WI.ScriptTimelineRecord.EventType.TimerInstalled:
230                 return WI.TimelineRecordTreeElement.TimerRecordIconStyleClass;
231             case WI.ScriptTimelineRecord.EventType.TimerFired:
232             case WI.ScriptTimelineRecord.EventType.TimerRemoved:
233                 return WI.TimelineRecordTreeElement.TimerRecordIconStyleClass;
234             case WI.ScriptTimelineRecord.EventType.AnimationFrameFired:
235             case WI.ScriptTimelineRecord.EventType.AnimationFrameRequested:
236             case WI.ScriptTimelineRecord.EventType.AnimationFrameCanceled:
237                 return WI.TimelineRecordTreeElement.AnimationRecordIconStyleClass;
238             default:
239                 console.error("Unknown ScriptTimelineRecord eventType: " + timelineRecord.eventType, timelineRecord);
240             }
241
242             break;
243
244         case WI.TimelineRecord.Type.RenderingFrame:
245             return WI.TimelineRecordTreeElement.RenderingFrameRecordIconStyleClass;
246
247         case WI.TimelineRecord.Type.HeapAllocations:
248             return "heap-snapshot-record";
249
250         case WI.TimelineRecord.Type.Media:
251             switch (timelineRecord.eventType) {
252             case WI.MediaTimelineRecord.EventType.DOMEvent:
253                 return "dom-event-record";
254             case WI.MediaTimelineRecord.EventType.LowPower:
255                 return "low-power-record";
256             default:
257                 console.error("Unknown MediaTimelineRecord eventType: " + timelineRecord.eventType, timelineRecord);
258             }
259
260             break;
261
262         case WI.TimelineRecord.Type.CPU:
263         case WI.TimelineRecord.Type.Memory:
264             // Not used. Fall through to error just in case.
265
266         default:
267             console.error("Unknown TimelineRecord type: " + timelineRecord.type, timelineRecord);
268         }
269
270         return null;
271     }
272
273     static displayNameForRecord(timelineRecord, includeDetailsInMainTitle)
274     {
275         switch (timelineRecord.type) {
276         case WI.TimelineRecord.Type.Network:
277             return WI.displayNameForURL(timelineRecord.resource.url, timelineRecord.resource.urlComponents);
278         case WI.TimelineRecord.Type.Layout:
279             return WI.LayoutTimelineRecord.displayNameForEventType(timelineRecord.eventType);
280         case WI.TimelineRecord.Type.Script:
281             return WI.ScriptTimelineRecord.EventType.displayName(timelineRecord.eventType, timelineRecord.details, includeDetailsInMainTitle);
282         case WI.TimelineRecord.Type.RenderingFrame:
283             return WI.UIString("Frame %d").format(timelineRecord.frameNumber);
284         case WI.TimelineRecord.Type.HeapAllocations:
285             if (timelineRecord.heapSnapshot.imported)
286                 return WI.UIString("Imported \u2014 %s").format(timelineRecord.heapSnapshot.title);
287             if (timelineRecord.heapSnapshot.title)
288                 return WI.UIString("Snapshot %d \u2014 %s").format(timelineRecord.heapSnapshot.identifier, timelineRecord.heapSnapshot.title);
289             return WI.UIString("Snapshot %d").format(timelineRecord.heapSnapshot.identifier);
290         case WI.TimelineRecord.Type.Media:
291             return timelineRecord.displayName;
292         case WI.TimelineRecord.Type.CPU:
293         case WI.TimelineRecord.Type.Memory:
294             // Not used. Fall through to error just in case.
295         default:
296             console.error("Unknown TimelineRecord type: " + timelineRecord.type, timelineRecord);
297         }
298
299         return null;
300     }
301
302     // Public
303
304     get type()
305     {
306         return WI.TimelineTabContentView.Type;
307     }
308
309     shown()
310     {
311         super.shown();
312
313         this._toggleRecordingShortcut.disabled = false;
314         this._toggleNewRecordingShortcut.disabled = false;
315
316         if (WI.visible)
317             WI.timelineManager.autoCaptureOnPageLoad = true;
318     }
319
320     hidden()
321     {
322         super.hidden();
323
324         this._toggleRecordingShortcut.disabled = true;
325         this._toggleNewRecordingShortcut.disabled = true;
326
327         WI.timelineManager.autoCaptureOnPageLoad = false;
328     }
329
330     closed()
331     {
332         super.closed();
333
334         if (WI.FPSInstrument.supported())
335             this.contentBrowser.navigationBar.removeEventListener(null, null, this);
336
337         WI.timelineManager.removeEventListener(null, null, this);
338         WI.notifications.removeEventListener(null, null, this);
339     }
340
341     canShowRepresentedObject(representedObject)
342     {
343         return representedObject instanceof WI.TimelineRecording;
344     }
345
346     async handleFileDrop(files)
347     {
348         await WI.FileUtilities.readJSON(files, (result) => WI.timelineManager.processJSON(result));
349     }
350
351     // Protected
352
353     restoreFromCookie(cookie)
354     {
355         console.assert(cookie);
356
357         this._restoredShowingTimelineRecordingContentView = cookie[WI.TimelineTabContentView.ShowingTimelineRecordingContentViewCookieKey];
358         if (!this._restoredShowingTimelineRecordingContentView) {
359             if (!this.contentBrowser.currentContentView) {
360                 // If this is the first time opening the tab, render the currently active recording.
361                 if (!this._displayedRecording && WI.timelineManager.activeRecording)
362                     this._recordingLoaded();
363
364                 this._showTimelineViewForType(WI.TimelineTabContentView.OverviewTimelineIdentifierCookieValue);
365             }
366             return;
367         }
368
369         let selectedTimelineViewIdentifier = cookie[WI.TimelineTabContentView.SelectedTimelineViewIdentifierCookieKey];
370         if (selectedTimelineViewIdentifier === WI.TimelineRecord.Type.RenderingFrame && !WI.FPSInstrument.supported())
371             selectedTimelineViewIdentifier = null;
372
373         this._showTimelineViewForType(selectedTimelineViewIdentifier);
374
375         super.restoreFromCookie(cookie);
376     }
377
378     saveToCookie(cookie)
379     {
380         console.assert(cookie);
381
382         cookie[WI.TimelineTabContentView.ShowingTimelineRecordingContentViewCookieKey] = this.contentBrowser.currentContentView instanceof WI.TimelineRecordingContentView;
383
384         if (this._viewMode === WI.TimelineOverview.ViewMode.RenderingFrames)
385             cookie[WI.TimelineTabContentView.SelectedTimelineViewIdentifierCookieKey] = WI.TimelineRecord.Type.RenderingFrame;
386         else {
387             let selectedTimeline = this._getTimelineForCurrentContentView();
388             if (selectedTimeline)
389                 cookie[WI.TimelineTabContentView.SelectedTimelineViewIdentifierCookieKey] = selectedTimeline.type;
390             else
391                 cookie[WI.TimelineTabContentView.SelectedTimelineViewIdentifierCookieKey] = WI.TimelineTabContentView.OverviewTimelineIdentifierCookieValue;
392         }
393
394         super.saveToCookie(cookie);
395     }
396
397     treeElementForRepresentedObject(representedObject)
398     {
399         // This can be called by the base class constructor before the map is created.
400         if (!this._recordingTreeElementMap)
401             return null;
402
403         if (representedObject instanceof WI.TimelineRecording)
404             return this._recordingTreeElementMap.get(representedObject);
405
406         return null;
407     }
408
409     // Private
410
411     _showRecordButton()
412     {
413         this._recordButton.hidden = false;
414         this._continueButton.hidden = true;
415     }
416
417     _showContinueButton()
418     {
419         this._recordButton.hidden = true;
420         this._continueButton.hidden = false;
421     }
422
423     _updateNavigationBarButtons()
424     {
425         if (!WI.modifierKeys.altKey || !WI.timelineManager.willAutoStop())
426             this._showRecordButton();
427         else
428             this._showContinueButton();
429     }
430
431     _handleTimelineCapturingStateChanged(event)
432     {
433         let enabled = WI.timelineManager.capturingState === WI.TimelineManager.CapturingState.Active || WI.timelineManager.capturingState === WI.TimelineManager.CapturingState.Inactive;
434
435         this._toggleRecordingShortcut.disabled = !enabled;
436         this._toggleNewRecordingShortcut.disabled = !enabled;
437
438         this._recordButton.toggled = WI.timelineManager.isCapturing();
439         this._recordButton.enabled = enabled;
440
441         this._updateNavigationBarButtons();
442     }
443
444     _inspectorVisibilityChanged(event)
445     {
446         WI.timelineManager.autoCaptureOnPageLoad = !!this.visible && !!WI.visible;
447     }
448
449     _globalModifierKeysDidChange(event)
450     {
451         this._updateNavigationBarButtons();
452     }
453
454     _toggleRecordingOnSpacebar(event)
455     {
456         if (WI.timelineManager.activeRecording.readonly)
457             return;
458
459         if (WI.isEventTargetAnEditableField(event))
460             return;
461
462         this._toggleRecording();
463
464         event.preventDefault();
465     }
466
467     _toggleNewRecordingOnSpacebar(event)
468     {
469         if (WI.isEventTargetAnEditableField(event))
470             return;
471
472         this._toggleRecording(true);
473
474         event.preventDefault();
475     }
476
477     _toggleRecording(shouldCreateRecording)
478     {
479         let isCapturing = WI.timelineManager.isCapturing();
480         this._recordButton.toggled = isCapturing;
481
482         if (isCapturing)
483             WI.timelineManager.stopCapturing();
484         else {
485             WI.timelineManager.startCapturing(shouldCreateRecording);
486             // Show the timeline to which events will be appended.
487             this._recordingLoaded();
488         }
489     }
490
491     _recordButtonClicked(event)
492     {
493         let shouldCreateNewRecording = window.event ? window.event.shiftKey : false;
494         if (WI.timelineManager.activeRecording.readonly)
495             shouldCreateNewRecording = true;
496
497         this._recordButton.toggled = !WI.timelineManager.isCapturing();
498         this._toggleRecording(shouldCreateNewRecording);
499     }
500
501     _continueButtonClicked(event)
502     {
503         console.assert(WI.timelineManager.willAutoStop());
504
505         WI.timelineManager.relaxAutoStop();
506
507         this._updateNavigationBarButtons();
508     }
509
510     _recordingsTreeSelectionDidChange(event)
511     {
512         let treeElement = this._recordingsTreeOutline.selectedTreeElement;
513         if (!treeElement)
514             return;
515
516         console.assert(treeElement.representedObject instanceof WI.TimelineRecording);
517
518         this._recordingSelected(treeElement.representedObject);
519     }
520
521     _recordingCreated(event)
522     {
523         this._addRecording(event.data.recording);
524         this._recordingCountChanged();
525     }
526
527     _addRecording(recording)
528     {
529         console.assert(recording instanceof WI.TimelineRecording, recording);
530
531         let recordingTreeElement = new WI.GeneralTreeElement(WI.TimelineTabContentView.StopwatchIconStyleClass, recording.displayName, null, recording);
532         this._recordingTreeElementMap.set(recording, recordingTreeElement);
533         this._recordingsTreeOutline.appendChild(recordingTreeElement);
534     }
535
536     _recordingCountChanged()
537     {
538         let previousTreeElement = null;
539         for (let treeElement of this._recordingTreeElementMap.values()) {
540             if (previousTreeElement) {
541                 previousTreeElement.nextSibling = treeElement;
542                 treeElement.previousSibling = previousTreeElement;
543             }
544
545             previousTreeElement = treeElement;
546         }
547     }
548
549     _recordingSelected(recording)
550     {
551         console.assert(recording instanceof WI.TimelineRecording, recording);
552
553         this._displayedRecording = recording;
554
555         // Save the current state incase we need to restore it to a new recording.
556         let cookie = {};
557         this.saveToCookie(cookie);
558
559         if (this._displayedContentView)
560             this._displayedContentView.removeEventListener(WI.ContentView.Event.NavigationItemsDidChange, this._displayedContentViewNavigationItemsDidChange, this);
561
562         // Try to get the recording content view if it exists already, if it does we don't want to restore the cookie.
563         let onlyExisting = true;
564         this._displayedContentView = this.contentBrowser.contentViewForRepresentedObject(this._displayedRecording, onlyExisting);
565         if (this._displayedContentView) {
566             this._displayedContentView.addEventListener(WI.ContentView.Event.NavigationItemsDidChange, this._displayedContentViewNavigationItemsDidChange, this);
567
568             // Show the timeline that was being shown to update the sidebar tree state.
569             let currentTimelineView = this._displayedContentView.currentTimelineView;
570             let timelineType = currentTimelineView && currentTimelineView.representedObject instanceof WI.Timeline ? currentTimelineView.representedObject.type : null;
571             this._showTimelineViewForType(timelineType);
572
573             return;
574         }
575
576         onlyExisting = false;
577         this._displayedContentView = this.contentBrowser.contentViewForRepresentedObject(this._displayedRecording, onlyExisting);
578         if (this._displayedContentView)
579             this._displayedContentView.addEventListener(WI.ContentView.Event.NavigationItemsDidChange, this._displayedContentViewNavigationItemsDidChange, this);
580
581         // Restore the cookie to carry over the previous recording view state to the new recording.
582         this.restoreFromCookie(cookie);
583     }
584
585     _recordingLoaded(event)
586     {
587         this._recordingSelected(WI.timelineManager.activeRecording);
588     }
589
590     _viewModeSelected(event)
591     {
592         let selectedNavigationItem = event.target.selectedNavigationItem;
593         console.assert(selectedNavigationItem);
594         if (!selectedNavigationItem)
595             return;
596
597         const selectedByUser = true;
598         this._changeViewMode(selectedNavigationItem.identifier, selectedByUser);
599     }
600
601     _changeViewMode(mode, selectedByUser)
602     {
603         if (this._viewMode === mode)
604             return;
605
606         let navigationItemForViewMode = this.contentBrowser.navigationBar.findNavigationItem(mode);
607         console.assert(navigationItemForViewMode, "Couldn't find navigation item for this view mode.");
608         if (!navigationItemForViewMode)
609             return;
610
611         this._viewMode = mode;
612
613         this.contentBrowser.navigationBar.selectedNavigationItem = navigationItemForViewMode;
614
615         if (!selectedByUser)
616             return;
617
618         let timelineType = this._previousSelectedTimelineType;
619         if (this._viewMode === WI.TimelineOverview.ViewMode.RenderingFrames) {
620             let timeline = this._getTimelineForCurrentContentView();
621             this._previousSelectedTimelineType = timeline ? timeline.type : null;
622             timelineType = WI.TimelineRecord.Type.RenderingFrame;
623         }
624
625         this._showTimelineViewForType(timelineType);
626     }
627
628     _showTimelineViewForType(timelineType)
629     {
630         console.assert(this._displayedRecording);
631         console.assert(this._displayedContentView);
632
633         let timeline = timelineType ? this._displayedRecording.timelines.get(timelineType) : null;
634         if (timeline)
635             this._displayedContentView.showTimelineViewForTimeline(timeline);
636         else
637             this._displayedContentView.showOverviewTimelineView();
638
639         if (this.contentBrowser.currentContentView !== this._displayedContentView)
640             this.contentBrowser.showContentView(this._displayedContentView);
641     }
642
643     _displayedContentViewNavigationItemsDidChange(event)
644     {
645         let timeline = this._getTimelineForCurrentContentView();
646         let newViewMode = WI.TimelineOverview.ViewMode.Timelines;
647         if (timeline && timeline.type === WI.TimelineRecord.Type.RenderingFrame)
648             newViewMode = WI.TimelineOverview.ViewMode.RenderingFrames;
649
650         const selectedByUser = false;
651         this._changeViewMode(newViewMode, selectedByUser);
652     }
653
654     _getTimelineForCurrentContentView()
655     {
656         let currentContentView = this.contentBrowser.currentContentView;
657         if (!(currentContentView instanceof WI.TimelineRecordingContentView))
658             return null;
659
660         let timelineView = currentContentView.currentTimelineView;
661         return (timelineView && timelineView.representedObject instanceof WI.Timeline) ? timelineView.representedObject : null;
662     }
663 };
664
665 WI.TimelineTabContentView.Type = "timeline";
666
667 WI.TimelineTabContentView.ShowingTimelineRecordingContentViewCookieKey = "timeline-sidebar-panel-showing-timeline-recording-content-view";
668 WI.TimelineTabContentView.SelectedTimelineViewIdentifierCookieKey = "timeline-sidebar-panel-selected-timeline-view-identifier";
669 WI.TimelineTabContentView.OverviewTimelineIdentifierCookieValue = "overview";
670 WI.TimelineTabContentView.StopwatchIconStyleClass = "stopwatch-icon";