Cleaning up formatting on recent additions win TestExpectations file
[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 (!isNaN(this._currentTime)) {
464             // This happens when you stop and later restart recording.
465             if (typeof startTime === "number")
466                 this._currentTime = startTime;
467             else {
468                 // COMPATIBILITY (iOS 9): Timeline.recordingStarted events did not include a timestamp.
469                 // We likely need to jump into the future to a better current time which we can
470                 // ascertained from a new incoming timeline record, so we wait for a Timeline to update.
471                 console.assert(!this._waitingToResetCurrentTime);
472                 this._waitingToResetCurrentTime = true;
473                 this._recording.addEventListener(WebInspector.TimelineRecording.Event.TimesUpdated, this._recordingTimesUpdated, this);
474             }
475         }
476
477         this._updating = true;
478
479         if (!this._updateCallback)
480             this._updateCallback = this._update.bind(this);
481
482         requestAnimationFrame(this._updateCallback);
483     }
484
485     _stopUpdatingCurrentTime()
486     {
487         console.assert(this._updating);
488         this._updating = false;
489
490         if (this._waitingToResetCurrentTime) {
491             // Did not get any event while waiting for the current time, but we should stop waiting.
492             this._recording.removeEventListener(WebInspector.TimelineRecording.Event.TimesUpdated, this._recordingTimesUpdated, this);
493             this._waitingToResetCurrentTime = false;
494         }
495     }
496
497     _capturingStarted(event)
498     {
499         if (!this._updating)
500             this._startUpdatingCurrentTime(event.data.startTime);
501     }
502
503     _capturingStopped(event)
504     {
505         if (this._updating)
506             this._stopUpdatingCurrentTime();
507     }
508
509     _debuggerPaused(event)
510     {
511         if (WebInspector.replayManager.sessionState === WebInspector.ReplayManager.SessionState.Replaying)
512             return;
513
514         if (this._updating)
515             this._stopUpdatingCurrentTime();
516     }
517
518     _debuggerResumed(event)
519     {
520         if (WebInspector.replayManager.sessionState === WebInspector.ReplayManager.SessionState.Replaying)
521             return;
522
523         if (!this._updating)
524             this._startUpdatingCurrentTime();
525     }
526
527     _recordingTimesUpdated(event)
528     {
529         if (!this._waitingToResetCurrentTime)
530             return;
531
532         // COMPATIBILITY (iOS 9): Timeline.recordingStarted events did not include a new startTime.
533         // Make the current time be the start time of the last added record. This is the best way
534         // currently to jump to the right period of time after recording starts.
535
536         for (var timeline of this._recording.timelines.values()) {
537             var lastRecord = timeline.records.lastValue;
538             if (!lastRecord)
539                 continue;
540             this._currentTime = Math.max(this._currentTime, lastRecord.startTime);
541         }
542
543         this._recording.removeEventListener(WebInspector.TimelineRecording.Event.TimesUpdated, this._recordingTimesUpdated, this);
544         this._waitingToResetCurrentTime = false;
545     }
546
547     _clearTimeline(event)
548     {
549         if (WebInspector.timelineManager.activeRecording === this._recording && WebInspector.timelineManager.isCapturing())
550             WebInspector.timelineManager.stopCapturing();
551
552         this._recording.reset();
553     }
554
555     _updateTimelineOverviewHeight()
556     {
557         const timelineHeight = 36;
558         const renderingFramesTimelineHeight = 108;
559         const rulerHeight = 29;
560
561         var overviewHeight;
562
563         if (this.currentTimelineView && this.currentTimelineView.representedObject.type === WebInspector.TimelineRecord.Type.RenderingFrame)
564             overviewHeight = renderingFramesTimelineHeight;
565         else {
566             var timelineCount = this._timelineViewMap.size;
567             if (this._renderingFrameTimeline)
568                 timelineCount--;
569
570             overviewHeight = timelineCount * timelineHeight;
571         }
572
573         var styleValue = (rulerHeight + overviewHeight) + "px";
574         this._currentTimelineOverview.element.style.height = styleValue;
575         this._contentViewContainer.element.style.top = styleValue;
576     }
577
578     _timelineAdded(timelineOrEvent)
579     {
580         var timeline = timelineOrEvent;
581         if (!(timeline instanceof WebInspector.Timeline))
582             timeline = timelineOrEvent.data.timeline;
583
584         console.assert(timeline instanceof WebInspector.Timeline, timeline);
585         console.assert(!this._timelineViewMap.has(timeline), timeline);
586
587         this._timelineViewMap.set(timeline, WebInspector.ContentView.createFromRepresentedObject(timeline, {timelineSidebarPanel: this._timelineSidebarPanel}));
588         if (timeline.type === WebInspector.TimelineRecord.Type.RenderingFrame)
589             this._renderingFrameTimeline = timeline;
590
591         var pathComponent = new WebInspector.HierarchicalPathComponent(timeline.displayName, timeline.iconClassName, timeline);
592         pathComponent.addEventListener(WebInspector.HierarchicalPathComponent.Event.SiblingWasSelected, this._pathComponentSelected, this);
593         this._pathComponentMap.set(timeline, pathComponent);
594
595         this._timelineCountChanged();
596     }
597
598     _timelineRemoved(event)
599     {
600         var timeline = event.data.timeline;
601         console.assert(timeline instanceof WebInspector.Timeline, timeline);
602         console.assert(this._timelineViewMap.has(timeline), timeline);
603
604         var timelineView = this._timelineViewMap.take(timeline);
605         if (this.currentTimelineView === timelineView)
606             this.showOverviewTimelineView();
607         if (timeline.type === WebInspector.TimelineRecord.Type.RenderingFrame)
608             this._renderingFrameTimeline = null;
609
610         this._pathComponentMap.delete(timeline);
611
612         this._timelineCountChanged();
613     }
614
615     _timelineCountChanged()
616     {
617         var previousPathComponent = null;
618         for (var pathComponent of this._pathComponentMap.values()) {
619             if (previousPathComponent) {
620                 previousPathComponent.nextSibling = pathComponent;
621                 pathComponent.previousSibling = previousPathComponent;
622             }
623
624             previousPathComponent = pathComponent;
625         }
626
627         this._updateTimelineOverviewHeight();
628     }
629
630     _recordingReset(event)
631     {
632         this._currentTime = NaN;
633
634         if (!this._updating) {
635             // Force the time ruler and views to reset to 0.
636             this._startTimeNeedsReset = true;
637             this._updateTimes(0, 0, 0);
638         }
639
640         this._lastUpdateTimestamp = NaN;
641         this._startTimeNeedsReset = true;
642
643         this._recording.removeEventListener(WebInspector.TimelineRecording.Event.TimesUpdated, this._recordingTimesUpdated, this);
644         this._waitingToResetCurrentTime = false;
645
646         this._linearTimelineOverview.reset();
647         this._renderingFrameTimelineOverview.reset();
648         this._overviewTimelineView.reset();
649         for (var timelineView of this._timelineViewMap.values())
650             timelineView.reset();
651     }
652
653     _recordingUnloaded(event)
654     {
655         console.assert(!this._updating);
656
657         WebInspector.timelineManager.removeEventListener(WebInspector.TimelineManager.Event.CapturingStarted, this._capturingStarted, this);
658         WebInspector.timelineManager.removeEventListener(WebInspector.TimelineManager.Event.CapturingStopped, this._capturingStopped, this);
659     }
660
661     _timeRangeSelectionChanged(event)
662     {
663         if (this.currentTimelineView) {
664             this.currentTimelineView.startTime = this._currentTimelineOverview.selectionStartTime;
665             this.currentTimelineView.endTime = this._currentTimelineOverview.selectionStartTime + this._currentTimelineOverview.selectionDuration;
666
667             if (this.currentTimelineView.representedObject.type === WebInspector.TimelineRecord.Type.RenderingFrame)
668                 this._updateFrameSelection();
669         }
670
671         // Delay until the next frame to stay in sync with the current timeline view's time-based layout changes.
672         requestAnimationFrame(function() {
673             var selectedTreeElement = this.currentTimelineView && this.currentTimelineView.navigationSidebarTreeOutline ? this.currentTimelineView.navigationSidebarTreeOutline.selectedTreeElement : null;
674             var selectionWasHidden = selectedTreeElement && selectedTreeElement.hidden;
675
676             this._timelineSidebarPanel.updateFilter();
677
678             if (selectedTreeElement && selectedTreeElement.hidden !== selectionWasHidden)
679                 this.dispatchEventToListeners(WebInspector.ContentView.Event.SelectionPathComponentsDidChange);
680         }.bind(this));
681     }
682
683     _recordSelected(event)
684     {
685         var timelineView = this._timelineViewMap.get(event.data.timeline);
686         console.assert(timelineView === this.currentTimelineView, timelineView);
687         if (timelineView !== this.currentTimelineView)
688             return;
689
690         var selectedTreeElement = this.currentTimelineView.navigationSidebarTreeOutline.selectedTreeElement;
691         if (!event.data.record) {
692             if (selectedTreeElement)
693                 selectedTreeElement.deselect();
694             return;
695         }
696
697         var treeElement = this.currentTimelineView.navigationSidebarTreeOutline.findTreeElement(event.data.record);
698         console.assert(treeElement, "Timeline view has no tree element for record selected in timeline overview.", timelineView, event.data.record);
699         if (!treeElement || treeElement.selected)
700             return;
701
702         // Don't select the record's tree element if one of it's children is already selected.
703         if (selectedTreeElement && selectedTreeElement.hasAncestor(treeElement))
704             return;
705
706         treeElement.revealAndSelect(false, false, false, true);
707     }
708
709     _updateFrameSelection()
710     {
711         console.assert(this._renderingFrameTimeline);
712         if (!this._renderingFrameTimeline)
713             return;
714
715         var startIndex = this._renderingFrameTimelineOverview.selectionStartTime;
716         var endIndex = startIndex + this._renderingFrameTimelineOverview.selectionDuration - 1;
717         this._timelineSidebarPanel.updateFrameSelection(startIndex, endIndex);
718     }
719 };
720
721 WebInspector.TimelineRecordingContentView.SelectedTimelineTypeCookieKey = "timeline-recording-content-view-selected-timeline-type";
722 WebInspector.TimelineRecordingContentView.OverviewTimelineViewCookieValue = "timeline-recording-content-view-overview-timeline-view";