7b0807075f796146b9fbc1540c9e77a32e6849c3
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / TimelineSidebarPanel.js
1 /*
2  * Copyright (C) 2013 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.TimelineSidebarPanel = function()
27 {
28     WebInspector.NavigationSidebarPanel.call(this, "timeline", WebInspector.UIString("Timelines"), "Images/NavigationItemStopwatch.svg", "2");
29
30     this._timelineEventsTitleBarElement = document.createElement("div");
31     this._timelineEventsTitleBarElement.classList.add(WebInspector.TimelineSidebarPanel.TitleBarStyleClass);
32     this._timelineEventsTitleBarElement.classList.add(WebInspector.TimelineSidebarPanel.TimelineEventsTitleBarStyleClass);
33     this.element.insertBefore(this._timelineEventsTitleBarElement, this.element.firstChild);
34
35     this.contentTreeOutlineLabel = "";
36
37     this._timelinesContentContainer = document.createElement("div");
38     this._timelinesContentContainer.classList.add(WebInspector.TimelineSidebarPanel.TimelinesContentContainerStyleClass);
39     this.element.insertBefore(this._timelinesContentContainer, this.element.firstChild);
40
41     // Maintain an invisible tree outline containing tree elements for all recordings.
42     // The visible recording's tree element is selected when the content view changes.
43     this._recordingTreeElementMap = new Map;
44     this._recordingsTreeOutline = this.createContentTreeOutline(true, true);
45     this._recordingsTreeOutline.element.classList.add(WebInspector.NavigationSidebarPanel.HideDisclosureButtonsStyleClassName);
46     this._recordingsTreeOutline.element.classList.add(WebInspector.NavigationSidebarPanel.ContentTreeOutlineElementHiddenStyleClassName);
47     this._recordingsTreeOutline.onselect = this._recordingsTreeElementSelected.bind(this);
48     this._timelinesContentContainer.appendChild(this._recordingsTreeOutline.element);
49
50     this._timelinesTreeOutline = this.createContentTreeOutline(true, true);
51     this._timelinesTreeOutline.element.classList.add(WebInspector.NavigationSidebarPanel.HideDisclosureButtonsStyleClassName);
52     this._timelinesTreeOutline.onselect = this._timelinesTreeElementSelected.bind(this);
53     this._timelinesContentContainer.appendChild(this._timelinesTreeOutline.element);
54
55     function createTimelineTreeElement(label, iconClass, identifier)
56     {
57         var treeElement = new WebInspector.GeneralTreeElement([iconClass, WebInspector.TimelineSidebarPanel.LargeIconStyleClass], label, null, identifier);
58
59         const tooltip = WebInspector.UIString("Close %s timeline view").format(label);
60         wrappedSVGDocument(platformImagePath("CloseLarge.svg"), WebInspector.TimelineSidebarPanel.CloseButtonStyleClass, tooltip, function(element) {
61             var button = new WebInspector.TreeElementStatusButton(element);
62             button.addEventListener(WebInspector.TreeElementStatusButton.Event.Clicked, this.showTimelineOverview, this);
63             treeElement.status = button.element;
64         }.bind(this));
65
66         return treeElement;
67     }
68
69     // Timeline elements are reused; clicking them displays a TimelineView
70     // for the relevant timeline of the active recording.
71     this._timelineTreeElementMap = new Map;
72     this._timelineTreeElementMap.set(WebInspector.TimelineRecord.Type.Network, createTimelineTreeElement.call(this, WebInspector.UIString("Network Requests"), WebInspector.TimelineSidebarPanel.NetworkIconStyleClass, WebInspector.TimelineRecord.Type.Network));
73     this._timelineTreeElementMap.set(WebInspector.TimelineRecord.Type.Layout, createTimelineTreeElement.call(this, WebInspector.UIString("Layout & Rendering"), WebInspector.TimelineSidebarPanel.ColorsIconStyleClass, WebInspector.TimelineRecord.Type.Layout));
74     this._timelineTreeElementMap.set(WebInspector.TimelineRecord.Type.Script, createTimelineTreeElement.call(this, WebInspector.UIString("JavaScript & Events"), WebInspector.TimelineSidebarPanel.ScriptIconStyleClass, WebInspector.TimelineRecord.Type.Script));
75
76     for (var timelineTreeElement of this._timelineTreeElementMap.values())
77         this._timelinesTreeOutline.appendChild(timelineTreeElement);
78
79     var timelinesTitleBarElement = document.createElement("div");
80     timelinesTitleBarElement.textContent = WebInspector.UIString("Timelines");
81     timelinesTitleBarElement.classList.add(WebInspector.TimelineSidebarPanel.TitleBarStyleClass);
82     timelinesTitleBarElement.classList.add(WebInspector.TimelineSidebarPanel.TimelinesTitleBarStyleClass);
83     this.element.insertBefore(timelinesTitleBarElement, this.element.firstChild);
84
85     var statusBarElement = this._statusBarElement = document.createElement("div");
86     statusBarElement.classList.add(WebInspector.TimelineSidebarPanel.StatusBarStyleClass);
87     this.element.insertBefore(statusBarElement, this.element.firstChild);
88
89     this._recordGlyphElement = document.createElement("div");
90     this._recordGlyphElement.className = WebInspector.TimelineSidebarPanel.RecordGlyphStyleClass;
91     this._recordGlyphElement.addEventListener("mouseover", this._recordGlyphMousedOver.bind(this));
92     this._recordGlyphElement.addEventListener("mouseout", this._recordGlyphMousedOut.bind(this));
93     this._recordGlyphElement.addEventListener("click", this._recordGlyphClicked.bind(this));
94     statusBarElement.appendChild(this._recordGlyphElement);
95
96     this._recordStatusElement = document.createElement("div");
97     this._recordStatusElement.className = WebInspector.TimelineSidebarPanel.RecordStatusStyleClass;
98     statusBarElement.appendChild(this._recordStatusElement);
99
100     WebInspector.showReplayInterfaceSetting.addEventListener(WebInspector.Setting.Event.Changed, this._updateReplayInterfaceVisibility, this);
101
102     // We always create a navigation bar; its visibility is controlled by WebInspector.showReplayInterfaceSetting.
103     this._navigationBar = new WebInspector.NavigationBar;
104     this.element.appendChild(this._navigationBar.element);
105
106     var toolTip = WebInspector.UIString("Begin Capturing");
107     var altToolTip = WebInspector.UIString("End Capturing");
108     this._replayCaptureButtonItem = new WebInspector.ActivateButtonNavigationItem("replay-capture", toolTip, altToolTip, "Images/Circle.svg", 16, 16);
109     this._replayCaptureButtonItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._replayCaptureButtonClicked, this);
110     this._replayCaptureButtonItem.enabled = true;
111     this._navigationBar.addNavigationItem(this._replayCaptureButtonItem);
112
113     toolTip = WebInspector.UIString("Start Playback");
114     altToolTip = WebInspector.UIString("Pause Playback");
115     this._replayPauseResumeButtonItem = new WebInspector.ToggleButtonNavigationItem("replay-pause-resume", toolTip, altToolTip, "Images/Resume.svg", "Images/Pause.svg", 16, 16, true);
116     this._replayPauseResumeButtonItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._replayPauseResumeButtonClicked, this);
117     this._replayPauseResumeButtonItem.enabled = false;
118     this._navigationBar.addNavigationItem(this._replayPauseResumeButtonItem);
119
120     WebInspector.replayManager.addEventListener(WebInspector.ReplayManager.Event.CaptureStarted, this._captureStarted, this);
121     WebInspector.replayManager.addEventListener(WebInspector.ReplayManager.Event.CaptureStopped, this._captureStopped, this);
122
123     this._statusBarElement.oncontextmenu = this._contextMenuNavigationBarOrStatusBar.bind(this);
124     this._navigationBar.element.oncontextmenu = this._contextMenuNavigationBarOrStatusBar.bind(this);
125     this._updateReplayInterfaceVisibility();
126
127     WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.RecordingCreated, this._recordingCreated, this);
128     WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.RecordingLoaded, this._recordingLoaded, this);
129
130     this._stripeBackgroundElement = document.createElement("div");
131     this._stripeBackgroundElement.className = WebInspector.TimelineSidebarPanel.StripeBackgroundStyleClass;
132     this.contentElement.insertBefore(this._stripeBackgroundElement, this.contentElement.firstChild);
133
134     WebInspector.contentBrowser.addEventListener(WebInspector.ContentBrowser.Event.CurrentContentViewDidChange, this._contentBrowserCurrentContentViewDidChange, this);
135     WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.CapturingStarted, this._capturingStarted, this);
136     WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.CapturingStopped, this._capturingStopped, this);
137 };
138
139 WebInspector.TimelineSidebarPanel.HiddenStyleClassName = "hidden";
140 WebInspector.TimelineSidebarPanel.StatusBarStyleClass = "status-bar";
141 WebInspector.TimelineSidebarPanel.RecordGlyphStyleClass = "record-glyph";
142 WebInspector.TimelineSidebarPanel.RecordGlyphRecordingStyleClass = "recording";
143 WebInspector.TimelineSidebarPanel.RecordGlyphRecordingForcedStyleClass = "forced";
144 WebInspector.TimelineSidebarPanel.RecordStatusStyleClass = "record-status";
145 WebInspector.TimelineSidebarPanel.TitleBarStyleClass = "title-bar";
146 WebInspector.TimelineSidebarPanel.TimelinesTitleBarStyleClass = "timelines";
147 WebInspector.TimelineSidebarPanel.TimelineEventsTitleBarStyleClass = "timeline-events";
148 WebInspector.TimelineSidebarPanel.TimelinesContentContainerStyleClass = "timelines-content";
149 WebInspector.TimelineSidebarPanel.StripeBackgroundStyleClass = "stripe-background";
150 WebInspector.TimelineSidebarPanel.CloseButtonStyleClass = "close-button";
151 WebInspector.TimelineSidebarPanel.LargeIconStyleClass = "large";
152 WebInspector.TimelineSidebarPanel.StopwatchIconStyleClass = "stopwatch-icon";
153 WebInspector.TimelineSidebarPanel.NetworkIconStyleClass = "network-icon";
154 WebInspector.TimelineSidebarPanel.ColorsIconStyleClass = "colors-icon";
155 WebInspector.TimelineSidebarPanel.ScriptIconStyleClass = "script-icon";
156 WebInspector.TimelineSidebarPanel.TimelineContentViewShowingStyleClass = "timeline-content-view-showing";
157
158 WebInspector.TimelineSidebarPanel.ShowingTimelineContentViewCookieKey = "timeline-sidebar-panel-showing-timeline-content-view";
159 WebInspector.TimelineSidebarPanel.SelectedTimelineViewIdentifierCookieKey = "timeline-sidebar-panel-selected-timeline-view-identifier";
160 WebInspector.TimelineSidebarPanel.OverviewTimelineIdentifierCookieValue = "overview";
161
162 WebInspector.TimelineSidebarPanel.prototype = {
163     constructor: WebInspector.TimelineSidebarPanel,
164     __proto__: WebInspector.NavigationSidebarPanel.prototype,
165
166     // Public
167
168     shown: function()
169     {
170         WebInspector.NavigationSidebarPanel.prototype.shown.call(this);
171
172         if (this._activeContentView)
173             WebInspector.contentBrowser.showContentView(this._activeContentView);
174     },
175
176     showDefaultContentView: function()
177     {
178         if (this._activeContentView)
179             this.showTimelineOverview();
180     },
181
182     get hasSelectedElement()
183     {
184         return !!this._contentTreeOutline.selectedTreeElement || !!this._recordingsTreeOutline.selectedTreeElement;
185     },
186
187     treeElementForRepresentedObject: function(representedObject)
188     {
189         if (representedObject instanceof WebInspector.TimelineRecording)
190             return this._recordingTreeElementMap.get(representedObject);
191
192         // The main resource is used as the representedObject instead of Frame in our tree.
193         if (representedObject instanceof WebInspector.Frame)
194             representedObject = representedObject.mainResource;
195
196         var foundTreeElement = this.contentTreeOutline.getCachedTreeElement(representedObject);
197         if (foundTreeElement)
198             return foundTreeElement;
199
200         // Look for TreeElements loosely based on represented objects that can contain the represented
201         // object we are really looking for. This allows a SourceCodeTimelineTreeElement or a
202         // TimelineRecordTreeElement to stay selected when the Resource it represents is showing.
203
204         function looselyCompareRepresentedObjects(candidateTreeElement)
205         {
206             if (!candidateTreeElement)
207                 return false;
208
209             var candidateRepresentedObject = candidateTreeElement.representedObject;
210             if (candidateRepresentedObject instanceof WebInspector.SourceCodeTimeline) {
211                 if (candidateRepresentedObject.sourceCode === representedObject)
212                     return true;
213                 return false;
214             }
215
216             if (candidateRepresentedObject instanceof WebInspector.TimelineRecord) {
217                 if (!candidateRepresentedObject.sourceCodeLocation)
218                     return false;
219                 if (candidateRepresentedObject.sourceCodeLocation.sourceCode === representedObject)
220                     return true;
221                 return false;
222             }
223
224             if (candidateRepresentedObject instanceof WebInspector.ProfileNode)
225                 return false;
226
227             console.error("Unknown TreeElement", candidateTreeElement);
228             return false;
229         }
230
231         // Check the selected tree element first so we don't need to do a longer search and it is
232         // likely to be the best candidate for the current view.
233         if (looselyCompareRepresentedObjects(this.contentTreeOutline.selectedTreeElement))
234             return this.contentTreeOutline.selectedTreeElement;
235
236         var currentTreeElement = this._contentTreeOutline.children[0];
237         while (currentTreeElement && !currentTreeElement.root) {
238             if (looselyCompareRepresentedObjects(currentTreeElement))
239                 return currentTreeElement;
240             currentTreeElement = currentTreeElement.traverseNextTreeElement(false, null, false);
241         }
242
243         return null;
244     },
245
246     get contentTreeOutlineLabel()
247     {
248         return this._timelineEventsTitleBarElement.textContent;
249     },
250
251     set contentTreeOutlineLabel(label)
252     {
253         label = label || WebInspector.UIString("Timeline Events");
254
255         this._timelineEventsTitleBarElement.textContent = label;
256         this.filterBar.placeholder = WebInspector.UIString("Filter %s").format(label);
257     },
258
259     showTimelineOverview: function()
260     {
261         if (this._timelinesTreeOutline.selectedTreeElement)
262             this._timelinesTreeOutline.selectedTreeElement.deselect();
263
264         this._activeContentView.showOverviewTimelineView();
265         WebInspector.contentBrowser.showContentView(this._activeContentView);
266     },
267
268     showTimelineViewForType: function(timelineType)
269     {
270         console.assert(this._timelineTreeElementMap.has(timelineType), timelineType);
271         if (!this._timelineTreeElementMap.has(timelineType))
272             return;
273
274         // Defer showing the relevant timeline to the onselect handler of the timelines tree element.
275         const wasSelectedByUser = true;
276         const shouldSuppressOnSelect = false;
277         this._timelineTreeElementMap.get(timelineType).select(true, wasSelectedByUser, shouldSuppressOnSelect, true);
278     },
279
280     // Protected
281
282     updateCustomContentOverflow: function()
283     {
284         if (!this._stripeBackgroundElement)
285             return;
286
287         var contentHeight = this.contentTreeOutline.element.offsetHeight;
288         var currentHeight = parseInt(this._stripeBackgroundElement.style.height);
289         if (currentHeight !== contentHeight)
290             this._stripeBackgroundElement.style.height = contentHeight + "px";
291     },
292
293     hasCustomFilters: function()
294     {
295         return true;
296     },
297
298     matchTreeElementAgainstCustomFilters: function(treeElement)
299     {
300         if (!this._activeContentView)
301             return true;
302
303         return this._activeContentView.matchTreeElementAgainstCustomFilters(treeElement);
304     },
305
306     canShowDifferentContentView: function()
307     {
308         return !this.restoringState || !this._restoredShowingTimelineContentView;
309     },
310
311     saveStateToCookie: function(cookie)
312     {
313         console.assert(cookie);
314
315         cookie[WebInspector.TimelineSidebarPanel.ShowingTimelineContentViewCookieKey] = WebInspector.contentBrowser.currentContentView instanceof WebInspector.TimelineContentView;
316
317         var selectedTreeElement = this._timelinesTreeOutline.selectedTreeElement;
318         if (selectedTreeElement)
319             cookie[WebInspector.TimelineSidebarPanel.SelectedTimelineViewIdentifierCookieKey] = selectedTreeElement.representedObject.type;
320         else
321             cookie[WebInspector.TimelineSidebarPanel.SelectedTimelineViewIdentifierCookieKey] = WebInspector.TimelineSidebarPanel.OverviewTimelineIdentifierCookieValue;
322
323         WebInspector.NavigationSidebarPanel.prototype.saveStateToCookie.call(this, cookie);
324     },
325
326     restoreStateFromCookie: function(cookie, relaxedMatchDelay)
327     {
328         console.assert(cookie);
329
330         // The _activeContentView is not ready on initial load, so delay the restore.
331         // This matches the delayed work in the WebInspector.TimelineSidebarPanel constructor.
332         if (!this._activeContentView) {
333             setTimeout(this.restoreStateFromCookie.bind(this, cookie, relaxedMatchDelay), 0);
334             return;
335         }
336
337         this._restoredShowingTimelineContentView = cookie[WebInspector.TimelineSidebarPanel.ShowingTimelineContentViewCookieKey];
338
339         var selectedTimelineViewIdentifier = cookie[WebInspector.TimelineSidebarPanel.SelectedTimelineViewIdentifierCookieKey];
340         if (!selectedTimelineViewIdentifier || selectedTimelineViewIdentifier === WebInspector.TimelineSidebarPanel.OverviewTimelineIdentifierCookieValue)
341             this.showTimelineOverview();
342         else
343             this.showTimelineViewForType(selectedTimelineViewIdentifier);
344
345         // Don't call NavigationSidebarPanel.restoreStateFromCookie, because it tries to match based
346         // on type only as a last resort. This would cause the first recording to be reselected on reload.
347     },
348
349     // Private
350
351     _recordingsTreeElementSelected: function(treeElement, selectedByUser)
352     {
353         console.assert(treeElement.representedObject instanceof WebInspector.TimelineRecording);
354         console.assert(!selectedByUser, "Recording tree elements should be hidden and only programmatically selectable.")
355
356         this._activeContentView = WebInspector.contentBrowser.contentViewForRepresentedObject(treeElement.representedObject);
357
358         // Deselect or re-select the timeline tree element for the timeline view being displayed.
359         var currentTimelineView = this._activeContentView.currentTimelineView;
360         if (currentTimelineView && currentTimelineView.representedObject instanceof WebInspector.Timeline) {
361             const wasSelectedByUser = false; // This is a simulated selection.
362             const shouldSuppressOnSelect = false;
363             this._timelineTreeElementMap.get(currentTimelineView.representedObject.type).select(true, wasSelectedByUser, shouldSuppressOnSelect, true);
364         } else if (this._timelinesTreeOutline.selectedTreeElement)
365             this._timelinesTreeOutline.selectedTreeElement.deselect();
366
367         this.updateFilter();
368     },
369
370     _timelinesTreeElementSelected: function(treeElement, selectedByUser)
371     {
372         console.assert(this._timelineTreeElementMap.get(treeElement.representedObject) === treeElement, treeElement);
373
374         // If not selected by user, then this selection merely synced the tree element with the content view's contents.
375         if (!selectedByUser) {
376             console.assert(this._activeContentView.currentTimelineView.representedObject.type === treeElement.representedObject);
377             return;
378         }
379
380         var timelineType = treeElement.representedObject;
381         var timeline = this._activeContentView.representedObject.timelines.get(timelineType);
382         this._activeContentView.showTimelineViewForTimeline(timeline);
383         WebInspector.contentBrowser.showContentView(this._activeContentView);
384     },
385
386     _contentBrowserCurrentContentViewDidChange: function(event)
387     {
388         var didShowTimelineContentView = WebInspector.contentBrowser.currentContentView instanceof WebInspector.TimelineContentView;
389         this.element.classList.toggle(WebInspector.TimelineSidebarPanel.TimelineContentViewShowingStyleClass, didShowTimelineContentView);
390     },
391
392     _capturingStarted: function(event)
393     {
394         this._recordStatusElement.textContent = WebInspector.UIString("Recording");
395         this._recordGlyphElement.classList.add(WebInspector.TimelineSidebarPanel.RecordGlyphRecordingStyleClass);
396     },
397
398     _capturingStopped: function(event)
399     {
400         this._recordStatusElement.textContent = "";
401         this._recordGlyphElement.classList.remove(WebInspector.TimelineSidebarPanel.RecordGlyphRecordingStyleClass);
402     },
403
404     _recordingCreated: function(event)
405     {
406         var recording = event.data.recording;
407         console.assert(recording instanceof WebInspector.TimelineRecording, recording);
408
409         var recordingTreeElement = new WebInspector.GeneralTreeElement(WebInspector.TimelineSidebarPanel.StopwatchIconStyleClass, recording.displayName, null, recording);
410         this._recordingTreeElementMap.set(recording, recordingTreeElement);
411         this._recordingsTreeOutline.appendChild(recordingTreeElement);
412
413         var previousTreeElement = null;
414         for (var treeElement of this._recordingTreeElementMap.values()) {
415             if (previousTreeElement) {
416                 previousTreeElement.nextSibling = treeElement;
417                 treeElement.previousSibling = previousTreeElement;
418             }
419
420             previousTreeElement = treeElement;
421         }
422     },
423
424     _recordingLoaded: function()
425     {
426         this._activeContentView = WebInspector.contentBrowser.contentViewForRepresentedObject(WebInspector.timelineManager.activeRecording);
427         WebInspector.contentBrowser.showContentView(this._activeContentView);
428     },
429
430     _recordGlyphMousedOver: function(event)
431     {
432         this._recordGlyphElement.classList.remove(WebInspector.TimelineSidebarPanel.RecordGlyphRecordingForcedStyleClass);
433
434         if (WebInspector.timelineManager.isCapturing())
435             this._recordStatusElement.textContent = WebInspector.UIString("Stop Recording");
436         else
437             this._recordStatusElement.textContent = WebInspector.UIString("Start Recording");
438     },
439
440     _recordGlyphMousedOut: function(event)
441     {
442         this._recordGlyphElement.classList.remove(WebInspector.TimelineSidebarPanel.RecordGlyphRecordingForcedStyleClass);
443
444         if (WebInspector.timelineManager.isCapturing())
445             this._recordStatusElement.textContent = WebInspector.UIString("Recording");
446         else
447             this._recordStatusElement.textContent = "";
448     },
449
450     _recordGlyphClicked: function(event)
451     {
452         // Add forced class to prevent the glyph from showing a confusing status after click.
453         this._recordGlyphElement.classList.add(WebInspector.TimelineSidebarPanel.RecordGlyphRecordingForcedStyleClass);
454
455         var shouldCreateRecording = event.shiftKey;
456
457         if (WebInspector.timelineManager.isCapturing())
458             WebInspector.timelineManager.stopCapturing();
459         else {
460             WebInspector.timelineManager.startCapturing(shouldCreateRecording);
461             // Show the timeline to which events will be appended.
462             this._recordingLoaded();
463         }
464     },
465
466     // These methods are only used when ReplayAgent is available.
467
468     _updateReplayInterfaceVisibility: function()
469     {
470         var shouldShowReplayInterface = window.ReplayAgent && WebInspector.showReplayInterfaceSetting.value;
471
472         this._statusBarElement.classList.toggle(WebInspector.TimelineSidebarPanel.HiddenStyleClassName, shouldShowReplayInterface);
473         this._navigationBar.element.classList.toggle(WebInspector.TimelineSidebarPanel.HiddenStyleClassName, !shouldShowReplayInterface);
474     },
475
476     _contextMenuNavigationBarOrStatusBar: function()
477     {
478         if (!window.ReplayAgent)
479             return;
480
481         function toggleReplayInterface() {
482             WebInspector.showReplayInterfaceSetting.value = !WebInspector.showReplayInterfaceSetting.value;
483         }
484
485         var contextMenu = new WebInspector.ContextMenu(event);
486         if (WebInspector.showReplayInterfaceSetting.value)
487             contextMenu.appendItem(WebInspector.UIString("Hide Replay Controls"), toggleReplayInterface);
488         else
489             contextMenu.appendItem(WebInspector.UIString("Show Replay Controls"), toggleReplayInterface);
490         contextMenu.show();
491     },
492
493     _replayCaptureButtonClicked: function()
494     {
495         if (!this._replayCaptureButtonItem.activated) {
496             WebInspector.replayManager.startCapturing();
497             WebInspector.timelineManager.startCapturing();
498
499             // De-bounce further presses until the backend has begun capturing.
500             this._replayCaptureButtonItem.activated = true;
501             this._replayCaptureButtonItem.enabled = false;
502         } else {
503             WebInspector.replayManager.stopCapturing();
504             WebInspector.timelineManager.stopCapturing();
505
506             this._replayCaptureButtonItem.enabled = false;
507         }
508     },
509
510     _replayPauseResumeButtonClicked: function()
511     {
512         if (this._replayPauseResumeButtonItem.toggled)
513             WebInspector.replayManager.pausePlayback();
514         else
515             WebInspector.replayManager.replayToCompletion();
516     },
517
518     _captureStarted: function()
519     {
520         this._replayCaptureButtonItem.enabled = true;
521     },
522
523     _captureStopped: function()
524     {
525         this._replayCaptureButtonItem.activated = false;
526         this._replayPauseResumeButtonItem.enabled = true;
527     },
528
529     _playbackStarted: function()
530     {
531         this._replayPauseResumeButtonItem.toggled = true;
532     },
533
534     _playbackPaused: function()
535     {
536         this._replayPauseResumeButtonItem.toggled = false;
537     }
538 };