Web Inspector: Timeline current time marker does not start moving when starting recor...
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / TimelineRecordingContentView.js
1 /*
2  * Copyright (C) 2013, 2015 Apple Inc. All rights reserved.
3  * Copyright (C) 2015 University of Washington.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
15  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
18  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24  * THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 WebInspector.TimelineRecordingContentView = class TimelineRecordingContentView extends WebInspector.ContentView
28 {
29     constructor(recording, extraArguments)
30     {
31         console.assert(extraArguments);
32         console.assert(extraArguments.timelineSidebarPanel instanceof WebInspector.TimelineSidebarPanel);
33
34         super(recording);
35
36         this._recording = recording;
37         this._timelineSidebarPanel = extraArguments.timelineSidebarPanel;
38
39         this.element.classList.add("timeline-recording");
40
41         this._linearTimelineOverview = new WebInspector.LinearTimelineOverview(this._recording);
42         this._linearTimelineOverview.addEventListener(WebInspector.TimelineOverview.Event.TimeRangeSelectionChanged, this._timeRangeSelectionChanged, this);
43
44         this._renderingFrameTimelineOverview = new WebInspector.RenderingFrameTimelineOverview(this._recording);
45         this._renderingFrameTimelineOverview.addEventListener(WebInspector.TimelineOverview.Event.TimeRangeSelectionChanged, this._timeRangeSelectionChanged, this);
46         this._renderingFrameTimelineOverview.addEventListener(WebInspector.TimelineOverview.Event.RecordSelected, this._recordSelected, this);
47
48         this._currentTimelineOverview = this._linearTimelineOverview;
49         this.element.appendChild(this._currentTimelineOverview.element);
50
51         this._contentViewContainer = new WebInspector.ContentViewContainer;
52         this._contentViewContainer.addEventListener(WebInspector.ContentViewContainer.Event.CurrentContentViewDidChange, this._currentContentViewDidChange, this);
53         this.element.appendChild(this._contentViewContainer.element);
54
55         this._clearTimelineNavigationItem = new WebInspector.ButtonNavigationItem("clear-timeline", WebInspector.UIString("Clear Timeline"), "Images/NavigationItemTrash.svg", 15, 15);
56         this._clearTimelineNavigationItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._clearTimeline, this);
57
58         this._overviewTimelineView = new WebInspector.OverviewTimelineView(recording, {timelineSidebarPanel: this._timelineSidebarPanel});
59         this._overviewTimelineView.secondsPerPixel = this._linearTimelineOverview.secondsPerPixel;
60
61         this._timelineViewMap = new Map;
62         this._pathComponentMap = new Map;
63
64         this._updating = false;
65         this._currentTime = NaN;
66         this._lastUpdateTimestamp = NaN;
67         this._startTimeNeedsReset = true;
68         this._renderingFrameTimeline = null;
69
70         this._recording.addEventListener(WebInspector.TimelineRecording.Event.TimelineAdded, this._timelineAdded, this);
71         this._recording.addEventListener(WebInspector.TimelineRecording.Event.TimelineRemoved, this._timelineRemoved, this);
72         this._recording.addEventListener(WebInspector.TimelineRecording.Event.Reset, this._recordingReset, this);
73         this._recording.addEventListener(WebInspector.TimelineRecording.Event.Unloaded, this._recordingUnloaded, this);
74
75         WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.CapturingStarted, this._capturingStarted, this);
76         WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.CapturingStopped, this._capturingStopped, this);
77
78         WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.Paused, this._debuggerPaused, this);
79         WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.Resumed, this._debuggerResumed, this);
80
81         WebInspector.ContentView.addEventListener(WebInspector.ContentView.Event.SelectionPathComponentsDidChange, this._contentViewSelectionPathComponentDidChange, this);
82         WebInspector.ContentView.addEventListener(WebInspector.ContentView.Event.SupplementalRepresentedObjectsDidChange, this._contentViewSupplementalRepresentedObjectsDidChange, this);
83
84         for (var timeline of this._recording.timelines.values())
85             this._timelineAdded(timeline);
86
87         this.showOverviewTimelineView();
88     }
89
90     // Public
91
92     showOverviewTimelineView()
93     {
94         this._contentViewContainer.showContentView(this._overviewTimelineView);
95     }
96
97     showTimelineViewForTimeline(timeline)
98     {
99         console.assert(timeline instanceof WebInspector.Timeline, timeline);
100         console.assert(this._timelineViewMap.has(timeline), timeline);
101         if (!this._timelineViewMap.has(timeline))
102             return;
103
104         this._contentViewContainer.showContentView(this._timelineViewMap.get(timeline));
105     }
106
107     get supportsSplitContentBrowser()
108     {
109         // The layout of the overview and split content browser don't work well.
110         return false;
111     }
112
113     get selectionPathComponents()
114     {
115         if (!this._contentViewContainer.currentContentView)
116             return [];
117
118         var pathComponents = this._contentViewContainer.currentContentView.selectionPathComponents || [];
119         var representedObject = this._contentViewContainer.currentContentView.representedObject;
120         if (representedObject instanceof WebInspector.Timeline)
121             pathComponents.unshift(this._pathComponentMap.get(representedObject));
122         return pathComponents;
123     }
124
125     get supplementalRepresentedObjects()
126     {
127         if (!this._contentViewContainer.currentContentView)
128             return [];
129         return this._contentViewContainer.currentContentView.supplementalRepresentedObjects;
130     }
131
132     get navigationItems()
133     {
134         return [this._clearTimelineNavigationItem];
135     }
136
137     get handleCopyEvent()
138     {
139         var currentContentView = this._contentViewContainer.currentContentView;
140         return currentContentView && typeof currentContentView.handleCopyEvent === "function" ? currentContentView.handleCopyEvent.bind(currentContentView) : null;
141     }
142
143     get supportsSave()
144     {
145         var currentContentView = this._contentViewContainer.currentContentView;
146         return currentContentView && currentContentView.supportsSave;
147     }
148
149     get saveData()
150     {
151         var currentContentView = this._contentViewContainer.currentContentView;
152         return currentContentView && currentContentView.saveData || null;
153     }
154
155     get currentTimelineView()
156     {
157         var contentView = this._contentViewContainer.currentContentView;
158         return (contentView instanceof WebInspector.TimelineView) ? contentView : null;
159     }
160
161     shown()
162     {
163         this._currentTimelineOverview.shown();
164         this._contentViewContainer.shown();
165         this._clearTimelineNavigationItem.enabled = this._recording.isWritable();
166
167         this._currentContentViewDidChange();
168
169         if (!this._updating && WebInspector.timelineManager.activeRecording === this._recording && WebInspector.timelineManager.isCapturing())
170             this._startUpdatingCurrentTime();
171     }
172
173     hidden()
174     {
175         this._currentTimelineOverview.hidden();
176         this._contentViewContainer.hidden();
177
178         if (this._updating)
179             this._stopUpdatingCurrentTime();
180     }
181
182     closed()
183     {
184         this._contentViewContainer.closeAllContentViews();
185
186         this._recording.removeEventListener(null, null, this);
187
188         WebInspector.timelineManager.removeEventListener(null, null, this);
189         WebInspector.debuggerManager.removeEventListener(null, null, this);
190         WebInspector.ContentView.removeEventListener(null, null, this);
191     }
192
193     canGoBack()
194     {
195         return this._contentViewContainer.canGoBack();
196     }
197
198     canGoForward()
199     {
200         return this._contentViewContainer.canGoForward();
201     }
202
203     goBack()
204     {
205         this._contentViewContainer.goBack();
206     }
207
208     goForward()
209     {
210         this._contentViewContainer.goForward();
211     }
212
213     updateLayout()
214     {
215         this._currentTimelineOverview.updateLayoutForResize();
216         this._contentViewContainer.updateLayout();
217     }
218
219     saveToCookie(cookie)
220     {
221         cookie.type = WebInspector.ContentViewCookieType.Timelines;
222
223         var currentContentView = this._contentViewContainer.currentContentView;
224         if (!currentContentView || currentContentView === this._overviewTimelineView)
225             cookie[WebInspector.TimelineRecordingContentView.SelectedTimelineTypeCookieKey] = WebInspector.TimelineRecordingContentView.OverviewTimelineViewCookieValue;
226         else if (currentContentView.representedObject instanceof WebInspector.Timeline)
227             cookie[WebInspector.TimelineRecordingContentView.SelectedTimelineTypeCookieKey] = this.currentTimelineView.representedObject.type;
228     }
229
230     restoreFromCookie(cookie)
231     {
232         var timelineType = cookie[WebInspector.TimelineRecordingContentView.SelectedTimelineTypeCookieKey];
233         if (timelineType === WebInspector.TimelineRecordingContentView.OverviewTimelineViewCookieValue)
234             this.showOverviewTimelineView();
235         else
236             this.showTimelineViewForTimeline(this.representedObject.timelines.get(timelineType));
237     }
238
239     filterDidChange()
240     {
241         if (!this.currentTimelineView)
242             return;
243
244         this.currentTimelineView.filterDidChange();
245     }
246
247     recordWasFiltered(record, filtered)
248     {
249         if (!this.currentTimelineView)
250             return;
251
252         this._currentTimelineOverview.recordWasFiltered(this.currentTimelineView.representedObject, record, filtered);
253     }
254
255     matchTreeElementAgainstCustomFilters(treeElement)
256     {
257         if (this.currentTimelineView && !this.currentTimelineView.matchTreeElementAgainstCustomFilters(treeElement))
258             return false;
259
260         var startTime = this._currentTimelineOverview.selectionStartTime;
261         var endTime = startTime + this._currentTimelineOverview.selectionDuration;
262         var currentTime = this._currentTime || this._recording.startTime;
263
264         if (this._timelineSidebarPanel.viewMode === WebInspector.TimelineSidebarPanel.ViewMode.RenderingFrames) {
265             console.assert(this._renderingFrameTimeline);
266
267             if (this._renderingFrameTimeline && this._renderingFrameTimeline.records.length) {
268                 var records = this._renderingFrameTimeline.records;
269                 var startIndex = this._currentTimelineOverview.timelineRuler.snapInterval ? startTime : Math.floor(startTime);
270                 if (startIndex >= records.length)
271                     return false;
272
273                 var endIndex = this._currentTimelineOverview.timelineRuler.snapInterval ? endTime - 1: Math.floor(endTime);
274                 endIndex = Math.min(endIndex, records.length - 1);
275                 console.assert(startIndex <= endIndex, startIndex);
276
277                 startTime = records[startIndex].startTime;
278                 endTime = records[endIndex].endTime;
279             }
280         }
281
282         function checkTimeBounds(itemStartTime, itemEndTime)
283         {
284             itemStartTime = itemStartTime || currentTime;
285             itemEndTime = itemEndTime || currentTime;
286
287             return startTime <= itemEndTime && itemStartTime <= endTime;
288         }
289
290         if (treeElement instanceof WebInspector.ResourceTreeElement) {
291             var resource = treeElement.resource;
292             return checkTimeBounds(resource.requestSentTimestamp, resource.finishedOrFailedTimestamp);
293         }
294
295         if (treeElement instanceof WebInspector.SourceCodeTimelineTreeElement) {
296             var sourceCodeTimeline = treeElement.sourceCodeTimeline;
297
298             // Do a quick check of the timeline bounds before we check each record.
299             if (!checkTimeBounds(sourceCodeTimeline.startTime, sourceCodeTimeline.endTime))
300                 return false;
301
302             for (var record of sourceCodeTimeline.records) {
303                 if (checkTimeBounds(record.startTime, record.endTime))
304                     return true;
305             }
306
307             return false;
308         }
309
310         if (treeElement instanceof WebInspector.ProfileNodeTreeElement) {
311             var profileNode = treeElement.profileNode;
312             if (checkTimeBounds(profileNode.startTime, profileNode.endTime))
313                 return true;
314
315             return false;
316         }
317
318         if (treeElement instanceof WebInspector.TimelineRecordTreeElement) {
319             var record = treeElement.record;
320             return checkTimeBounds(record.startTime, record.endTime);
321         }
322
323         console.error("Unknown TreeElement, can't filter by time.");
324         return true;
325     }
326
327     // Private
328
329     _currentContentViewDidChange(event)
330     {
331         let newTimelineOverview;
332         let timelineView = this.currentTimelineView;
333         if (timelineView && timelineView.representedObject.type === WebInspector.TimelineRecord.Type.RenderingFrame)
334             newTimelineOverview = this._renderingFrameTimelineOverview;
335         else
336             newTimelineOverview = this._linearTimelineOverview;
337
338         if (newTimelineOverview !== this._currentTimelineOverview) {
339             this._currentTimelineOverview.hidden();
340
341             this.element.insertBefore(newTimelineOverview.element, this._currentTimelineOverview.element);
342             this.element.removeChild(this._currentTimelineOverview.element);
343
344             this._currentTimelineOverview = newTimelineOverview;
345             this._currentTimelineOverview.shown();
346
347             this._updateTimelineOverviewHeight();
348         }
349
350         if (timelineView) {
351             this._timelineSidebarPanel.contentTreeOutline = timelineView.navigationSidebarTreeOutline;
352             this._timelineSidebarPanel.contentTreeOutlineLabel = timelineView.navigationSidebarTreeOutlineLabel;
353             this._timelineSidebarPanel.contentTreeOutlineScopeBar = timelineView.navigationSidebarTreeOutlineScopeBar;
354
355             timelineView.startTime = newTimelineOverview.selectionStartTime;
356             timelineView.endTime = newTimelineOverview.selectionStartTime + newTimelineOverview.selectionDuration;
357             timelineView.currentTime = this._currentTime;
358         }
359
360         this.dispatchEventToListeners(WebInspector.ContentView.Event.SelectionPathComponentsDidChange);
361         this.dispatchEventToListeners(WebInspector.ContentView.Event.NavigationItemsDidChange);
362     }
363
364     _pathComponentSelected(event)
365     {
366         this._timelineSidebarPanel.showTimelineViewForTimeline(event.data.pathComponent.representedObject);
367     }
368
369     _contentViewSelectionPathComponentDidChange(event)
370     {
371         if (event.target !== this._contentViewContainer.currentContentView)
372             return;
373
374         this.dispatchEventToListeners(WebInspector.ContentView.Event.SelectionPathComponentsDidChange);
375
376         if (this.currentTimelineView === this._overviewTimelineView)
377             return;
378
379         var recordPathComponent = this.selectionPathComponents.find(function(element) { return element.representedObject instanceof WebInspector.TimelineRecord; });
380         var record = recordPathComponent ? recordPathComponent.representedObject : null;
381         this._currentTimelineOverview.selectRecord(event.target.representedObject, record);
382     }
383
384     _contentViewSupplementalRepresentedObjectsDidChange(event)
385     {
386         if (event.target !== this._contentViewContainer.currentContentView)
387             return;
388         this.dispatchEventToListeners(WebInspector.ContentView.Event.SupplementalRepresentedObjectsDidChange);
389     }
390
391     _update(timestamp)
392     {
393         if (this._waitingToResetCurrentTime) {
394             requestAnimationFrame(this._updateCallback);
395             return;
396         }
397
398         var startTime = this._recording.startTime;
399         var currentTime = this._currentTime || startTime;
400         var endTime = this._recording.endTime;
401         var timespanSinceLastUpdate = (timestamp - this._lastUpdateTimestamp) / 1000 || 0;
402
403         currentTime += timespanSinceLastUpdate;
404
405         this._updateTimes(startTime, currentTime, endTime);
406
407         // Only stop updating if the current time is greater than the end time, or the end time is NaN.
408         // The recording end time will be NaN if no records were added.
409         if (!this._updating && (currentTime >= endTime || isNaN(endTime))) {
410             this._lastUpdateTimestamp = NaN;
411             return;
412         }
413
414         this._lastUpdateTimestamp = timestamp;
415
416         requestAnimationFrame(this._updateCallback);
417     }
418
419     _updateTimes(startTime, currentTime, endTime)
420     {
421         if (this._startTimeNeedsReset && !isNaN(startTime)) {
422             var selectionOffset = this._linearTimelineOverview.selectionStartTime - this._linearTimelineOverview.startTime;
423
424             this._linearTimelineOverview.startTime = startTime;
425             this._linearTimelineOverview.selectionStartTime = startTime + selectionOffset;
426
427             this._overviewTimelineView.zeroTime = startTime;
428             for (var timelineView of this._timelineViewMap.values())
429                 timelineView.zeroTime = startTime;
430
431             this._startTimeNeedsReset = false;
432         }
433
434         this._linearTimelineOverview.endTime = Math.max(endTime, currentTime);
435
436         this._currentTime = currentTime;
437         this._linearTimelineOverview.currentTime = currentTime;
438         if (this.currentTimelineView)
439             this.currentTimelineView.currentTime = currentTime;
440
441         if (this._renderingFrameTimeline) {
442             var currentFrameNumber = 0;
443             if (this._renderingFrameTimeline.records.length)
444                 currentFrameNumber = this._renderingFrameTimeline.records.lastValue.frameNumber;
445
446             this._renderingFrameTimelineOverview.currentTime = this._renderingFrameTimelineOverview.endTime = currentFrameNumber;
447         }
448
449         this._timelineSidebarPanel.updateFilter();
450
451         // Force a layout now since we are already in an animation frame and don't need to delay it until the next.
452         this._currentTimelineOverview.updateLayoutIfNeeded();
453         if (this.currentTimelineView)
454             this.currentTimelineView.updateLayoutIfNeeded();
455     }
456
457     _startUpdatingCurrentTime(startTime)
458     {
459         console.assert(!this._updating);
460         if (this._updating)
461             return;
462
463         if (typeof startTime === "number")
464             this._currentTime = startTime;
465         else if (!isNaN(this._currentTime)) {
466             // This happens when you stop and later restart recording.
467             // COMPATIBILITY (iOS 9): Timeline.recordingStarted events did not include a timestamp.
468             // We likely need to jump into the future to a better current time which we can
469             // ascertained from a new incoming timeline record, so we wait for a Timeline to update.
470             console.assert(!this._waitingToResetCurrentTime);
471             this._waitingToResetCurrentTime = true;
472             this._recording.addEventListener(WebInspector.TimelineRecording.Event.TimesUpdated, this._recordingTimesUpdated, this);
473         }
474
475         this._updating = true;
476
477         if (!this._updateCallback)
478             this._updateCallback = this._update.bind(this);
479
480         requestAnimationFrame(this._updateCallback);
481     }
482
483     _stopUpdatingCurrentTime()
484     {
485         console.assert(this._updating);
486         this._updating = false;
487
488         if (this._waitingToResetCurrentTime) {
489             // Did not get any event while waiting for the current time, but we should stop waiting.
490             this._recording.removeEventListener(WebInspector.TimelineRecording.Event.TimesUpdated, this._recordingTimesUpdated, this);
491             this._waitingToResetCurrentTime = false;
492         }
493     }
494
495     _capturingStarted(event)
496     {
497         if (!this._updating)
498             this._startUpdatingCurrentTime(event.data.startTime);
499     }
500
501     _capturingStopped(event)
502     {
503         if (this._updating)
504             this._stopUpdatingCurrentTime();
505     }
506
507     _debuggerPaused(event)
508     {
509         if (WebInspector.replayManager.sessionState === WebInspector.ReplayManager.SessionState.Replaying)
510             return;
511
512         if (this._updating)
513             this._stopUpdatingCurrentTime();
514     }
515
516     _debuggerResumed(event)
517     {
518         if (WebInspector.replayManager.sessionState === WebInspector.ReplayManager.SessionState.Replaying)
519             return;
520
521         if (!this._updating)
522             this._startUpdatingCurrentTime();
523     }
524
525     _recordingTimesUpdated(event)
526     {
527         if (!this._waitingToResetCurrentTime)
528             return;
529
530         // COMPATIBILITY (iOS 9): Timeline.recordingStarted events did not include a new startTime.
531         // Make the current time be the start time of the last added record. This is the best way
532         // currently to jump to the right period of time after recording starts.
533
534         for (var timeline of this._recording.timelines.values()) {
535             var lastRecord = timeline.records.lastValue;
536             if (!lastRecord)
537                 continue;
538             this._currentTime = Math.max(this._currentTime, lastRecord.startTime);
539         }
540
541         this._recording.removeEventListener(WebInspector.TimelineRecording.Event.TimesUpdated, this._recordingTimesUpdated, this);
542         this._waitingToResetCurrentTime = false;
543     }
544
545     _clearTimeline(event)
546     {
547         if (WebInspector.timelineManager.activeRecording === this._recording && WebInspector.timelineManager.isCapturing())
548             WebInspector.timelineManager.stopCapturing();
549
550         this._recording.reset();
551     }
552
553     _updateTimelineOverviewHeight()
554     {
555         const timelineHeight = 36;
556         const renderingFramesTimelineHeight = 108;
557         const rulerHeight = 29;
558
559         var overviewHeight;
560
561         if (this.currentTimelineView && this.currentTimelineView.representedObject.type === WebInspector.TimelineRecord.Type.RenderingFrame)
562             overviewHeight = renderingFramesTimelineHeight;
563         else {
564             var timelineCount = this._timelineViewMap.size;
565             if (this._renderingFrameTimeline)
566                 timelineCount--;
567
568             overviewHeight = timelineCount * timelineHeight;
569         }
570
571         var styleValue = (rulerHeight + overviewHeight) + "px";
572         this._currentTimelineOverview.element.style.height = styleValue;
573         this._contentViewContainer.element.style.top = styleValue;
574     }
575
576     _timelineAdded(timelineOrEvent)
577     {
578         var timeline = timelineOrEvent;
579         if (!(timeline instanceof WebInspector.Timeline))
580             timeline = timelineOrEvent.data.timeline;
581
582         console.assert(timeline instanceof WebInspector.Timeline, timeline);
583         console.assert(!this._timelineViewMap.has(timeline), timeline);
584
585         this._timelineViewMap.set(timeline, WebInspector.ContentView.createFromRepresentedObject(timeline, {timelineSidebarPanel: this._timelineSidebarPanel}));
586         if (timeline.type === WebInspector.TimelineRecord.Type.RenderingFrame)
587             this._renderingFrameTimeline = timeline;
588
589         var pathComponent = new WebInspector.HierarchicalPathComponent(timeline.displayName, timeline.iconClassName, timeline);
590         pathComponent.addEventListener(WebInspector.HierarchicalPathComponent.Event.SiblingWasSelected, this._pathComponentSelected, this);
591         this._pathComponentMap.set(timeline, pathComponent);
592
593         this._timelineCountChanged();
594     }
595
596     _timelineRemoved(event)
597     {
598         var timeline = event.data.timeline;
599         console.assert(timeline instanceof WebInspector.Timeline, timeline);
600         console.assert(this._timelineViewMap.has(timeline), timeline);
601
602         var timelineView = this._timelineViewMap.take(timeline);
603         if (this.currentTimelineView === timelineView)
604             this.showOverviewTimelineView();
605         if (timeline.type === WebInspector.TimelineRecord.Type.RenderingFrame)
606             this._renderingFrameTimeline = null;
607
608         this._pathComponentMap.delete(timeline);
609
610         this._timelineCountChanged();
611     }
612
613     _timelineCountChanged()
614     {
615         var previousPathComponent = null;
616         for (var pathComponent of this._pathComponentMap.values()) {
617             if (previousPathComponent) {
618                 previousPathComponent.nextSibling = pathComponent;
619                 pathComponent.previousSibling = previousPathComponent;
620             }
621
622             previousPathComponent = pathComponent;
623         }
624
625         this._updateTimelineOverviewHeight();
626     }
627
628     _recordingReset(event)
629     {
630         this._currentTime = NaN;
631
632         if (!this._updating) {
633             // Force the time ruler and views to reset to 0.
634             this._startTimeNeedsReset = true;
635             this._updateTimes(0, 0, 0);
636         }
637
638         this._lastUpdateTimestamp = NaN;
639         this._startTimeNeedsReset = true;
640
641         this._recording.removeEventListener(WebInspector.TimelineRecording.Event.TimesUpdated, this._recordingTimesUpdated, this);
642         this._waitingToResetCurrentTime = false;
643
644         this._linearTimelineOverview.reset();
645         this._renderingFrameTimelineOverview.reset();
646         this._overviewTimelineView.reset();
647         for (var timelineView of this._timelineViewMap.values())
648             timelineView.reset();
649     }
650
651     _recordingUnloaded(event)
652     {
653         console.assert(!this._updating);
654
655         WebInspector.timelineManager.removeEventListener(WebInspector.TimelineManager.Event.CapturingStarted, this._capturingStarted, this);
656         WebInspector.timelineManager.removeEventListener(WebInspector.TimelineManager.Event.CapturingStopped, this._capturingStopped, this);
657     }
658
659     _timeRangeSelectionChanged(event)
660     {
661         if (this.currentTimelineView) {
662             this.currentTimelineView.startTime = this._currentTimelineOverview.selectionStartTime;
663             this.currentTimelineView.endTime = this._currentTimelineOverview.selectionStartTime + this._currentTimelineOverview.selectionDuration;
664
665             if (this.currentTimelineView.representedObject.type === WebInspector.TimelineRecord.Type.RenderingFrame)
666                 this._updateFrameSelection();
667         }
668
669         // Delay until the next frame to stay in sync with the current timeline view's time-based layout changes.
670         requestAnimationFrame(function() {
671             var selectedTreeElement = this.currentTimelineView && this.currentTimelineView.navigationSidebarTreeOutline ? this.currentTimelineView.navigationSidebarTreeOutline.selectedTreeElement : null;
672             var selectionWasHidden = selectedTreeElement && selectedTreeElement.hidden;
673
674             this._timelineSidebarPanel.updateFilter();
675
676             if (selectedTreeElement && selectedTreeElement.hidden !== selectionWasHidden)
677                 this.dispatchEventToListeners(WebInspector.ContentView.Event.SelectionPathComponentsDidChange);
678         }.bind(this));
679     }
680
681     _recordSelected(event)
682     {
683         var timelineView = this._timelineViewMap.get(event.data.timeline);
684         console.assert(timelineView === this.currentTimelineView, timelineView);
685         if (timelineView !== this.currentTimelineView)
686             return;
687
688         var selectedTreeElement = this.currentTimelineView.navigationSidebarTreeOutline.selectedTreeElement;
689         if (!event.data.record) {
690             if (selectedTreeElement)
691                 selectedTreeElement.deselect();
692             return;
693         }
694
695         var treeElement = this.currentTimelineView.navigationSidebarTreeOutline.findTreeElement(event.data.record);
696         console.assert(treeElement, "Timeline view has no tree element for record selected in timeline overview.", timelineView, event.data.record);
697         if (!treeElement || treeElement.selected)
698             return;
699
700         // Don't select the record's tree element if one of it's children is already selected.
701         if (selectedTreeElement && selectedTreeElement.hasAncestor(treeElement))
702             return;
703
704         treeElement.revealAndSelect(false, false, false, true);
705     }
706
707     _updateFrameSelection()
708     {
709         console.assert(this._renderingFrameTimeline);
710         if (!this._renderingFrameTimeline)
711             return;
712
713         var startIndex = this._renderingFrameTimelineOverview.selectionStartTime;
714         var endIndex = startIndex + this._renderingFrameTimelineOverview.selectionDuration - 1;
715         this._timelineSidebarPanel.updateFrameSelection(startIndex, endIndex);
716     }
717 };
718
719 WebInspector.TimelineRecordingContentView.SelectedTimelineTypeCookieKey = "timeline-recording-content-view-selected-timeline-type";
720 WebInspector.TimelineRecordingContentView.OverviewTimelineViewCookieValue = "timeline-recording-content-view-overview-timeline-view";