RegExpCache::finalize should not delete code
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / TimelineSidebarPanel.js
1 /*
2  * Copyright (C) 2013, 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.TimelineSidebarPanel = class TimelineSidebarPanel extends WebInspector.NavigationSidebarPanel
27 {
28     constructor(contentBrowser)
29     {
30         super("timeline", WebInspector.UIString("Timelines"));
31
32         this.contentBrowser = contentBrowser;
33
34         var timelineEventsTitleBarContainer = document.createElement("div");
35         timelineEventsTitleBarContainer.classList.add(WebInspector.TimelineSidebarPanel.TitleBarStyleClass);
36         timelineEventsTitleBarContainer.classList.add(WebInspector.TimelineSidebarPanel.TimelineEventsTitleBarStyleClass);
37
38         this._timelineEventsTitleBarElement = document.createElement("div");
39         this._timelineEventsTitleBarElement.classList.add(WebInspector.TimelineSidebarPanel.TitleBarTextStyleClass);
40         timelineEventsTitleBarContainer.appendChild(this._timelineEventsTitleBarElement);
41
42         this._timelineEventsTitleBarScopeContainer = document.createElement("div");
43         this._timelineEventsTitleBarScopeContainer.classList.add(WebInspector.TimelineSidebarPanel.TitleBarScopeBarStyleClass);
44         timelineEventsTitleBarContainer.appendChild(this._timelineEventsTitleBarScopeContainer);
45
46         this.element.insertBefore(timelineEventsTitleBarContainer, this.element.firstChild);
47
48         this.contentTreeOutlineLabel = "";
49
50         this._timelinesContentContainerElement = document.createElement("div");
51         this._timelinesContentContainerElement.classList.add(WebInspector.TimelineSidebarPanel.TimelinesContentContainerStyleClass);
52         this.element.insertBefore(this._timelinesContentContainerElement, this.element.firstChild);
53
54         this._displayedRecording = null;
55         this._displayedContentView = null;
56         this._viewMode = null;
57         this._previousSelectedTimelineType = null;
58
59         // Maintain an invisible tree outline containing tree elements for all recordings.
60         // The visible recording's tree element is selected when the content view changes.
61         this._recordingTreeElementMap = new Map;
62         this._recordingsTreeOutline = this.createContentTreeOutline(true, true);
63         this._recordingsTreeOutline.element.classList.add(WebInspector.NavigationSidebarPanel.HideDisclosureButtonsStyleClassName);
64         this._recordingsTreeOutline.element.classList.add(WebInspector.NavigationSidebarPanel.ContentTreeOutlineElementHiddenStyleClassName);
65         this._recordingsTreeOutline.onselect = this._recordingsTreeElementSelected.bind(this);
66         this._timelinesContentContainerElement.appendChild(this._recordingsTreeOutline.element);
67
68         // Maintain a tree outline with tree elements for each timeline of the selected recording.
69         this._timelinesTreeOutline = this.createContentTreeOutline(true, true);
70         this._timelinesTreeOutline.element.classList.add(WebInspector.NavigationSidebarPanel.HideDisclosureButtonsStyleClassName);
71         this._timelinesTreeOutline.onselect = this._timelinesTreeElementSelected.bind(this);
72         this._timelinesContentContainerElement.appendChild(this._timelinesTreeOutline.element);
73
74         this._timelineTreeElementMap = new Map;
75
76         // COMPATIBILITY (iOS 8): TimelineAgent.EventType.RenderingFrame did not exist.
77         this._renderingFramesSupported = window.TimelineAgent && TimelineAgent.EventType.RenderingFrame;
78
79         if (this._renderingFramesSupported) {
80             var timelinesNavigationItem = new WebInspector.RadioButtonNavigationItem(WebInspector.TimelineSidebarPanel.ViewMode.Timelines, WebInspector.UIString("Timelines"))
81             var renderingFramesNavigationItem = new WebInspector.RadioButtonNavigationItem(WebInspector.TimelineSidebarPanel.ViewMode.RenderingFrames, WebInspector.UIString("Rendering Frames"))
82             this._viewModeNavigationBar = new WebInspector.NavigationBar(null, [timelinesNavigationItem, renderingFramesNavigationItem], "tablist");
83             this._viewModeNavigationBar.addEventListener(WebInspector.NavigationBar.Event.NavigationItemSelected, this._viewModeSelected, this);
84
85             var container = document.createElement("div");
86             container.className = "navigation-bar-container";
87             container.appendChild(this._viewModeNavigationBar.element);
88             this.element.insertBefore(container, this.element.firstChild);
89
90             this._chartColors = new Map;
91             this._chartColors.set(WebInspector.RenderingFrameTimelineRecord.TaskType.Script, "rgb(153, 113, 185)");
92             this._chartColors.set(WebInspector.RenderingFrameTimelineRecord.TaskType.Layout, "rgb(212, 108, 108)");
93             this._chartColors.set(WebInspector.RenderingFrameTimelineRecord.TaskType.Paint, "rgb(152, 188, 77)");
94             this._chartColors.set(WebInspector.RenderingFrameTimelineRecord.TaskType.Other, "rgb(221, 221, 221)");
95
96             this._frameSelectionChartRow = new WebInspector.ChartDetailsSectionRow(this);
97             this._frameSelectionChartRow.innerRadius = 0.5;
98             this._frameSelectionChartRow.addEventListener(WebInspector.ChartDetailsSectionRow.Event.LegendItemChecked, this._frameSelectionLegendItemChecked, this);
99
100             this._renderingFrameTaskFilter = new Set;
101
102             var chartGroup = new WebInspector.DetailsSectionGroup([this._frameSelectionChartRow]);
103             this._frameSelectionChartSection = new WebInspector.DetailsSection("frames-selection-chart", WebInspector.UIString("Selected Frames"), [chartGroup], null, true);
104             this._timelinesContentContainerElement.appendChild(this._frameSelectionChartSection.element);
105         } else {
106             var timelinesTitleBarElement = document.createElement("div");
107             timelinesTitleBarElement.textContent = WebInspector.UIString("Timelines");
108             timelinesTitleBarElement.classList.add(WebInspector.TimelineSidebarPanel.TitleBarStyleClass);
109             timelinesTitleBarElement.classList.add(WebInspector.TimelineSidebarPanel.TimelinesTitleBarStyleClass);
110             this.element.insertBefore(timelinesTitleBarElement, this.element.firstChild);
111         }
112
113         var statusBarElement = this._statusBarElement = document.createElement("div");
114         statusBarElement.classList.add(WebInspector.TimelineSidebarPanel.StatusBarStyleClass);
115         this.element.insertBefore(statusBarElement, this.element.firstChild);
116
117         this._recordGlyphElement = document.createElement("div");
118         this._recordGlyphElement.className = WebInspector.TimelineSidebarPanel.RecordGlyphStyleClass;
119         this._recordGlyphElement.title = WebInspector.UIString("Click or press the spacebar to record.")
120         this._recordGlyphElement.addEventListener("mouseover", this._recordGlyphMousedOver.bind(this));
121         this._recordGlyphElement.addEventListener("mouseout", this._recordGlyphMousedOut.bind(this));
122         this._recordGlyphElement.addEventListener("click", this._recordGlyphClicked.bind(this));
123         statusBarElement.appendChild(this._recordGlyphElement);
124
125         this._recordStatusElement = document.createElement("div");
126         this._recordStatusElement.className = WebInspector.TimelineSidebarPanel.RecordStatusStyleClass;
127         statusBarElement.appendChild(this._recordStatusElement);
128
129         WebInspector.showReplayInterfaceSetting.addEventListener(WebInspector.Setting.Event.Changed, this._updateReplayInterfaceVisibility, this);
130
131         // We always create a replay navigation bar; its visibility is controlled by WebInspector.showReplayInterfaceSetting.
132         this._replayNavigationBar = new WebInspector.NavigationBar;
133         this.element.appendChild(this._replayNavigationBar.element);
134
135         var toolTip = WebInspector.UIString("Begin Capturing");
136         var altToolTip = WebInspector.UIString("End Capturing");
137         this._replayCaptureButtonItem = new WebInspector.ActivateButtonNavigationItem("replay-capture", toolTip, altToolTip, "Images/Circle.svg", 16, 16);
138         this._replayCaptureButtonItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._replayCaptureButtonClicked, this);
139         this._replayCaptureButtonItem.enabled = true;
140         this._replayNavigationBar.addNavigationItem(this._replayCaptureButtonItem);
141
142         toolTip = WebInspector.UIString("Start Playback");
143         altToolTip = WebInspector.UIString("Pause Playback");
144         this._replayPauseResumeButtonItem = new WebInspector.ToggleButtonNavigationItem("replay-pause-resume", toolTip, altToolTip, "Images/Resume.svg", "Images/Pause.svg", 15, 15, true);
145         this._replayPauseResumeButtonItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._replayPauseResumeButtonClicked, this);
146         this._replayPauseResumeButtonItem.enabled = false;
147         this._replayNavigationBar.addNavigationItem(this._replayPauseResumeButtonItem);
148
149         WebInspector.replayManager.addEventListener(WebInspector.ReplayManager.Event.CaptureStarted, this._captureStarted, this);
150         WebInspector.replayManager.addEventListener(WebInspector.ReplayManager.Event.CaptureStopped, this._captureStopped, this);
151
152         this._statusBarElement.oncontextmenu = this._contextMenuNavigationBarOrStatusBar.bind(this);
153         this._replayNavigationBar.element.oncontextmenu = this._contextMenuNavigationBarOrStatusBar.bind(this);
154         this._updateReplayInterfaceVisibility();
155
156         WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.RecordingCreated, this._recordingCreated, this);
157         WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.RecordingLoaded, this._recordingLoaded, this);
158
159         this.contentBrowser.addEventListener(WebInspector.ContentBrowser.Event.CurrentContentViewDidChange, this._contentBrowserCurrentContentViewDidChange, this);
160         WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.CapturingStarted, this._capturingStarted, this);
161         WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.CapturingStopped, this._capturingStopped, this);
162
163         for (var recording of WebInspector.timelineManager.recordings)
164             this._addRecording(recording);
165
166         this._recordingCountChanged();
167
168         if (WebInspector.timelineManager.activeRecording)
169             this._recordingLoaded();
170
171         this._toggleRecordingShortcut = new WebInspector.KeyboardShortcut(null, WebInspector.KeyboardShortcut.Key.Space, this._toggleRecordingOnSpacebar.bind(this));
172         this._toggleRecordingShortcut.implicitlyPreventsDefault = false;
173
174         this._toggleNewRecordingShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.Shift, WebInspector.KeyboardShortcut.Key.Space, this._toggleNewRecordingOnSpacebar.bind(this));
175         this._toggleNewRecordingShortcut.implicitlyPreventsDefault = false;
176     }
177
178     // Public
179
180     shown()
181     {
182         super.shown();
183
184         if (this._displayedContentView)
185             this.contentBrowser.showContentView(this._displayedContentView);
186
187         if (this.viewMode === WebInspector.TimelineSidebarPanel.ViewMode.RenderingFrames)
188             this._refreshFrameSelectionChart();
189
190         this._toggleRecordingShortcut.disabled = false;
191         this._toggleNewRecordingShortcut.disabled = false;
192     }
193
194     hidden()
195     {
196         super.hidden();
197
198         this._toggleRecordingShortcut.disabled = true;
199         this._toggleNewRecordingShortcut.disabled = true;
200     }
201
202     closed()
203     {
204         super.closed();
205
206         WebInspector.showReplayInterfaceSetting.removeEventListener(null, null, this);
207         WebInspector.replayManager.removeEventListener(null, null, this);
208         WebInspector.timelineManager.removeEventListener(null, null, this);
209
210         WebInspector.timelineManager.reset();
211     }
212
213     get viewMode()
214     {
215         return this._viewMode;
216     }
217
218     showDefaultContentView()
219     {
220         if (this._displayedContentView)
221             this.showTimelineOverview();
222     }
223
224     get hasSelectedElement()
225     {
226         return !!this._contentTreeOutline.selectedTreeElement || !!this._recordingsTreeOutline.selectedTreeElement;
227     }
228
229     treeElementForRepresentedObject(representedObject)
230     {
231         if (representedObject instanceof WebInspector.TimelineRecording)
232             return this._recordingTreeElementMap.get(representedObject);
233
234         // This fails if the timeline does not belong to the selected recording.
235         if (representedObject instanceof WebInspector.Timeline) {
236             var foundTreeElement = this._timelineTreeElementMap.get(representedObject);
237             if (foundTreeElement)
238                 return foundTreeElement;
239         }
240
241         // The main resource is used as the representedObject instead of Frame in our tree.
242         if (representedObject instanceof WebInspector.Frame)
243             representedObject = representedObject.mainResource;
244
245         var foundTreeElement = this.contentTreeOutline.getCachedTreeElement(representedObject);
246         if (foundTreeElement)
247             return foundTreeElement;
248
249         // Look for TreeElements loosely based on represented objects that can contain the represented
250         // object we are really looking for. This allows a SourceCodeTimelineTreeElement or a
251         // TimelineRecordTreeElement to stay selected when the Resource it represents is showing.
252
253         function looselyCompareRepresentedObjects(candidateTreeElement)
254         {
255             if (!candidateTreeElement)
256                 return false;
257
258             var candidateRepresentedObject = candidateTreeElement.representedObject;
259             if (candidateRepresentedObject instanceof WebInspector.SourceCodeTimeline) {
260                 if (candidateRepresentedObject.sourceCode === representedObject)
261                     return true;
262                 return false;
263             } else if (candidateRepresentedObject instanceof WebInspector.Timeline && representedObject instanceof WebInspector.Timeline) {
264                 // Reopen to the same timeline, even if a different parent recording is currently shown.
265                 if (candidateRepresentedObject.type === representedObject.type)
266                     return true;
267                 return false;
268             }
269
270             if (candidateRepresentedObject instanceof WebInspector.TimelineRecord) {
271                 if (!candidateRepresentedObject.sourceCodeLocation)
272                     return false;
273                 if (candidateRepresentedObject.sourceCodeLocation.sourceCode === representedObject)
274                     return true;
275                 return false;
276             }
277
278             if (candidateRepresentedObject instanceof WebInspector.ProfileNode)
279                 return false;
280
281             console.error("Unknown TreeElement", candidateTreeElement);
282             return false;
283         }
284
285         // Check the selected tree element first so we don't need to do a longer search and it is
286         // likely to be the best candidate for the current view.
287         if (looselyCompareRepresentedObjects(this.contentTreeOutline.selectedTreeElement))
288             return this.contentTreeOutline.selectedTreeElement;
289
290         var currentTreeElement = this._contentTreeOutline.children[0];
291         while (currentTreeElement && !currentTreeElement.root) {
292             if (looselyCompareRepresentedObjects(currentTreeElement))
293                 return currentTreeElement;
294             currentTreeElement = currentTreeElement.traverseNextTreeElement(false, null, false);
295         }
296
297         return null;
298     }
299
300     get contentTreeOutlineLabel()
301     {
302         return this._timelineEventsTitleBarElement.textContent;
303     }
304
305     set contentTreeOutlineLabel(label)
306     {
307         label = label || WebInspector.UIString("Timeline Events");
308
309         this._timelineEventsTitleBarElement.textContent = label;
310         this.filterBar.placeholder = WebInspector.UIString("Filter %s").format(label);
311     }
312
313     get contentTreeOutlineScopeBar()
314     {
315         return this._timelineEventsTitleBarScopeContainer.children;
316     }
317
318     set contentTreeOutlineScopeBar(scopeBar)
319     {
320         this._timelineEventsTitleBarScopeContainer.removeChildren();
321
322         if (!scopeBar || !scopeBar.element)
323             return;
324
325         this._timelineEventsTitleBarScopeContainer.appendChild(scopeBar.element);
326     }
327
328     showTimelineOverview()
329     {
330         if (this._timelinesTreeOutline.selectedTreeElement)
331             this._timelinesTreeOutline.selectedTreeElement.deselect();
332
333         this._displayedContentView.showOverviewTimelineView();
334         this.contentBrowser.showContentView(this._displayedContentView);
335
336         var selectedByUser = false;
337         this._changeViewMode(WebInspector.TimelineSidebarPanel.ViewMode.Timelines, selectedByUser);
338     }
339
340     showTimelineViewForTimeline(timeline)
341     {
342         console.assert(timeline instanceof WebInspector.Timeline, timeline);
343         console.assert(this._displayedRecording.timelines.has(timeline.type), "Cannot show timeline because it does not belong to the shown recording.", timeline.type);
344
345         // The sidebar view mode must be in the correct state before changing the content view.
346         var selectedByUser = false;
347         this._changeViewMode(this._viewModeForTimeline(timeline), selectedByUser);
348
349         if (this._timelineTreeElementMap.has(timeline)) {
350             // Defer showing the relevant timeline to the onselect handler of the timelines tree element.
351             var wasSelectedByUser = true;
352             var shouldSuppressOnSelect = false;
353             this._timelineTreeElementMap.get(timeline).select(true, wasSelectedByUser, shouldSuppressOnSelect, true);
354         } else {
355             this._displayedContentView.showTimelineViewForTimeline(timeline);
356             this.contentBrowser.showContentView(this._displayedContentView);
357         }
358     }
359
360     updateFrameSelection(startFrameIndex, endFrameIndex)
361     {
362         console.assert(startFrameIndex <= endFrameIndex);
363         console.assert(this.viewMode === WebInspector.TimelineSidebarPanel.ViewMode.RenderingFrames, this._viewMode);
364         if (this._startFrameIndex === startFrameIndex && this._endFrameIndex === endFrameIndex)
365             return;
366
367         this._startFrameIndex = startFrameIndex;
368         this._endFrameIndex = endFrameIndex;
369
370         this._refreshFrameSelectionChart();
371     }
372
373     formatChartValue(value)
374     {
375         return this._frameSelectionChartRow.total === 0 ? "" : Number.secondsToString(value);
376     }
377
378     // Protected
379
380     updateFilter()
381     {
382         super.updateFilter();
383
384         this._displayedContentView.filterDidChange();
385     }
386
387     hasCustomFilters()
388     {
389         return true;
390     }
391
392     matchTreeElementAgainstCustomFilters(treeElement)
393     {
394         if (!this._displayedContentView)
395             return true;
396
397         if (this._viewMode === WebInspector.TimelineSidebarPanel.ViewMode.RenderingFrames && this._renderingFrameTaskFilter.size) {
398             while (treeElement && !(treeElement.record instanceof WebInspector.RenderingFrameTimelineRecord))
399                 treeElement = treeElement.parent;
400
401             console.assert(treeElement, "Cannot apply task filter: no RenderingFrameTimelineRecord found.");
402             if (!treeElement)
403                 return false;
404
405             var visible = false;
406             for (var key in WebInspector.RenderingFrameTimelineRecord.TaskType) {
407                 var taskType = WebInspector.RenderingFrameTimelineRecord.TaskType[key];
408                 if (taskType === WebInspector.RenderingFrameTimelineRecord.TaskType.Other)
409                     continue;
410
411                 if (!this._renderingFrameTaskFilter.has(taskType) && treeElement.record.durationForTask(taskType) > 0) {
412                     visible = true;
413                     break;
414                 }
415             }
416
417             if (!visible)
418                 return false;
419         }
420
421         return this._displayedContentView.matchTreeElementAgainstCustomFilters(treeElement);
422     }
423
424     treeElementAddedOrChanged(treeElement)
425     {
426         if (treeElement.status)
427             return;
428
429         if (!treeElement.treeOutline || typeof treeElement.treeOutline.__canShowContentViewForTreeElement !== "function")
430             return;
431
432         if (!treeElement.treeOutline.__canShowContentViewForTreeElement(treeElement))
433             return;
434
435         wrappedSVGDocument("Images/Close.svg", null, WebInspector.UIString("Close resource view"), function(element) {
436             var fragment = document.createDocumentFragment();
437
438             var closeButton = new WebInspector.TreeElementStatusButton(element);
439             closeButton.element.classList.add("close");
440             closeButton.addEventListener(WebInspector.TreeElementStatusButton.Event.Clicked, this._treeElementCloseButtonClicked, this);
441             fragment.appendChild(closeButton.element);
442
443             var goToButton = new WebInspector.TreeElementStatusButton(WebInspector.createGoToArrowButton());
444             goToButton.__treeElement = treeElement;
445             goToButton.addEventListener(WebInspector.TreeElementStatusButton.Event.Clicked, this._treeElementGoToArrowWasClicked, this);
446             fragment.appendChild(goToButton.element);
447
448             treeElement.status = fragment;
449         }.bind(this));
450     }
451
452     canShowDifferentContentView()
453     {
454         if (this._clickedTreeElementGoToArrow)
455             return true;
456
457         if (this.contentBrowser.currentContentView instanceof WebInspector.TimelineRecordingContentView)
458             return false;
459
460         return !this.restoringState || !this._restoredShowingTimelineRecordingContentView;
461     }
462
463     saveStateToCookie(cookie)
464     {
465         console.assert(cookie);
466
467         cookie[WebInspector.TimelineSidebarPanel.ShowingTimelineRecordingContentViewCookieKey] = this.contentBrowser.currentContentView instanceof WebInspector.TimelineRecordingContentView;
468
469         if (this._viewMode === WebInspector.TimelineSidebarPanel.ViewMode.RenderingFrames)
470             cookie[WebInspector.TimelineSidebarPanel.SelectedTimelineViewIdentifierCookieKey] = WebInspector.TimelineRecord.Type.RenderingFrame;
471         else {
472             var selectedTreeElement = this._timelinesTreeOutline.selectedTreeElement;
473             if (selectedTreeElement)
474                 cookie[WebInspector.TimelineSidebarPanel.SelectedTimelineViewIdentifierCookieKey] = selectedTreeElement.representedObject.type;
475             else
476                 cookie[WebInspector.TimelineSidebarPanel.SelectedTimelineViewIdentifierCookieKey] = WebInspector.TimelineSidebarPanel.OverviewTimelineIdentifierCookieValue;    
477         }
478
479         super.saveStateToCookie(cookie);
480     }
481
482     restoreStateFromCookie(cookie, relaxedMatchDelay)
483     {
484         console.assert(cookie);
485
486         // The _displayedContentView is not ready on initial load, so delay the restore.
487         // This matches the delayed work in the WebInspector.TimelineSidebarPanel constructor.
488         if (!this._displayedContentView) {
489             setTimeout(this.restoreStateFromCookie.bind(this, cookie, relaxedMatchDelay), 0);
490             return;
491         }
492
493         this._restoredShowingTimelineRecordingContentView = cookie[WebInspector.TimelineSidebarPanel.ShowingTimelineRecordingContentViewCookieKey];
494
495         var selectedTimelineViewIdentifier = cookie[WebInspector.TimelineSidebarPanel.SelectedTimelineViewIdentifierCookieKey];
496         if (selectedTimelineViewIdentifier === WebInspector.TimelineRecord.Type.RenderingFrame && !this._renderingFramesSupported)
497             selectedTimelineViewIdentifier = null;
498
499         if (selectedTimelineViewIdentifier && this._displayedRecording.timelines.has(selectedTimelineViewIdentifier))
500             this.showTimelineViewForTimeline(this._displayedRecording.timelines.get(selectedTimelineViewIdentifier));
501         else
502             this.showTimelineOverview();
503
504         // Don't call NavigationSidebarPanel.restoreStateFromCookie, because it tries to match based
505         // on type only as a last resort. This would cause the first recording to be reselected on reload.
506     }
507
508     // Private
509
510     _toggleRecordingOnSpacebar(event)
511     {
512         if (WebInspector.isEventTargetAnEditableField(event))
513             return;
514
515         this._toggleRecording();
516     }
517
518     _toggleNewRecordingOnSpacebar(event)
519     {
520         if (WebInspector.isEventTargetAnEditableField(event))
521             return;
522
523         this._toggleRecording(true);
524     }
525
526     _toggleRecording(shouldCreateRecording)
527     {
528         if (WebInspector.timelineManager.isCapturing()) {
529             WebInspector.timelineManager.stopCapturing();
530
531             this._recordGlyphElement.title = WebInspector.UIString("Click or press the spacebar to record.")
532         } else {
533             WebInspector.timelineManager.startCapturing(shouldCreateRecording);
534             // Show the timeline to which events will be appended.
535             this._recordingLoaded();
536
537             this._recordGlyphElement.title = WebInspector.UIString("Click or press the spacebar to stop recording.")
538         }
539     }
540
541     _treeElementGoToArrowWasClicked(event)
542     {
543         this._clickedTreeElementGoToArrow = true;
544
545         var treeElement = event.target.__treeElement;
546         console.assert(treeElement instanceof WebInspector.TreeElement);
547
548         treeElement.select(true, true);
549
550         this._clickedTreeElementGoToArrow = false;
551     }
552
553     _treeElementCloseButtonClicked(event)
554     {
555         var currentTimelineView = this._displayedContentView ? this._displayedContentView.currentTimelineView : null;
556         if (currentTimelineView && currentTimelineView.representedObject instanceof WebInspector.Timeline)
557             this.showTimelineViewForTimeline(currentTimelineView.representedObject);
558         else
559             this.showTimelineOverview();
560     }
561
562     _recordingsTreeElementSelected(treeElement, selectedByUser)
563     {
564         console.assert(treeElement.representedObject instanceof WebInspector.TimelineRecording);
565
566         this._recordingSelected(treeElement.representedObject);
567     }
568
569     _renderingFrameTimelineTimesUpdated(event)
570     {
571         if (this.viewMode === WebInspector.TimelineSidebarPanel.ViewMode.RenderingFrames)
572             this._refreshFrameSelectionChart();
573     }
574
575     _timelinesTreeElementSelected(treeElement, selectedByUser)
576     {
577         console.assert(this._timelineTreeElementMap.get(treeElement.representedObject) === treeElement, treeElement);
578
579         // If not selected by user, then this selection merely synced the tree element with the content view's contents.
580         if (!selectedByUser) {
581             console.assert(this._displayedContentView.currentTimelineView.representedObject === treeElement.representedObject);
582             return;
583         }
584
585         var timeline = treeElement.representedObject;
586         console.assert(timeline instanceof WebInspector.Timeline, timeline);
587         console.assert(this._displayedRecording.timelines.get(timeline.type) === timeline, timeline);
588
589         this._previousSelectedTimelineType = timeline.type;
590
591         this._displayedContentView.showTimelineViewForTimeline(timeline);
592         this.contentBrowser.showContentView(this._displayedContentView);
593     }
594
595     _contentBrowserCurrentContentViewDidChange(event)
596     {
597         var didShowTimelineRecordingContentView = this.contentBrowser.currentContentView instanceof WebInspector.TimelineRecordingContentView;
598         this.element.classList.toggle(WebInspector.TimelineSidebarPanel.TimelineRecordingContentViewShowingStyleClass, didShowTimelineRecordingContentView);
599
600         if (this.viewMode === WebInspector.TimelineSidebarPanel.ViewMode.RenderingFrames)
601             this._refreshFrameSelectionChart();
602     }
603
604     _capturingStarted(event)
605     {
606         this._recordStatusElement.textContent = WebInspector.UIString("Recording");
607         this._recordGlyphElement.classList.add(WebInspector.TimelineSidebarPanel.RecordGlyphRecordingStyleClass);
608     }
609
610     _capturingStopped(event)
611     {
612         this._recordStatusElement.textContent = "";
613         this._recordGlyphElement.classList.remove(WebInspector.TimelineSidebarPanel.RecordGlyphRecordingStyleClass);
614     }
615
616     _recordingCreated(event)
617     {
618         this._addRecording(event.data.recording)
619         this._recordingCountChanged();
620     }
621
622     _addRecording(recording)
623     {
624         console.assert(recording instanceof WebInspector.TimelineRecording, recording);
625
626         var recordingTreeElement = new WebInspector.GeneralTreeElement(WebInspector.TimelineSidebarPanel.StopwatchIconStyleClass, recording.displayName, null, recording);
627         this._recordingTreeElementMap.set(recording, recordingTreeElement);
628         this._recordingsTreeOutline.appendChild(recordingTreeElement);
629     }
630
631     _recordingCountChanged()
632     {
633         var previousTreeElement = null;
634         for (var treeElement of this._recordingTreeElementMap.values()) {
635             if (previousTreeElement) {
636                 previousTreeElement.nextSibling = treeElement;
637                 treeElement.previousSibling = previousTreeElement;
638             }
639
640             previousTreeElement = treeElement;
641         }
642     }
643
644     _recordingSelected(recording)
645     {
646         console.assert(recording instanceof WebInspector.TimelineRecording, recording);
647
648         var oldRecording = this._displayedRecording || null;
649
650         if (oldRecording) {
651             oldRecording.removeEventListener(WebInspector.TimelineRecording.Event.TimelineAdded, this._timelineAdded, this);
652             oldRecording.removeEventListener(WebInspector.TimelineRecording.Event.TimelineRemoved, this._timelineRemoved, this);
653
654             // Destroy tree elements in one operation to avoid unnecessary fixups.
655             this._timelinesTreeOutline.removeChildren();
656             this._timelineTreeElementMap.clear();
657         }
658
659         this._displayedRecording = recording;
660         this._displayedRecording.addEventListener(WebInspector.TimelineRecording.Event.TimelineAdded, this._timelineAdded, this);
661         this._displayedRecording.addEventListener(WebInspector.TimelineRecording.Event.TimelineRemoved, this._timelineRemoved, this);
662
663         for (var timeline of recording.timelines.values())
664             this._timelineAdded(timeline);
665
666         // Save the current state incase we need to restore it to a new recording.
667         var cookie = {};
668         this.saveStateToCookie(cookie);
669
670         // Try to get the recording content view if it exists already, if it does we don't want to restore the cookie.
671         var onlyExisting = true;
672         this._displayedContentView = this.contentBrowser.contentViewForRepresentedObject(this._displayedRecording, onlyExisting, {timelineSidebarPanel: this});
673         if (this._displayedContentView) {
674             // Show the timeline that was being shown to update the sidebar tree state.
675             var currentTimelineView = this._displayedContentView.currentTimelineView;
676             if (currentTimelineView && currentTimelineView.representedObject instanceof WebInspector.Timeline)
677                 this.showTimelineViewForTimeline(currentTimelineView.representedObject);
678             else
679                 this.showTimelineOverview();
680
681             this.updateFilter();
682             return;
683         }
684
685         onlyExisting = false;
686         this._displayedContentView = this.contentBrowser.contentViewForRepresentedObject(this._displayedRecording, onlyExisting, {timelineSidebarPanel: this});
687
688         // Restore the cookie to carry over the previous recording view state to the new recording.
689         this.restoreStateFromCookie(cookie);
690
691         this.updateFilter();
692     }
693
694     _recordingLoaded(event)
695     {
696         this._recordingSelected(WebInspector.timelineManager.activeRecording);
697     }
698
699     _timelineAdded(timelineOrEvent)
700     {
701         var timeline = timelineOrEvent;
702         if (!(timeline instanceof WebInspector.Timeline))
703             timeline = timelineOrEvent.data.timeline;
704
705         console.assert(timeline instanceof WebInspector.Timeline, timeline);
706         console.assert(!this._timelineTreeElementMap.has(timeline), timeline);
707
708         if (timeline.type === WebInspector.TimelineRecord.Type.RenderingFrame) {
709             timeline.addEventListener(WebInspector.Timeline.Event.TimesUpdated, this._renderingFrameTimelineTimesUpdated, this);
710             return;
711         }
712
713         var timelineTreeElement = new WebInspector.GeneralTreeElement([timeline.iconClassName, WebInspector.TimelineSidebarPanel.LargeIconStyleClass], timeline.displayName, null, timeline);
714         var tooltip = WebInspector.UIString("Close %s timeline view").format(timeline.displayName);
715         wrappedSVGDocument("Images/CloseLarge.svg", WebInspector.TimelineSidebarPanel.CloseButtonStyleClass, tooltip, function(element) {
716             var button = new WebInspector.TreeElementStatusButton(element);
717             button.addEventListener(WebInspector.TreeElementStatusButton.Event.Clicked, this.showTimelineOverview, this);
718             timelineTreeElement.status = button.element;
719         }.bind(this));
720
721         this._timelinesTreeOutline.appendChild(timelineTreeElement);
722         this._timelineTreeElementMap.set(timeline, timelineTreeElement);
723
724         this._timelineCountChanged();
725     }
726
727     _timelineRemoved(event)
728     {
729         var timeline = event.data.timeline;
730         console.assert(timeline instanceof WebInspector.Timeline, timeline);
731
732         if (timeline.type === WebInspector.TimelineRecord.Type.RenderingFrame) {
733             timeline.removeEventListener(WebInspector.Timeline.Event.TimesUpdated, this._renderingFrameTimelineTimesUpdated, this);
734             return;
735         }
736
737         console.assert(this._timelineTreeElementMap.has(timeline), timeline);
738
739         var timelineTreeElement = this._timelineTreeElementMap.take(timeline);
740         var shouldSuppressOnDeselect = false;
741         var shouldSuppressSelectSibling = true;
742         this._timelinesTreeOutline.removeChild(timelineTreeElement, shouldSuppressOnDeselect, shouldSuppressSelectSibling);
743         this._timelineTreeElementMap.delete(timeline);
744
745         this._timelineCountChanged();
746     }
747
748     _timelineCountChanged()
749     {
750         var previousTreeElement = null;
751         for (var treeElement of this._timelineTreeElementMap.values()) {
752             if (previousTreeElement) {
753                 previousTreeElement.nextSibling = treeElement;
754                 treeElement.previousSibling = previousTreeElement;
755             }
756
757             previousTreeElement = treeElement;
758         }
759
760         var timelineHeight = 36;
761         var eventTitleBarOffset = 58;
762         var contentElementOffset = 81;
763         var timelineCount = this._timelineTreeElementMap.size;
764
765         this._timelinesContentContainerElement.style.height = (timelineHeight * timelineCount) + "px";
766         this._timelineEventsTitleBarElement.style.top = (timelineHeight * timelineCount + eventTitleBarOffset) + "px";
767         this.contentElement.style.top = (timelineHeight * timelineCount + contentElementOffset) + "px";
768     }
769
770     _recordGlyphMousedOver(event)
771     {
772         this._recordGlyphElement.classList.remove(WebInspector.TimelineSidebarPanel.RecordGlyphRecordingForcedStyleClass);
773
774         if (WebInspector.timelineManager.isCapturing())
775             this._recordStatusElement.textContent = WebInspector.UIString("Stop Recording");
776         else
777             this._recordStatusElement.textContent = WebInspector.UIString("Start Recording");
778     }
779
780     _recordGlyphMousedOut(event)
781     {
782         this._recordGlyphElement.classList.remove(WebInspector.TimelineSidebarPanel.RecordGlyphRecordingForcedStyleClass);
783
784         if (WebInspector.timelineManager.isCapturing())
785             this._recordStatusElement.textContent = WebInspector.UIString("Recording");
786         else
787             this._recordStatusElement.textContent = "";
788     }
789
790     _recordGlyphClicked(event)
791     {
792         // Add forced class to prevent the glyph from showing a confusing status after click.
793         this._recordGlyphElement.classList.add(WebInspector.TimelineSidebarPanel.RecordGlyphRecordingForcedStyleClass);
794
795         this._toggleRecording(event.shiftKey);
796     }
797
798     _viewModeSelected(event)
799     {
800         console.assert(event.target.selectedNavigationItem);
801         if (!event.target.selectedNavigationItem)
802             return;
803
804         var selectedNavigationItem = event.target.selectedNavigationItem;
805         var selectedByUser = true;
806         this._changeViewMode(selectedNavigationItem.identifier, selectedByUser);
807     }
808
809     _viewModeForTimeline(timeline)
810     {
811         if (timeline && timeline.type === WebInspector.TimelineRecord.Type.RenderingFrame)
812             return WebInspector.TimelineSidebarPanel.ViewMode.RenderingFrames;
813
814         return WebInspector.TimelineSidebarPanel.ViewMode.Timelines;
815     }
816
817     _changeViewMode(mode, selectedByUser)
818     {
819         if (!this._renderingFramesSupported || this._viewMode === mode)
820             return;
821
822         this._viewMode = mode;
823         this._viewModeNavigationBar.selectedNavigationItem = this._viewMode;
824
825         if (this._viewMode === WebInspector.TimelineSidebarPanel.ViewMode.Timelines) {
826             this._timelinesTreeOutline.element.classList.remove(WebInspector.NavigationSidebarPanel.ContentTreeOutlineElementHiddenStyleClassName);
827             this._frameSelectionChartSection.collapsed = true;
828         } else {
829             this._timelinesTreeOutline.element.classList.add(WebInspector.NavigationSidebarPanel.ContentTreeOutlineElementHiddenStyleClassName);
830             this._frameSelectionChartSection.collapsed = false;
831         }
832
833         if (selectedByUser) {
834             var timelineType = this._previousSelectedTimelineType;
835             if (this._viewMode === WebInspector.TimelineSidebarPanel.ViewMode.RenderingFrames) {
836                 this._previousSelectedTimelineType = this._timelinesTreeOutline.selectedTreeElement ? this._timelinesTreeOutline.selectedTreeElement.representedObject.type : null;
837                 timelineType = WebInspector.TimelineRecord.Type.RenderingFrame;
838             }
839
840             if (timelineType) {
841                 console.assert(this._displayedRecording.timelines.has(timelineType), timelineType);
842                 this.showTimelineViewForTimeline(this._displayedRecording.timelines.get(timelineType));
843             } else
844                 this.showTimelineOverview();
845         }
846
847         this.updateFilter();
848     }
849
850     _frameSelectionLegendItemChecked(event)
851     {
852         if (event.data.checked)
853             this._renderingFrameTaskFilter.delete(event.data.id);
854         else
855             this._renderingFrameTaskFilter.add(event.data.id);
856
857         this.updateFilter();
858     }
859
860     _refreshFrameSelectionChart()
861     {
862         if (!this.visible)
863             return;
864
865         function getSelectedRecords()
866         {
867             console.assert(this._displayedRecording);
868             console.assert(this._displayedRecording.timelines.has(WebInspector.TimelineRecord.Type.RenderingFrame), "Cannot find rendering frames timeline in displayed recording");
869
870             var timeline = this._displayedRecording.timelines.get(WebInspector.TimelineRecord.Type.RenderingFrame);
871             var selectedRecords = [];
872             for (var record of timeline.records) {
873                 console.assert(record instanceof WebInspector.RenderingFrameTimelineRecord);
874                 // If this frame is completely before the bounds of the graph, skip this record.
875                 if (record.frameIndex < this._startFrameIndex)
876                     continue;
877
878                 // If this record is completely after the end time, break out now.
879                 // Records are sorted, so all records after this will be beyond the end time too.
880                 if (record.frameIndex > this._endFrameIndex)
881                     break;
882
883                 selectedRecords.push(record);
884             }
885
886             return selectedRecords;
887         }
888
889         var chart = this._frameSelectionChartRow;
890         var records = getSelectedRecords.call(this);
891         var chartData = Object.keys(WebInspector.RenderingFrameTimelineRecord.TaskType).map(function(taskTypeKey) {
892             var taskType = WebInspector.RenderingFrameTimelineRecord.TaskType[taskTypeKey];
893             var label = WebInspector.RenderingFrameTimelineRecord.displayNameForTaskType(taskType);
894             var value = records.reduce(function(previousValue, currentValue) { return previousValue + currentValue.durationForTask(taskType); }, 0);
895             var color = this._chartColors.get(taskType);
896             return {id: taskType, label, value, color, checkbox: taskType !== WebInspector.RenderingFrameTimelineRecord.TaskType.Other};
897         }, this);
898
899         this._frameSelectionChartRow.data = chartData;
900
901         if (!records.length) {
902             this._frameSelectionChartRow.title = WebInspector.UIString("Frames: None Selected");
903             return;
904         }
905
906         var firstRecord = records[0];
907         var lastRecord = records.lastValue;
908
909         if (records.length > 1) {
910             this._frameSelectionChartRow.title = WebInspector.UIString("Frames: %d – %d (%s – %s)").format(firstRecord.frameNumber, lastRecord.frameNumber,
911                 Number.secondsToString(firstRecord.startTime), Number.secondsToString(lastRecord.endTime));
912         } else {
913             this._frameSelectionChartRow.title = WebInspector.UIString("Frame: %d (%s – %s)").format(firstRecord.frameNumber,
914                 Number.secondsToString(firstRecord.startTime), Number.secondsToString(lastRecord.endTime));
915         }
916     }
917
918     // These methods are only used when ReplayAgent is available.
919
920     _updateReplayInterfaceVisibility()
921     {
922         var shouldShowReplayInterface = window.ReplayAgent && WebInspector.showReplayInterfaceSetting.value;
923
924         this._statusBarElement.classList.toggle(WebInspector.TimelineSidebarPanel.HiddenStyleClassName, shouldShowReplayInterface);
925         this._replayNavigationBar.element.classList.toggle(WebInspector.TimelineSidebarPanel.HiddenStyleClassName, !shouldShowReplayInterface);
926     }
927
928     _contextMenuNavigationBarOrStatusBar()
929     {
930         if (!window.ReplayAgent)
931             return;
932
933         function toggleReplayInterface() {
934             WebInspector.showReplayInterfaceSetting.value = !WebInspector.showReplayInterfaceSetting.value;
935         }
936
937         var contextMenu = new WebInspector.ContextMenu(event);
938         if (WebInspector.showReplayInterfaceSetting.value)
939             contextMenu.appendItem(WebInspector.UIString("Hide Replay Controls"), toggleReplayInterface);
940         else
941             contextMenu.appendItem(WebInspector.UIString("Show Replay Controls"), toggleReplayInterface);
942         contextMenu.show();
943     }
944
945     _replayCaptureButtonClicked()
946     {
947         if (!this._replayCaptureButtonItem.activated) {
948             WebInspector.replayManager.startCapturing();
949             WebInspector.timelineManager.startCapturing();
950
951             // De-bounce further presses until the backend has begun capturing.
952             this._replayCaptureButtonItem.activated = true;
953             this._replayCaptureButtonItem.enabled = false;
954         } else {
955             WebInspector.replayManager.stopCapturing();
956             WebInspector.timelineManager.stopCapturing();
957
958             this._replayCaptureButtonItem.enabled = false;
959         }
960     }
961
962     _replayPauseResumeButtonClicked()
963     {
964         if (this._replayPauseResumeButtonItem.toggled)
965             WebInspector.replayManager.pausePlayback();
966         else
967             WebInspector.replayManager.replayToCompletion();
968     }
969
970     _captureStarted()
971     {
972         this._replayCaptureButtonItem.enabled = true;
973     }
974
975     _captureStopped()
976     {
977         this._replayCaptureButtonItem.activated = false;
978         this._replayPauseResumeButtonItem.enabled = true;
979     }
980
981     _playbackStarted()
982     {
983         this._replayPauseResumeButtonItem.toggled = true;
984     }
985
986     _playbackPaused()
987     {
988         this._replayPauseResumeButtonItem.toggled = false;
989     }
990 };
991
992 WebInspector.TimelineSidebarPanel.ViewMode = {
993     Timelines: "timeline-sidebar-panel-view-mode-timelines",
994     RenderingFrames: "timeline-sidebar-panel-view-mode-frames"
995 };
996
997 WebInspector.TimelineSidebarPanel.HiddenStyleClassName = "hidden";
998 WebInspector.TimelineSidebarPanel.StatusBarStyleClass = "status-bar";
999 WebInspector.TimelineSidebarPanel.RecordGlyphStyleClass = "record-glyph";
1000 WebInspector.TimelineSidebarPanel.RecordGlyphRecordingStyleClass = "recording";
1001 WebInspector.TimelineSidebarPanel.RecordGlyphRecordingForcedStyleClass = "forced";
1002 WebInspector.TimelineSidebarPanel.RecordStatusStyleClass = "record-status";
1003 WebInspector.TimelineSidebarPanel.TitleBarStyleClass = "title-bar";
1004 WebInspector.TimelineSidebarPanel.TitleBarTextStyleClass = "title-bar-text";
1005 WebInspector.TimelineSidebarPanel.TitleBarScopeBarStyleClass = "title-bar-scope-bar";
1006 WebInspector.TimelineSidebarPanel.TimelinesTitleBarStyleClass = "timelines";
1007 WebInspector.TimelineSidebarPanel.TimelineEventsTitleBarStyleClass = "timeline-events";
1008 WebInspector.TimelineSidebarPanel.TimelinesContentContainerStyleClass = "timelines-content";
1009 WebInspector.TimelineSidebarPanel.CloseButtonStyleClass = "close-button";
1010 WebInspector.TimelineSidebarPanel.LargeIconStyleClass = "large";
1011 WebInspector.TimelineSidebarPanel.StopwatchIconStyleClass = "stopwatch-icon";
1012 WebInspector.TimelineSidebarPanel.NetworkIconStyleClass = "network-icon";
1013 WebInspector.TimelineSidebarPanel.ColorsIconStyleClass = "colors-icon";
1014 WebInspector.TimelineSidebarPanel.ScriptIconStyleClass = "script-icon";
1015 WebInspector.TimelineSidebarPanel.RenderingFrameIconStyleClass = "rendering-frame-icon";
1016 WebInspector.TimelineSidebarPanel.TimelineRecordingContentViewShowingStyleClass = "timeline-recording-content-view-showing";
1017
1018 WebInspector.TimelineSidebarPanel.ShowingTimelineRecordingContentViewCookieKey = "timeline-sidebar-panel-showing-timeline-recording-content-view";
1019 WebInspector.TimelineSidebarPanel.SelectedTimelineViewIdentifierCookieKey = "timeline-sidebar-panel-selected-timeline-view-identifier";
1020 WebInspector.TimelineSidebarPanel.OverviewTimelineIdentifierCookieValue = "overview";