Web Inspector: Hide child rows for filtered tasks in the Rendering Frames data grid
[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.TimelineRecord))
399                 treeElement = treeElement.parent;
400
401             console.assert(treeElement, "Cannot apply task filter: no TimelineRecord found.");
402             if (!treeElement)
403                 return false;
404
405             var visible = false;
406             if (!(treeElement.record instanceof WebInspector.RenderingFrameTimelineRecord)) {
407                 var taskType = WebInspector.RenderingFrameTimelineRecord.taskTypeForTimelineRecord(treeElement.record);
408                 if (!this._renderingFrameTaskFilter.has(taskType))
409                     visible = true;
410             }
411
412             if (!visible)
413                 return false;
414         }
415
416         return this._displayedContentView.matchTreeElementAgainstCustomFilters(treeElement);
417     }
418
419     treeElementAddedOrChanged(treeElement)
420     {
421         if (treeElement.status)
422             return;
423
424         if (!treeElement.treeOutline || typeof treeElement.treeOutline.__canShowContentViewForTreeElement !== "function")
425             return;
426
427         if (!treeElement.treeOutline.__canShowContentViewForTreeElement(treeElement))
428             return;
429
430         wrappedSVGDocument("Images/Close.svg", null, WebInspector.UIString("Close resource view"), function(element) {
431             var fragment = document.createDocumentFragment();
432
433             var closeButton = new WebInspector.TreeElementStatusButton(element);
434             closeButton.element.classList.add("close");
435             closeButton.addEventListener(WebInspector.TreeElementStatusButton.Event.Clicked, this._treeElementCloseButtonClicked, this);
436             fragment.appendChild(closeButton.element);
437
438             var goToButton = new WebInspector.TreeElementStatusButton(WebInspector.createGoToArrowButton());
439             goToButton.__treeElement = treeElement;
440             goToButton.addEventListener(WebInspector.TreeElementStatusButton.Event.Clicked, this._treeElementGoToArrowWasClicked, this);
441             fragment.appendChild(goToButton.element);
442
443             treeElement.status = fragment;
444         }.bind(this));
445     }
446
447     canShowDifferentContentView()
448     {
449         if (this._clickedTreeElementGoToArrow)
450             return true;
451
452         if (this.contentBrowser.currentContentView instanceof WebInspector.TimelineRecordingContentView)
453             return false;
454
455         return !this.restoringState || !this._restoredShowingTimelineRecordingContentView;
456     }
457
458     saveStateToCookie(cookie)
459     {
460         console.assert(cookie);
461
462         cookie[WebInspector.TimelineSidebarPanel.ShowingTimelineRecordingContentViewCookieKey] = this.contentBrowser.currentContentView instanceof WebInspector.TimelineRecordingContentView;
463
464         if (this._viewMode === WebInspector.TimelineSidebarPanel.ViewMode.RenderingFrames)
465             cookie[WebInspector.TimelineSidebarPanel.SelectedTimelineViewIdentifierCookieKey] = WebInspector.TimelineRecord.Type.RenderingFrame;
466         else {
467             var selectedTreeElement = this._timelinesTreeOutline.selectedTreeElement;
468             if (selectedTreeElement)
469                 cookie[WebInspector.TimelineSidebarPanel.SelectedTimelineViewIdentifierCookieKey] = selectedTreeElement.representedObject.type;
470             else
471                 cookie[WebInspector.TimelineSidebarPanel.SelectedTimelineViewIdentifierCookieKey] = WebInspector.TimelineSidebarPanel.OverviewTimelineIdentifierCookieValue;    
472         }
473
474         super.saveStateToCookie(cookie);
475     }
476
477     restoreStateFromCookie(cookie, relaxedMatchDelay)
478     {
479         console.assert(cookie);
480
481         // The _displayedContentView is not ready on initial load, so delay the restore.
482         // This matches the delayed work in the WebInspector.TimelineSidebarPanel constructor.
483         if (!this._displayedContentView) {
484             setTimeout(this.restoreStateFromCookie.bind(this, cookie, relaxedMatchDelay), 0);
485             return;
486         }
487
488         this._restoredShowingTimelineRecordingContentView = cookie[WebInspector.TimelineSidebarPanel.ShowingTimelineRecordingContentViewCookieKey];
489
490         var selectedTimelineViewIdentifier = cookie[WebInspector.TimelineSidebarPanel.SelectedTimelineViewIdentifierCookieKey];
491         if (selectedTimelineViewIdentifier === WebInspector.TimelineRecord.Type.RenderingFrame && !this._renderingFramesSupported)
492             selectedTimelineViewIdentifier = null;
493
494         if (selectedTimelineViewIdentifier && this._displayedRecording.timelines.has(selectedTimelineViewIdentifier))
495             this.showTimelineViewForTimeline(this._displayedRecording.timelines.get(selectedTimelineViewIdentifier));
496         else
497             this.showTimelineOverview();
498
499         // Don't call NavigationSidebarPanel.restoreStateFromCookie, because it tries to match based
500         // on type only as a last resort. This would cause the first recording to be reselected on reload.
501     }
502
503     // Private
504
505     _toggleRecordingOnSpacebar(event)
506     {
507         if (WebInspector.isEventTargetAnEditableField(event))
508             return;
509
510         this._toggleRecording();
511     }
512
513     _toggleNewRecordingOnSpacebar(event)
514     {
515         if (WebInspector.isEventTargetAnEditableField(event))
516             return;
517
518         this._toggleRecording(true);
519     }
520
521     _toggleRecording(shouldCreateRecording)
522     {
523         if (WebInspector.timelineManager.isCapturing()) {
524             WebInspector.timelineManager.stopCapturing();
525
526             this._recordGlyphElement.title = WebInspector.UIString("Click or press the spacebar to record.")
527         } else {
528             WebInspector.timelineManager.startCapturing(shouldCreateRecording);
529             // Show the timeline to which events will be appended.
530             this._recordingLoaded();
531
532             this._recordGlyphElement.title = WebInspector.UIString("Click or press the spacebar to stop recording.")
533         }
534     }
535
536     _treeElementGoToArrowWasClicked(event)
537     {
538         this._clickedTreeElementGoToArrow = true;
539
540         var treeElement = event.target.__treeElement;
541         console.assert(treeElement instanceof WebInspector.TreeElement);
542
543         treeElement.select(true, true);
544
545         this._clickedTreeElementGoToArrow = false;
546     }
547
548     _treeElementCloseButtonClicked(event)
549     {
550         var currentTimelineView = this._displayedContentView ? this._displayedContentView.currentTimelineView : null;
551         if (currentTimelineView && currentTimelineView.representedObject instanceof WebInspector.Timeline)
552             this.showTimelineViewForTimeline(currentTimelineView.representedObject);
553         else
554             this.showTimelineOverview();
555     }
556
557     _recordingsTreeElementSelected(treeElement, selectedByUser)
558     {
559         console.assert(treeElement.representedObject instanceof WebInspector.TimelineRecording);
560
561         this._recordingSelected(treeElement.representedObject);
562     }
563
564     _renderingFrameTimelineTimesUpdated(event)
565     {
566         if (this.viewMode === WebInspector.TimelineSidebarPanel.ViewMode.RenderingFrames)
567             this._refreshFrameSelectionChart();
568     }
569
570     _timelinesTreeElementSelected(treeElement, selectedByUser)
571     {
572         console.assert(this._timelineTreeElementMap.get(treeElement.representedObject) === treeElement, treeElement);
573
574         // If not selected by user, then this selection merely synced the tree element with the content view's contents.
575         if (!selectedByUser) {
576             console.assert(this._displayedContentView.currentTimelineView.representedObject === treeElement.representedObject);
577             return;
578         }
579
580         var timeline = treeElement.representedObject;
581         console.assert(timeline instanceof WebInspector.Timeline, timeline);
582         console.assert(this._displayedRecording.timelines.get(timeline.type) === timeline, timeline);
583
584         this._previousSelectedTimelineType = timeline.type;
585
586         this._displayedContentView.showTimelineViewForTimeline(timeline);
587         this.contentBrowser.showContentView(this._displayedContentView);
588     }
589
590     _contentBrowserCurrentContentViewDidChange(event)
591     {
592         var didShowTimelineRecordingContentView = this.contentBrowser.currentContentView instanceof WebInspector.TimelineRecordingContentView;
593         this.element.classList.toggle(WebInspector.TimelineSidebarPanel.TimelineRecordingContentViewShowingStyleClass, didShowTimelineRecordingContentView);
594
595         if (this.viewMode === WebInspector.TimelineSidebarPanel.ViewMode.RenderingFrames)
596             this._refreshFrameSelectionChart();
597     }
598
599     _capturingStarted(event)
600     {
601         this._recordStatusElement.textContent = WebInspector.UIString("Recording");
602         this._recordGlyphElement.classList.add(WebInspector.TimelineSidebarPanel.RecordGlyphRecordingStyleClass);
603     }
604
605     _capturingStopped(event)
606     {
607         this._recordStatusElement.textContent = "";
608         this._recordGlyphElement.classList.remove(WebInspector.TimelineSidebarPanel.RecordGlyphRecordingStyleClass);
609     }
610
611     _recordingCreated(event)
612     {
613         this._addRecording(event.data.recording)
614         this._recordingCountChanged();
615     }
616
617     _addRecording(recording)
618     {
619         console.assert(recording instanceof WebInspector.TimelineRecording, recording);
620
621         var recordingTreeElement = new WebInspector.GeneralTreeElement(WebInspector.TimelineSidebarPanel.StopwatchIconStyleClass, recording.displayName, null, recording);
622         this._recordingTreeElementMap.set(recording, recordingTreeElement);
623         this._recordingsTreeOutline.appendChild(recordingTreeElement);
624     }
625
626     _recordingCountChanged()
627     {
628         var previousTreeElement = null;
629         for (var treeElement of this._recordingTreeElementMap.values()) {
630             if (previousTreeElement) {
631                 previousTreeElement.nextSibling = treeElement;
632                 treeElement.previousSibling = previousTreeElement;
633             }
634
635             previousTreeElement = treeElement;
636         }
637     }
638
639     _recordingSelected(recording)
640     {
641         console.assert(recording instanceof WebInspector.TimelineRecording, recording);
642
643         var oldRecording = this._displayedRecording || null;
644
645         if (oldRecording) {
646             oldRecording.removeEventListener(WebInspector.TimelineRecording.Event.TimelineAdded, this._timelineAdded, this);
647             oldRecording.removeEventListener(WebInspector.TimelineRecording.Event.TimelineRemoved, this._timelineRemoved, this);
648
649             // Destroy tree elements in one operation to avoid unnecessary fixups.
650             this._timelinesTreeOutline.removeChildren();
651             this._timelineTreeElementMap.clear();
652         }
653
654         this._displayedRecording = recording;
655         this._displayedRecording.addEventListener(WebInspector.TimelineRecording.Event.TimelineAdded, this._timelineAdded, this);
656         this._displayedRecording.addEventListener(WebInspector.TimelineRecording.Event.TimelineRemoved, this._timelineRemoved, this);
657
658         for (var timeline of recording.timelines.values())
659             this._timelineAdded(timeline);
660
661         // Save the current state incase we need to restore it to a new recording.
662         var cookie = {};
663         this.saveStateToCookie(cookie);
664
665         // Try to get the recording content view if it exists already, if it does we don't want to restore the cookie.
666         var onlyExisting = true;
667         this._displayedContentView = this.contentBrowser.contentViewForRepresentedObject(this._displayedRecording, onlyExisting, {timelineSidebarPanel: this});
668         if (this._displayedContentView) {
669             // Show the timeline that was being shown to update the sidebar tree state.
670             var currentTimelineView = this._displayedContentView.currentTimelineView;
671             if (currentTimelineView && currentTimelineView.representedObject instanceof WebInspector.Timeline)
672                 this.showTimelineViewForTimeline(currentTimelineView.representedObject);
673             else
674                 this.showTimelineOverview();
675
676             this.updateFilter();
677             return;
678         }
679
680         onlyExisting = false;
681         this._displayedContentView = this.contentBrowser.contentViewForRepresentedObject(this._displayedRecording, onlyExisting, {timelineSidebarPanel: this});
682
683         // Restore the cookie to carry over the previous recording view state to the new recording.
684         this.restoreStateFromCookie(cookie);
685
686         this.updateFilter();
687     }
688
689     _recordingLoaded(event)
690     {
691         this._recordingSelected(WebInspector.timelineManager.activeRecording);
692     }
693
694     _timelineAdded(timelineOrEvent)
695     {
696         var timeline = timelineOrEvent;
697         if (!(timeline instanceof WebInspector.Timeline))
698             timeline = timelineOrEvent.data.timeline;
699
700         console.assert(timeline instanceof WebInspector.Timeline, timeline);
701         console.assert(!this._timelineTreeElementMap.has(timeline), timeline);
702
703         if (timeline.type === WebInspector.TimelineRecord.Type.RenderingFrame) {
704             timeline.addEventListener(WebInspector.Timeline.Event.TimesUpdated, this._renderingFrameTimelineTimesUpdated, this);
705             return;
706         }
707
708         var timelineTreeElement = new WebInspector.GeneralTreeElement([timeline.iconClassName, WebInspector.TimelineSidebarPanel.LargeIconStyleClass], timeline.displayName, null, timeline);
709         var tooltip = WebInspector.UIString("Close %s timeline view").format(timeline.displayName);
710         wrappedSVGDocument("Images/CloseLarge.svg", WebInspector.TimelineSidebarPanel.CloseButtonStyleClass, tooltip, function(element) {
711             var button = new WebInspector.TreeElementStatusButton(element);
712             button.addEventListener(WebInspector.TreeElementStatusButton.Event.Clicked, this.showTimelineOverview, this);
713             timelineTreeElement.status = button.element;
714         }.bind(this));
715
716         this._timelinesTreeOutline.appendChild(timelineTreeElement);
717         this._timelineTreeElementMap.set(timeline, timelineTreeElement);
718
719         this._timelineCountChanged();
720     }
721
722     _timelineRemoved(event)
723     {
724         var timeline = event.data.timeline;
725         console.assert(timeline instanceof WebInspector.Timeline, timeline);
726
727         if (timeline.type === WebInspector.TimelineRecord.Type.RenderingFrame) {
728             timeline.removeEventListener(WebInspector.Timeline.Event.TimesUpdated, this._renderingFrameTimelineTimesUpdated, this);
729             return;
730         }
731
732         console.assert(this._timelineTreeElementMap.has(timeline), timeline);
733
734         var timelineTreeElement = this._timelineTreeElementMap.take(timeline);
735         var shouldSuppressOnDeselect = false;
736         var shouldSuppressSelectSibling = true;
737         this._timelinesTreeOutline.removeChild(timelineTreeElement, shouldSuppressOnDeselect, shouldSuppressSelectSibling);
738         this._timelineTreeElementMap.delete(timeline);
739
740         this._timelineCountChanged();
741     }
742
743     _timelineCountChanged()
744     {
745         var previousTreeElement = null;
746         for (var treeElement of this._timelineTreeElementMap.values()) {
747             if (previousTreeElement) {
748                 previousTreeElement.nextSibling = treeElement;
749                 treeElement.previousSibling = previousTreeElement;
750             }
751
752             previousTreeElement = treeElement;
753         }
754
755         var timelineHeight = 36;
756         var eventTitleBarOffset = 58;
757         var contentElementOffset = 81;
758         var timelineCount = this._timelineTreeElementMap.size;
759
760         this._timelinesContentContainerElement.style.height = (timelineHeight * timelineCount) + "px";
761         this._timelineEventsTitleBarElement.style.top = (timelineHeight * timelineCount + eventTitleBarOffset) + "px";
762         this.contentElement.style.top = (timelineHeight * timelineCount + contentElementOffset) + "px";
763     }
764
765     _recordGlyphMousedOver(event)
766     {
767         this._recordGlyphElement.classList.remove(WebInspector.TimelineSidebarPanel.RecordGlyphRecordingForcedStyleClass);
768
769         if (WebInspector.timelineManager.isCapturing())
770             this._recordStatusElement.textContent = WebInspector.UIString("Stop Recording");
771         else
772             this._recordStatusElement.textContent = WebInspector.UIString("Start Recording");
773     }
774
775     _recordGlyphMousedOut(event)
776     {
777         this._recordGlyphElement.classList.remove(WebInspector.TimelineSidebarPanel.RecordGlyphRecordingForcedStyleClass);
778
779         if (WebInspector.timelineManager.isCapturing())
780             this._recordStatusElement.textContent = WebInspector.UIString("Recording");
781         else
782             this._recordStatusElement.textContent = "";
783     }
784
785     _recordGlyphClicked(event)
786     {
787         // Add forced class to prevent the glyph from showing a confusing status after click.
788         this._recordGlyphElement.classList.add(WebInspector.TimelineSidebarPanel.RecordGlyphRecordingForcedStyleClass);
789
790         this._toggleRecording(event.shiftKey);
791     }
792
793     _viewModeSelected(event)
794     {
795         console.assert(event.target.selectedNavigationItem);
796         if (!event.target.selectedNavigationItem)
797             return;
798
799         var selectedNavigationItem = event.target.selectedNavigationItem;
800         var selectedByUser = true;
801         this._changeViewMode(selectedNavigationItem.identifier, selectedByUser);
802     }
803
804     _viewModeForTimeline(timeline)
805     {
806         if (timeline && timeline.type === WebInspector.TimelineRecord.Type.RenderingFrame)
807             return WebInspector.TimelineSidebarPanel.ViewMode.RenderingFrames;
808
809         return WebInspector.TimelineSidebarPanel.ViewMode.Timelines;
810     }
811
812     _changeViewMode(mode, selectedByUser)
813     {
814         if (!this._renderingFramesSupported || this._viewMode === mode)
815             return;
816
817         this._viewMode = mode;
818         this._viewModeNavigationBar.selectedNavigationItem = this._viewMode;
819
820         if (this._viewMode === WebInspector.TimelineSidebarPanel.ViewMode.Timelines) {
821             this._timelinesTreeOutline.element.classList.remove(WebInspector.NavigationSidebarPanel.ContentTreeOutlineElementHiddenStyleClassName);
822             this._frameSelectionChartSection.collapsed = true;
823         } else {
824             this._timelinesTreeOutline.element.classList.add(WebInspector.NavigationSidebarPanel.ContentTreeOutlineElementHiddenStyleClassName);
825             this._frameSelectionChartSection.collapsed = false;
826         }
827
828         if (selectedByUser) {
829             var timelineType = this._previousSelectedTimelineType;
830             if (this._viewMode === WebInspector.TimelineSidebarPanel.ViewMode.RenderingFrames) {
831                 this._previousSelectedTimelineType = this._timelinesTreeOutline.selectedTreeElement ? this._timelinesTreeOutline.selectedTreeElement.representedObject.type : null;
832                 timelineType = WebInspector.TimelineRecord.Type.RenderingFrame;
833             }
834
835             if (timelineType) {
836                 console.assert(this._displayedRecording.timelines.has(timelineType), timelineType);
837                 this.showTimelineViewForTimeline(this._displayedRecording.timelines.get(timelineType));
838             } else
839                 this.showTimelineOverview();
840         }
841
842         this.updateFilter();
843     }
844
845     _frameSelectionLegendItemChecked(event)
846     {
847         if (event.data.checked)
848             this._renderingFrameTaskFilter.delete(event.data.id);
849         else
850             this._renderingFrameTaskFilter.add(event.data.id);
851
852         this.updateFilter();
853     }
854
855     _refreshFrameSelectionChart()
856     {
857         if (!this.visible)
858             return;
859
860         function getSelectedRecords()
861         {
862             console.assert(this._displayedRecording);
863             console.assert(this._displayedRecording.timelines.has(WebInspector.TimelineRecord.Type.RenderingFrame), "Cannot find rendering frames timeline in displayed recording");
864
865             var timeline = this._displayedRecording.timelines.get(WebInspector.TimelineRecord.Type.RenderingFrame);
866             var selectedRecords = [];
867             for (var record of timeline.records) {
868                 console.assert(record instanceof WebInspector.RenderingFrameTimelineRecord);
869                 // If this frame is completely before the bounds of the graph, skip this record.
870                 if (record.frameIndex < this._startFrameIndex)
871                     continue;
872
873                 // If this record is completely after the end time, break out now.
874                 // Records are sorted, so all records after this will be beyond the end time too.
875                 if (record.frameIndex > this._endFrameIndex)
876                     break;
877
878                 selectedRecords.push(record);
879             }
880
881             return selectedRecords;
882         }
883
884         var chart = this._frameSelectionChartRow;
885         var records = getSelectedRecords.call(this);
886         var chartData = Object.keys(WebInspector.RenderingFrameTimelineRecord.TaskType).map(function(taskTypeKey) {
887             var taskType = WebInspector.RenderingFrameTimelineRecord.TaskType[taskTypeKey];
888             var label = WebInspector.RenderingFrameTimelineRecord.displayNameForTaskType(taskType);
889             var value = records.reduce(function(previousValue, currentValue) { return previousValue + currentValue.durationForTask(taskType); }, 0);
890             var color = this._chartColors.get(taskType);
891             return {id: taskType, label, value, color, checkbox: taskType !== WebInspector.RenderingFrameTimelineRecord.TaskType.Other};
892         }, this);
893
894         this._frameSelectionChartRow.data = chartData;
895
896         if (!records.length) {
897             this._frameSelectionChartRow.title = WebInspector.UIString("Frames: None Selected");
898             return;
899         }
900
901         var firstRecord = records[0];
902         var lastRecord = records.lastValue;
903
904         if (records.length > 1) {
905             this._frameSelectionChartRow.title = WebInspector.UIString("Frames: %d – %d (%s – %s)").format(firstRecord.frameNumber, lastRecord.frameNumber,
906                 Number.secondsToString(firstRecord.startTime), Number.secondsToString(lastRecord.endTime));
907         } else {
908             this._frameSelectionChartRow.title = WebInspector.UIString("Frame: %d (%s – %s)").format(firstRecord.frameNumber,
909                 Number.secondsToString(firstRecord.startTime), Number.secondsToString(lastRecord.endTime));
910         }
911     }
912
913     // These methods are only used when ReplayAgent is available.
914
915     _updateReplayInterfaceVisibility()
916     {
917         var shouldShowReplayInterface = window.ReplayAgent && WebInspector.showReplayInterfaceSetting.value;
918
919         this._statusBarElement.classList.toggle(WebInspector.TimelineSidebarPanel.HiddenStyleClassName, shouldShowReplayInterface);
920         this._replayNavigationBar.element.classList.toggle(WebInspector.TimelineSidebarPanel.HiddenStyleClassName, !shouldShowReplayInterface);
921     }
922
923     _contextMenuNavigationBarOrStatusBar()
924     {
925         if (!window.ReplayAgent)
926             return;
927
928         function toggleReplayInterface() {
929             WebInspector.showReplayInterfaceSetting.value = !WebInspector.showReplayInterfaceSetting.value;
930         }
931
932         var contextMenu = new WebInspector.ContextMenu(event);
933         if (WebInspector.showReplayInterfaceSetting.value)
934             contextMenu.appendItem(WebInspector.UIString("Hide Replay Controls"), toggleReplayInterface);
935         else
936             contextMenu.appendItem(WebInspector.UIString("Show Replay Controls"), toggleReplayInterface);
937         contextMenu.show();
938     }
939
940     _replayCaptureButtonClicked()
941     {
942         if (!this._replayCaptureButtonItem.activated) {
943             WebInspector.replayManager.startCapturing();
944             WebInspector.timelineManager.startCapturing();
945
946             // De-bounce further presses until the backend has begun capturing.
947             this._replayCaptureButtonItem.activated = true;
948             this._replayCaptureButtonItem.enabled = false;
949         } else {
950             WebInspector.replayManager.stopCapturing();
951             WebInspector.timelineManager.stopCapturing();
952
953             this._replayCaptureButtonItem.enabled = false;
954         }
955     }
956
957     _replayPauseResumeButtonClicked()
958     {
959         if (this._replayPauseResumeButtonItem.toggled)
960             WebInspector.replayManager.pausePlayback();
961         else
962             WebInspector.replayManager.replayToCompletion();
963     }
964
965     _captureStarted()
966     {
967         this._replayCaptureButtonItem.enabled = true;
968     }
969
970     _captureStopped()
971     {
972         this._replayCaptureButtonItem.activated = false;
973         this._replayPauseResumeButtonItem.enabled = true;
974     }
975
976     _playbackStarted()
977     {
978         this._replayPauseResumeButtonItem.toggled = true;
979     }
980
981     _playbackPaused()
982     {
983         this._replayPauseResumeButtonItem.toggled = false;
984     }
985 };
986
987 WebInspector.TimelineSidebarPanel.ViewMode = {
988     Timelines: "timeline-sidebar-panel-view-mode-timelines",
989     RenderingFrames: "timeline-sidebar-panel-view-mode-frames"
990 };
991
992 WebInspector.TimelineSidebarPanel.HiddenStyleClassName = "hidden";
993 WebInspector.TimelineSidebarPanel.StatusBarStyleClass = "status-bar";
994 WebInspector.TimelineSidebarPanel.RecordGlyphStyleClass = "record-glyph";
995 WebInspector.TimelineSidebarPanel.RecordGlyphRecordingStyleClass = "recording";
996 WebInspector.TimelineSidebarPanel.RecordGlyphRecordingForcedStyleClass = "forced";
997 WebInspector.TimelineSidebarPanel.RecordStatusStyleClass = "record-status";
998 WebInspector.TimelineSidebarPanel.TitleBarStyleClass = "title-bar";
999 WebInspector.TimelineSidebarPanel.TitleBarTextStyleClass = "title-bar-text";
1000 WebInspector.TimelineSidebarPanel.TitleBarScopeBarStyleClass = "title-bar-scope-bar";
1001 WebInspector.TimelineSidebarPanel.TimelinesTitleBarStyleClass = "timelines";
1002 WebInspector.TimelineSidebarPanel.TimelineEventsTitleBarStyleClass = "timeline-events";
1003 WebInspector.TimelineSidebarPanel.TimelinesContentContainerStyleClass = "timelines-content";
1004 WebInspector.TimelineSidebarPanel.CloseButtonStyleClass = "close-button";
1005 WebInspector.TimelineSidebarPanel.LargeIconStyleClass = "large";
1006 WebInspector.TimelineSidebarPanel.StopwatchIconStyleClass = "stopwatch-icon";
1007 WebInspector.TimelineSidebarPanel.NetworkIconStyleClass = "network-icon";
1008 WebInspector.TimelineSidebarPanel.ColorsIconStyleClass = "colors-icon";
1009 WebInspector.TimelineSidebarPanel.ScriptIconStyleClass = "script-icon";
1010 WebInspector.TimelineSidebarPanel.RenderingFrameIconStyleClass = "rendering-frame-icon";
1011 WebInspector.TimelineSidebarPanel.TimelineRecordingContentViewShowingStyleClass = "timeline-recording-content-view-showing";
1012
1013 WebInspector.TimelineSidebarPanel.ShowingTimelineRecordingContentViewCookieKey = "timeline-sidebar-panel-showing-timeline-recording-content-view";
1014 WebInspector.TimelineSidebarPanel.SelectedTimelineViewIdentifierCookieKey = "timeline-sidebar-panel-selected-timeline-view-identifier";
1015 WebInspector.TimelineSidebarPanel.OverviewTimelineIdentifierCookieValue = "overview";