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