Web Inspector: Clicking a frame in the Rendering Frames timeline should select the...
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / TimelineRecordingContentView.js
1 /*
2  * Copyright (C) 2013 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 = function(recording, extraArguments)
28 {
29     console.assert(extraArguments);
30     console.assert(extraArguments.timelineSidebarPanel instanceof WebInspector.TimelineSidebarPanel);
31
32     WebInspector.ContentView.call(this, recording);
33
34     this._recording = recording;
35     this._timelineSidebarPanel = extraArguments.timelineSidebarPanel;
36
37     this.element.classList.add("timeline-recording");
38
39     this._linearTimelineOverview = new WebInspector.LinearTimelineOverview(this._recording);
40     this._linearTimelineOverview.addEventListener(WebInspector.TimelineOverview.Event.TimeRangeSelectionChanged, this._timeRangeSelectionChanged, this);
41
42     this._renderingFrameTimelineOverview = new WebInspector.RenderingFrameTimelineOverview(this._recording);
43     this._renderingFrameTimelineOverview.addEventListener(WebInspector.TimelineOverview.Event.TimeRangeSelectionChanged, this._timeRangeSelectionChanged, this);
44     this._renderingFrameTimelineOverview.addEventListener(WebInspector.TimelineOverview.Event.RecordSelected, this._recordSelected, this);
45
46     this._currentTimelineOverview = this._linearTimelineOverview;
47     this.element.appendChild(this._currentTimelineOverview.element);
48
49     this._contentViewContainer = new WebInspector.ContentViewContainer();
50     this._contentViewContainer.addEventListener(WebInspector.ContentViewContainer.Event.CurrentContentViewDidChange, this._currentContentViewDidChange, this);
51     this.element.appendChild(this._contentViewContainer.element);
52
53     this._clearTimelineNavigationItem = new WebInspector.ButtonNavigationItem("clear-timeline", WebInspector.UIString("Clear Timeline"), "Images/NavigationItemTrash.svg", 15, 15);
54     this._clearTimelineNavigationItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._clearTimeline, this);
55
56     this._overviewTimelineView = new WebInspector.OverviewTimelineView(recording, {timelineSidebarPanel: this._timelineSidebarPanel});
57     this._overviewTimelineView.secondsPerPixel = this._linearTimelineOverview.secondsPerPixel;
58
59     this._timelineViewMap = new Map;
60     this._pathComponentMap = new Map;
61
62     this._updating = false;
63     this._currentTime = NaN;
64     this._lastUpdateTimestamp = NaN;
65     this._startTimeNeedsReset = true;
66     this._renderingFrameTimeline = null;
67
68     this._recording.addEventListener(WebInspector.TimelineRecording.Event.TimelineAdded, this._timelineAdded, this);
69     this._recording.addEventListener(WebInspector.TimelineRecording.Event.TimelineRemoved, this._timelineRemoved, this);
70     this._recording.addEventListener(WebInspector.TimelineRecording.Event.Reset, this._recordingReset, this);
71     this._recording.addEventListener(WebInspector.TimelineRecording.Event.Unloaded, this._recordingUnloaded, this);
72
73     WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.CapturingStarted, this._capturingStarted, this);
74     WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.CapturingStopped, this._capturingStopped, this);
75
76     WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.Paused, this._debuggerPaused, this);
77     WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.Resumed, this._debuggerResumed, this);
78
79     WebInspector.ContentView.addEventListener(WebInspector.ContentView.Event.SelectionPathComponentsDidChange, this._contentViewSelectionPathComponentDidChange, this);
80     WebInspector.ContentView.addEventListener(WebInspector.ContentView.Event.SupplementalRepresentedObjectsDidChange, this._contentViewSupplementalRepresentedObjectsDidChange, this);
81
82     for (var timeline of this._recording.timelines.values())
83         this._timelineAdded(timeline);
84
85     this.showOverviewTimelineView();
86 };
87
88 WebInspector.TimelineRecordingContentView.SelectedTimelineTypeCookieKey = "timeline-recording-content-view-selected-timeline-type";
89 WebInspector.TimelineRecordingContentView.OverviewTimelineViewCookieValue = "timeline-recording-content-view-overview-timeline-view";
90
91 WebInspector.TimelineRecordingContentView.prototype = {
92     constructor: WebInspector.TimelineRecordingContentView,
93     __proto__: WebInspector.ContentView.prototype,
94
95     // Public
96
97     showOverviewTimelineView: function()
98     {
99         this._contentViewContainer.showContentView(this._overviewTimelineView);
100     },
101
102     showTimelineViewForTimeline: function(timeline)
103     {
104         console.assert(timeline instanceof WebInspector.Timeline, timeline);
105         console.assert(this._timelineViewMap.has(timeline), timeline);
106         if (!this._timelineViewMap.has(timeline))
107             return;
108
109         this._contentViewContainer.showContentView(this._timelineViewMap.get(timeline));
110     },
111
112     get supportsSplitContentBrowser()
113     {
114         // The layout of the overview and split content browser don't work well.
115         return false;
116     },
117
118     get selectionPathComponents()
119     {
120         if (!this._contentViewContainer.currentContentView)
121             return [];
122
123         var pathComponents = this._contentViewContainer.currentContentView.selectionPathComponents || [];
124         var representedObject = this._contentViewContainer.currentContentView.representedObject;
125         if (representedObject instanceof WebInspector.Timeline)
126             pathComponents.unshift(this._pathComponentMap.get(representedObject));
127         return pathComponents;
128     },
129
130     get supplementalRepresentedObjects()
131     {
132         if (!this._contentViewContainer.currentContentView)
133             return [];
134         return this._contentViewContainer.currentContentView.supplementalRepresentedObjects;
135     },
136
137     get navigationItems()
138     {
139         return [this._clearTimelineNavigationItem];
140     },
141
142     get handleCopyEvent()
143     {
144         var currentContentView = this._contentViewContainer.currentContentView;
145         return currentContentView && typeof currentContentView.handleCopyEvent === "function" ? currentContentView.handleCopyEvent.bind(currentContentView) : null;
146     },
147
148     get supportsSave()
149     {
150         var currentContentView = this._contentViewContainer.currentContentView;
151         return currentContentView && currentContentView.supportsSave;
152     },
153
154     get saveData()
155     {
156         var currentContentView = this._contentViewContainer.currentContentView;
157         return currentContentView && currentContentView.saveData || null;
158     },
159
160     get currentTimelineView()
161     {
162         var contentView = this._contentViewContainer.currentContentView;
163         return (contentView instanceof WebInspector.TimelineView) ? contentView : null;
164     },
165
166     shown: function()
167     {
168         this._currentTimelineOverview.shown();
169         this._contentViewContainer.shown();
170         this._clearTimelineNavigationItem.enabled = this._recording.isWritable();
171
172         this._currentContentViewDidChange();
173
174         if (!this._updating && WebInspector.timelineManager.activeRecording === this._recording && WebInspector.timelineManager.isCapturing())
175             this._startUpdatingCurrentTime();
176     },
177
178     hidden: function()
179     {
180         this._currentTimelineOverview.hidden();
181         this._contentViewContainer.hidden();
182
183         if (this._updating)
184             this._stopUpdatingCurrentTime();
185     },
186
187     closed: function()
188     {
189         this._contentViewContainer.closeAllContentViews();
190
191         this._recording.removeEventListener(null, null, this);
192
193         WebInspector.timelineManager.removeEventListener(null, null, this);
194         WebInspector.debuggerManager.removeEventListener(null, null, this);
195         WebInspector.ContentView.removeEventListener(null, null, this);
196     },
197
198     canGoBack: function()
199     {
200         return this._contentViewContainer.canGoBack();
201     },
202
203     canGoForward: function()
204     {
205         return this._contentViewContainer.canGoForward();
206     },
207
208     goBack: function()
209     {
210         this._contentViewContainer.goBack();
211     },
212
213     goForward: function()
214     {
215         this._contentViewContainer.goForward();
216     },
217
218     updateLayout: function()
219     {
220         this._currentTimelineOverview.updateLayoutForResize();
221
222         var currentContentView = this._contentViewContainer.currentContentView;
223         if (currentContentView)
224             currentContentView.updateLayout();
225     },
226
227     saveToCookie: function(cookie)
228     {
229         cookie.type = WebInspector.ContentViewCookieType.Timelines;
230
231         var currentContentView = this._contentViewContainer.currentContentView;
232         if (!currentContentView || currentContentView === this._overviewTimelineView)
233             cookie[WebInspector.TimelineRecordingContentView.SelectedTimelineTypeCookieKey] = WebInspector.TimelineRecordingContentView.OverviewTimelineViewCookieValue;
234         else if (currentContentView.representedObject instanceof WebInspector.Timeline)
235             cookie[WebInspector.TimelineRecordingContentView.SelectedTimelineTypeCookieKey] = this.currentTimelineView.representedObject.type;
236     },
237
238     restoreFromCookie: function(cookie)
239     {
240         var timelineType = cookie[WebInspector.TimelineRecordingContentView.SelectedTimelineTypeCookieKey];
241         if (timelineType === WebInspector.TimelineRecordingContentView.OverviewTimelineViewCookieValue)
242             this.showOverviewTimelineView();
243         else
244             this.showTimelineViewForTimeline(this.representedObject.timelines.get(timelineType));
245     },
246
247     filterDidChange: function()
248     {
249         if (!this.currentTimelineView)
250             return;
251
252         this.currentTimelineView.filterDidChange();
253     },
254
255     matchTreeElementAgainstCustomFilters: function(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: function(event)
330     {
331         var newTimelineOverview = this._linearTimelineOverview;
332         var timelineView = this.currentTimelineView;
333         if (timelineView) {
334             this._timelineSidebarPanel.contentTreeOutline = timelineView.navigationSidebarTreeOutline;
335             this._timelineSidebarPanel.contentTreeOutlineLabel = timelineView.navigationSidebarTreeOutlineLabel;
336             this._timelineSidebarPanel.contentTreeOutlineScopeBar = timelineView.navigationSidebarTreeOutlineScopeBar;
337
338             if (timelineView.representedObject.type === WebInspector.TimelineRecord.Type.RenderingFrame)
339                 newTimelineOverview = this._renderingFrameTimelineOverview;
340
341             timelineView.startTime = newTimelineOverview.selectionStartTime;
342             timelineView.endTime = newTimelineOverview.selectionStartTime + newTimelineOverview.selectionDuration;
343             timelineView.currentTime = this._currentTime;
344         }
345
346         if (newTimelineOverview !== this._currentTimelineOverview) {
347             this._currentTimelineOverview.hidden();
348
349             this.element.insertBefore(newTimelineOverview.element, this._currentTimelineOverview.element);
350             this.element.removeChild(this._currentTimelineOverview.element);
351
352             this._currentTimelineOverview = newTimelineOverview;
353             this._currentTimelineOverview.shown();
354
355             this._updateTimelineOverviewHeight();
356         }
357
358         this.dispatchEventToListeners(WebInspector.ContentView.Event.SelectionPathComponentsDidChange);
359         this.dispatchEventToListeners(WebInspector.ContentView.Event.NavigationItemsDidChange);
360     },
361
362     _pathComponentSelected: function(event)
363     {
364         this._timelineSidebarPanel.showTimelineViewForTimeline(event.data.pathComponent.representedObject);
365     },
366
367     _contentViewSelectionPathComponentDidChange: function(event)
368     {
369         if (event.target !== this._contentViewContainer.currentContentView)
370             return;
371
372         this.dispatchEventToListeners(WebInspector.ContentView.Event.SelectionPathComponentsDidChange);
373
374         if (this.currentTimelineView === this._overviewTimelineView)
375             return;
376
377         var recordPathComponent = this.selectionPathComponents.find(function(element) { return element.representedObject instanceof WebInspector.TimelineRecord; });
378         var record = recordPathComponent ? recordPathComponent.representedObject : null;
379         this._currentTimelineOverview.selectRecord(event.target.representedObject, record);
380     },
381
382     _contentViewSupplementalRepresentedObjectsDidChange: function(event)
383     {
384         if (event.target !== this._contentViewContainer.currentContentView)
385             return;
386         this.dispatchEventToListeners(WebInspector.ContentView.Event.SupplementalRepresentedObjectsDidChange);
387     },
388
389     _update: function(timestamp)
390     {
391         if (this._waitingToResetCurrentTime) {
392             requestAnimationFrame(this._updateCallback);
393             return;
394         }
395
396         var startTime = this._recording.startTime;
397         var currentTime = this._currentTime || startTime;
398         var endTime = this._recording.endTime;
399         var timespanSinceLastUpdate = (timestamp - this._lastUpdateTimestamp) / 1000 || 0;
400
401         currentTime += timespanSinceLastUpdate;
402
403         this._updateTimes(startTime, currentTime, endTime);
404
405         // Only stop updating if the current time is greater than the end time.
406         if (!this._updating && currentTime >= endTime) {
407             this._lastUpdateTimestamp = NaN;
408             return;
409         }
410
411         this._lastUpdateTimestamp = timestamp;
412
413         requestAnimationFrame(this._updateCallback);
414     },
415
416     _updateTimes: function(startTime, currentTime, endTime)
417     {
418         if (this._startTimeNeedsReset && !isNaN(startTime)) {
419             var selectionOffset = this._linearTimelineOverview.selectionStartTime - this._linearTimelineOverview.startTime;
420
421             this._linearTimelineOverview.startTime = startTime;
422             this._linearTimelineOverview.selectionStartTime = startTime + selectionOffset;
423
424             this._overviewTimelineView.zeroTime = startTime;
425             for (var timelineView of this._timelineViewMap.values())
426                 timelineView.zeroTime = startTime;
427
428             this._startTimeNeedsReset = false;
429         }
430
431         this._linearTimelineOverview.endTime = Math.max(endTime, currentTime);
432
433         this._currentTime = currentTime;
434         this._linearTimelineOverview.currentTime = currentTime;
435         if (this.currentTimelineView)
436             this.currentTimelineView.currentTime = currentTime;
437
438         if (this._renderingFrameTimeline) {
439             var currentFrameNumber = 0;
440             if (this._renderingFrameTimeline.records.length)
441                 currentFrameNumber = this._renderingFrameTimeline.records.lastValue.frameNumber;
442
443             this._renderingFrameTimelineOverview.currentTime = this._renderingFrameTimelineOverview.endTime = currentFrameNumber;
444         }
445
446         this._timelineSidebarPanel.updateFilter();
447
448         // Force a layout now since we are already in an animation frame and don't need to delay it until the next.
449         this._currentTimelineOverview.updateLayoutIfNeeded();
450         if (this.currentTimelineView)
451             this.currentTimelineView.updateLayoutIfNeeded();
452     },
453
454     _startUpdatingCurrentTime: function(startTime)
455     {
456         console.assert(!this._updating);
457         if (this._updating)
458             return;
459
460         if (!isNaN(this._currentTime)) {
461             // This happens when you stop and later restart recording.
462             if (typeof startTime === "number")
463                 this._currentTime = startTime;
464             else {
465                 // COMPATIBILITY (iOS 9): Timeline.recordingStarted events did not include a timestamp.
466                 // We likely need to jump into the future to a better current time which we can
467                 // ascertained from a new incoming timeline record, so we wait for a Timeline to update.
468                 console.assert(!this._waitingToResetCurrentTime);
469                 this._waitingToResetCurrentTime = true;
470                 this._recording.addEventListener(WebInspector.TimelineRecording.Event.TimesUpdated, this._recordingTimesUpdated, this);
471             }
472         }
473
474         this._updating = true;
475
476         if (!this._updateCallback)
477             this._updateCallback = this._update.bind(this);
478
479         requestAnimationFrame(this._updateCallback);
480     },
481
482     _stopUpdatingCurrentTime: function()
483     {
484         console.assert(this._updating);
485         this._updating = false;
486
487         if (this._waitingToResetCurrentTime) {
488             // Did not get any event while waiting for the current time, but we should stop waiting.
489             this._recording.removeEventListener(WebInspector.TimelineRecording.Event.TimesUpdated, this._recordingTimesUpdated, this);
490             this._waitingToResetCurrentTime = false;
491         }
492     },
493
494     _capturingStarted: function(event)
495     {
496         this._startUpdatingCurrentTime(event.data.startTime);
497     },
498
499     _capturingStopped: function(event)
500     {
501         if (this._updating)
502             this._stopUpdatingCurrentTime();
503     },
504
505     _debuggerPaused: function(event)
506     {
507         if (WebInspector.replayManager.sessionState === WebInspector.ReplayManager.SessionState.Replaying)
508             return;
509
510         this._stopUpdatingCurrentTime();
511     },
512
513     _debuggerResumed: function(event)
514     {
515         if (WebInspector.replayManager.sessionState === WebInspector.ReplayManager.SessionState.Replaying)
516             return;
517
518         this._startUpdatingCurrentTime();
519     },
520
521     _recordingTimesUpdated: function(event)
522     {
523         if (!this._waitingToResetCurrentTime)
524             return;
525
526         // COMPATIBILITY (iOS 9): Timeline.recordingStarted events did not include a new startTime.
527         // Make the current time be the start time of the last added record. This is the best way
528         // currently to jump to the right period of time after recording starts.
529
530         for (var timeline of this._recording.timelines.values()) {
531             var lastRecord = timeline.records.lastValue;
532             if (!lastRecord)
533                 continue;
534             this._currentTime = Math.max(this._currentTime, lastRecord.startTime);
535         }
536
537         this._recording.removeEventListener(WebInspector.TimelineRecording.Event.TimesUpdated, this._recordingTimesUpdated, this);
538         this._waitingToResetCurrentTime = false;
539     },
540
541     _clearTimeline: function(event)
542     {
543         if (WebInspector.timelineManager.activeRecording === this._recording && WebInspector.timelineManager.isCapturing())
544             WebInspector.timelineManager.stopCapturing();
545
546         this._recording.reset();
547     },
548
549     _updateTimelineOverviewHeight: function()
550     {
551         const timelineHeight = 36;
552         const renderingFramesTimelineHeight = 108;
553         const rulerHeight = 29;
554
555         var overviewHeight;
556
557         if (this.currentTimelineView && this.currentTimelineView.representedObject.type === WebInspector.TimelineRecord.Type.RenderingFrame)
558             overviewHeight = renderingFramesTimelineHeight;
559         else {
560             var timelineCount = this._timelineViewMap.size;
561             if (this._renderingFrameTimeline)
562                 timelineCount--;
563
564             overviewHeight = timelineCount * timelineHeight;
565         }
566
567         var styleValue = (rulerHeight + overviewHeight) + "px";
568         this._currentTimelineOverview.element.style.height = styleValue;
569         this._contentViewContainer.element.style.top = styleValue;
570     },
571
572     _timelineAdded: function(timelineOrEvent)
573     {
574         var timeline = timelineOrEvent;
575         if (!(timeline instanceof WebInspector.Timeline))
576             timeline = timelineOrEvent.data.timeline;
577
578         console.assert(timeline instanceof WebInspector.Timeline, timeline);
579         console.assert(!this._timelineViewMap.has(timeline), timeline);
580
581         this._timelineViewMap.set(timeline, new WebInspector.ContentView(timeline, {timelineSidebarPanel: this._timelineSidebarPanel}));
582         if (timeline.type === WebInspector.TimelineRecord.Type.RenderingFrame)
583             this._renderingFrameTimeline = timeline;
584
585         var pathComponent = new WebInspector.HierarchicalPathComponent(timeline.displayName, timeline.iconClassName, timeline);
586         pathComponent.addEventListener(WebInspector.HierarchicalPathComponent.Event.SiblingWasSelected, this._pathComponentSelected, this);
587         this._pathComponentMap.set(timeline, pathComponent);
588
589         this._timelineCountChanged();
590     },
591
592     _timelineRemoved: function(event)
593     {
594         var timeline = event.data.timeline;
595         console.assert(timeline instanceof WebInspector.Timeline, timeline);
596         console.assert(this._timelineViewMap.has(timeline), timeline);
597
598         var timelineView = this._timelineViewMap.take(timeline);
599         if (this.currentTimelineView === timelineView)
600             this.showOverviewTimelineView();
601         if (timeline.type === WebInspector.TimelineRecord.Type.RenderingFrame)
602             this._renderingFrameTimeline = null;
603
604         this._pathComponentMap.delete(timeline);
605
606         this._timelineCountChanged();
607     },
608
609     _timelineCountChanged: function()
610     {
611         var previousPathComponent = null;
612         for (var pathComponent of this._pathComponentMap.values()) {
613             if (previousPathComponent) {
614                 previousPathComponent.nextSibling = pathComponent;
615                 pathComponent.previousSibling = previousPathComponent;
616             }
617
618             previousPathComponent = pathComponent;
619         }
620
621         this._updateTimelineOverviewHeight();
622     },
623
624     _recordingReset: function(event)
625     {
626         this._currentTime = NaN;
627
628         if (!this._updating) {
629             // Force the time ruler and views to reset to 0.
630             this._startTimeNeedsReset = true;
631             this._updateTimes(0, 0, 0);
632         }
633
634         this._lastUpdateTimestamp = NaN;
635         this._startTimeNeedsReset = true;
636
637         this._recording.removeEventListener(WebInspector.TimelineRecording.Event.TimesUpdated, this._recordingTimesUpdated, this);
638         this._waitingToResetCurrentTime = false;
639
640         this._linearTimelineOverview.reset();
641         this._renderingFrameTimelineOverview.reset();
642         this._overviewTimelineView.reset();
643         for (var timelineView of this._timelineViewMap.values())
644             timelineView.reset();
645     },
646
647     _recordingUnloaded: function(event)
648     {
649         console.assert(!this._updating);
650
651         WebInspector.timelineManager.removeEventListener(WebInspector.TimelineManager.Event.CapturingStarted, this._capturingStarted, this);
652         WebInspector.timelineManager.removeEventListener(WebInspector.TimelineManager.Event.CapturingStopped, this._capturingStopped, this);
653     },
654
655     _timeRangeSelectionChanged: function(event)
656     {
657         if (this.currentTimelineView) {
658             this.currentTimelineView.startTime = this._currentTimelineOverview.selectionStartTime;
659             this.currentTimelineView.endTime = this._currentTimelineOverview.selectionStartTime + this._currentTimelineOverview.selectionDuration;
660
661             if (this.currentTimelineView.representedObject.type === WebInspector.TimelineRecord.Type.RenderingFrame)
662                 this._updateFrameSelection();
663         }
664
665         // Delay until the next frame to stay in sync with the current timeline view's time-based layout changes.
666         requestAnimationFrame(function() {
667             var selectedTreeElement = this.currentTimelineView && this.currentTimelineView.navigationSidebarTreeOutline ? this.currentTimelineView.navigationSidebarTreeOutline.selectedTreeElement : null;
668             var selectionWasHidden = selectedTreeElement && selectedTreeElement.hidden;
669
670             this._timelineSidebarPanel.updateFilter();
671
672             if (selectedTreeElement && selectedTreeElement.hidden !== selectionWasHidden)
673                 this.dispatchEventToListeners(WebInspector.ContentView.Event.SelectionPathComponentsDidChange);
674         }.bind(this));
675     },
676
677     _recordSelected: function(event)
678     {
679         var timelineView = this._timelineViewMap.get(event.data.timeline);
680         console.assert(timelineView === this.currentTimelineView, timelineView);
681         if (timelineView !== this.currentTimelineView)
682             return;
683
684         var selectedTreeElement = this.currentTimelineView.navigationSidebarTreeOutline.selectedTreeElement;
685         if (!event.data.record) {
686             if (selectedTreeElement)
687                 selectedTreeElement.deselect();
688             return;
689         }
690
691         var treeElement = this.currentTimelineView.navigationSidebarTreeOutline.findTreeElement(event.data.record);
692         console.assert(treeElement, "Timeline view has no tree element for record selected in timeline overview.", timelineView, event.data.record);
693         if (!treeElement)
694             return;
695
696         // Don't select the record's tree element if one of it's children is already selected.
697         if (selectedTreeElement && selectedTreeElement.hasAncestor(treeElement))
698             return;
699
700         treeElement.revealAndSelect(false, false, false, true);
701     },
702
703     _updateFrameSelection: function()
704     {
705         console.assert(this._renderingFrameTimeline);
706         if (!this._renderingFrameTimeline)
707             return;
708
709         var startIndex = this._renderingFrameTimelineOverview.selectionStartTime;
710         var endIndex = startIndex + this._renderingFrameTimelineOverview.selectionDuration - 1;
711         this._timelineSidebarPanel.updateFrameSelection(startIndex, endIndex);
712     }
713 };