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