0202537fa68f3d9ef0b4a96ad42dba0b0cd4a65a
[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)
30     {
31         super(recording);
32
33         this._recording = recording;
34
35         this.element.classList.add("timeline-recording");
36
37         this._timelineOverview = new WebInspector.TimelineOverview(this._recording, this);
38         this._timelineOverview.addEventListener(WebInspector.TimelineOverview.Event.TimeRangeSelectionChanged, this._timeRangeSelectionChanged, this);
39         this._timelineOverview.addEventListener(WebInspector.TimelineOverview.Event.RecordSelected, this._recordSelected, this);
40         this._timelineOverview.addEventListener(WebInspector.TimelineOverview.Event.TimelineSelected, this._timelineSelected, this);
41         this._timelineOverview.addEventListener(WebInspector.TimelineOverview.Event.EditingInstrumentsDidChange, this._editingInstrumentsDidChange, this);
42         this.addSubview(this._timelineOverview);
43
44         const disableBackForward = true;
45         const disableFindBanner = true;
46         this._timelineContentBrowser = new WebInspector.ContentBrowser(null, this, disableBackForward, disableFindBanner);
47         this._timelineContentBrowser.addEventListener(WebInspector.ContentBrowser.Event.CurrentContentViewDidChange, this._currentContentViewDidChange, this);
48
49         this._entireRecordingPathComponent = this._createTimelineRangePathComponent(WebInspector.UIString("Entire Recording"));
50         this._timelineSelectionPathComponent = this._createTimelineRangePathComponent();
51         this._timelineSelectionPathComponent.previousSibling = this._entireRecordingPathComponent;
52         this._selectedTimeRangePathComponent = this._entireRecordingPathComponent;
53
54         this._filterBarNavigationItem = new WebInspector.FilterBarNavigationItem;
55         this._filterBarNavigationItem.filterBar.placeholder = WebInspector.UIString("Filter Records");
56         this._filterBarNavigationItem.filterBar.addEventListener(WebInspector.FilterBar.Event.FilterDidChange, this._filterDidChange, this);
57         this._timelineContentBrowser.navigationBar.addNavigationItem(this._filterBarNavigationItem);
58         this.addSubview(this._timelineContentBrowser);
59
60         this._clearTimelineNavigationItem = new WebInspector.ButtonNavigationItem("clear-timeline", WebInspector.UIString("Clear Timeline"), "Images/NavigationItemTrash.svg", 15, 15);
61         this._clearTimelineNavigationItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._clearTimeline, this);
62
63         this._overviewTimelineView = new WebInspector.OverviewTimelineView(recording);
64         this._overviewTimelineView.secondsPerPixel = this._timelineOverview.secondsPerPixel;
65
66         this._progressView = new WebInspector.TimelineRecordingProgressView;
67         this._timelineContentBrowser.addSubview(this._progressView);
68
69         this._timelineViewMap = new Map;
70         this._pathComponentMap = new Map;
71
72         this._updating = false;
73         this._currentTime = NaN;
74         this._lastUpdateTimestamp = NaN;
75         this._startTimeNeedsReset = true;
76         this._renderingFrameTimeline = null;
77
78         this._recording.addEventListener(WebInspector.TimelineRecording.Event.InstrumentAdded, this._instrumentAdded, this);
79         this._recording.addEventListener(WebInspector.TimelineRecording.Event.InstrumentRemoved, this._instrumentRemoved, this);
80         this._recording.addEventListener(WebInspector.TimelineRecording.Event.Reset, this._recordingReset, this);
81         this._recording.addEventListener(WebInspector.TimelineRecording.Event.Unloaded, this._recordingUnloaded, this);
82
83         WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.CapturingStarted, this._capturingStarted, this);
84         WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.CapturingStopped, this._capturingStopped, this);
85
86         WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.Paused, this._debuggerPaused, this);
87         WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.Resumed, this._debuggerResumed, this);
88
89         WebInspector.ContentView.addEventListener(WebInspector.ContentView.Event.SelectionPathComponentsDidChange, this._contentViewSelectionPathComponentDidChange, this);
90         WebInspector.ContentView.addEventListener(WebInspector.ContentView.Event.SupplementalRepresentedObjectsDidChange, this._contentViewSupplementalRepresentedObjectsDidChange, this);
91
92         WebInspector.TimelineView.addEventListener(WebInspector.TimelineView.Event.RecordWasFiltered, this._recordWasFiltered, this);
93
94         WebInspector.notifications.addEventListener(WebInspector.Notification.VisibilityStateDidChange, this._inspectorVisibilityStateChanged, this);
95
96         for (let instrument of this._recording.instruments)
97             this._instrumentAdded(instrument);
98
99         this.showOverviewTimelineView();
100     }
101
102     // Public
103
104     showOverviewTimelineView()
105     {
106         this._timelineContentBrowser.showContentView(this._overviewTimelineView);
107     }
108
109     showTimelineViewForTimeline(timeline)
110     {
111         console.assert(timeline instanceof WebInspector.Timeline, timeline);
112         console.assert(this._timelineViewMap.has(timeline), timeline);
113         if (!this._timelineViewMap.has(timeline))
114             return;
115
116         this._timelineContentBrowser.showContentView(this._timelineViewMap.get(timeline));
117     }
118
119     get supportsSplitContentBrowser()
120     {
121         // The layout of the overview and split content browser don't work well.
122         return false;
123     }
124
125     get selectionPathComponents()
126     {
127         if (!this._timelineContentBrowser.currentContentView)
128             return [];
129
130         let pathComponents = [];
131         let representedObject = this._timelineContentBrowser.currentContentView.representedObject;
132         if (representedObject instanceof WebInspector.Timeline)
133             pathComponents.push(this._pathComponentMap.get(representedObject));
134
135         pathComponents.push(this._selectedTimeRangePathComponent);
136         return pathComponents;
137     }
138
139     get supplementalRepresentedObjects()
140     {
141         if (!this._timelineContentBrowser.currentContentView)
142             return [];
143         return this._timelineContentBrowser.currentContentView.supplementalRepresentedObjects;
144     }
145
146     get navigationItems()
147     {
148         return [this._clearTimelineNavigationItem];
149     }
150
151     get handleCopyEvent()
152     {
153         let currentContentView = this._timelineContentBrowser.currentContentView;
154         return currentContentView && typeof currentContentView.handleCopyEvent === "function" ? currentContentView.handleCopyEvent.bind(currentContentView) : null;
155     }
156
157     get supportsSave()
158     {
159         let currentContentView = this._timelineContentBrowser.currentContentView;
160         return currentContentView && currentContentView.supportsSave;
161     }
162
163     get saveData()
164     {
165         let currentContentView = this._timelineContentBrowser.currentContentView;
166         return currentContentView && currentContentView.saveData || null;
167     }
168
169     get currentTimelineView()
170     {
171         return this._timelineContentBrowser.currentContentView;
172     }
173
174     shown()
175     {
176         this._timelineOverview.shown();
177         this._timelineContentBrowser.shown();
178         this._clearTimelineNavigationItem.enabled = !this._recording.readonly && !isNaN(this._recording.startTime);
179
180         this._currentContentViewDidChange();
181
182         if (!this._updating && WebInspector.timelineManager.activeRecording === this._recording && WebInspector.timelineManager.isCapturing())
183             this._startUpdatingCurrentTime(this._currentTime);
184     }
185
186     hidden()
187     {
188         this._timelineOverview.hidden();
189         this._timelineContentBrowser.hidden();
190
191         if (this._updating)
192             this._stopUpdatingCurrentTime();
193     }
194
195     closed()
196     {
197         this._timelineContentBrowser.contentViewContainer.closeAllContentViews();
198
199         this._recording.removeEventListener(null, null, this);
200
201         WebInspector.timelineManager.removeEventListener(null, null, this);
202         WebInspector.debuggerManager.removeEventListener(null, null, this);
203         WebInspector.ContentView.removeEventListener(null, null, this);
204     }
205
206     canGoBack()
207     {
208         return this._timelineContentBrowser.canGoBack();
209     }
210
211     canGoForward()
212     {
213         return this._timelineContentBrowser.canGoForward();
214     }
215
216     goBack()
217     {
218         this._timelineContentBrowser.goBack();
219     }
220
221     goForward()
222     {
223         this._timelineContentBrowser.goForward();
224     }
225
226     // ContentBrowser delegate
227
228     contentBrowserTreeElementForRepresentedObject(contentBrowser, representedObject)
229     {
230         if (!(representedObject instanceof WebInspector.Timeline) && !(representedObject instanceof WebInspector.TimelineRecording))
231             return null;
232
233         let iconClassName;
234         let title;
235         if (representedObject instanceof WebInspector.Timeline) {
236             iconClassName = WebInspector.TimelineTabContentView.iconClassNameForTimelineType(representedObject.type);
237             title = WebInspector.UIString("Details");
238         } else {
239             iconClassName = WebInspector.TimelineTabContentView.StopwatchIconStyleClass;
240             title = WebInspector.UIString("Overview");
241         }
242
243         const hasChildren = false;
244         return new WebInspector.GeneralTreeElement(iconClassName, title, representedObject, hasChildren);
245     }
246
247     // TimelineOverview delegate
248
249     timelineOverviewUserSelectedRecord(timelineOverview, timelineRecord)
250     {
251         let timelineViewForRecord = null;
252         for (let timelineView of this._timelineViewMap.values()) {
253             if (timelineView.representedObject.type === timelineRecord.type) {
254                 timelineViewForRecord = timelineView;
255                 break;
256             }
257         }
258
259         if (!timelineViewForRecord)
260             return;
261
262         this._timelineContentBrowser.showContentView(timelineViewForRecord);
263         timelineViewForRecord.userSelectedRecordFromOverview(timelineRecord);
264     }
265
266     // Private
267
268     _currentContentViewDidChange(event)
269     {
270         let newViewMode;
271         let timelineView = this.currentTimelineView;
272         if (timelineView && timelineView.representedObject.type === WebInspector.TimelineRecord.Type.RenderingFrame)
273             newViewMode = WebInspector.TimelineOverview.ViewMode.RenderingFrames;
274         else
275             newViewMode = WebInspector.TimelineOverview.ViewMode.Timelines;
276
277         this._timelineOverview.viewMode = newViewMode;
278         this._updateTimelineOverviewHeight();
279         this._updateProgressView();
280         this._updateFilterBar();
281
282         if (timelineView) {
283             this._updateTimelineViewTimes(timelineView);
284
285             let timeline = null;
286             if (timelineView.representedObject instanceof WebInspector.Timeline)
287                 timeline = timelineView.representedObject;
288
289             this._timelineOverview.selectedTimeline = timeline;
290         }
291
292         this.dispatchEventToListeners(WebInspector.ContentView.Event.SelectionPathComponentsDidChange);
293         this.dispatchEventToListeners(WebInspector.ContentView.Event.NavigationItemsDidChange);
294     }
295
296     _timelinePathComponentSelected(event)
297     {
298         let selectedTimeline = event.data.pathComponent.representedObject;
299         this.showTimelineViewForTimeline(selectedTimeline);
300     }
301
302     _timeRangePathComponentSelected(event)
303     {
304         let selectedPathComponent = event.data.pathComponent;
305         if (selectedPathComponent === this._selectedTimeRangePathComponent)
306             return;
307
308         let timelineRuler = this._timelineOverview.timelineRuler
309         if (selectedPathComponent === this._entireRecordingPathComponent)
310             timelineRuler.selectEntireRange();
311         else {
312             let timelineRange = selectedPathComponent.representedObject;
313             timelineRuler.selectionStartTime = timelineRuler.zeroTime + timelineRange.startValue;
314             timelineRuler.selectionEndTime = timelineRuler.zeroTime + timelineRange.endValue;
315         }
316     }
317
318     _contentViewSelectionPathComponentDidChange(event)
319     {
320         if (event.target !== this._timelineContentBrowser.currentContentView)
321             return;
322
323         this._updateFilterBar();
324
325         this.dispatchEventToListeners(WebInspector.ContentView.Event.SelectionPathComponentsDidChange);
326
327         if (this.currentTimelineView === this._overviewTimelineView)
328             return;
329
330         let record = null;
331         if (this.currentTimelineView.selectionPathComponents) {
332             let recordPathComponent = this.currentTimelineView.selectionPathComponents.find((element) => element.representedObject instanceof WebInspector.TimelineRecord);
333             record = recordPathComponent ? recordPathComponent.representedObject : null;
334         }
335
336         this._timelineOverview.selectRecord(event.target.representedObject, record);
337     }
338
339     _contentViewSupplementalRepresentedObjectsDidChange(event)
340     {
341         if (event.target !== this._timelineContentBrowser.currentContentView)
342             return;
343         this.dispatchEventToListeners(WebInspector.ContentView.Event.SupplementalRepresentedObjectsDidChange);
344     }
345
346     _inspectorVisibilityStateChanged()
347     {
348         if (WebInspector.timelineManager.activeRecording !== this._recording)
349             return;
350
351         // Stop updating since the results won't be rendered anyway.
352         if (!WebInspector.visible && this._updating) {
353             this._stopUpdatingCurrentTime();
354             return;
355         }
356
357         // Nothing else to do if the current time was not being updated.
358         if (!WebInspector.visible)
359             return;
360
361         let {startTime, endTime} = this.representedObject;
362         if (!WebInspector.timelineManager.isCapturing()) {
363             // Force the overview to render data from the entire recording.
364             // This is necessary if the recording was started when the inspector was not
365             // visible because the views were never updated with currentTime/endTime.
366             this._updateTimes(startTime, endTime, endTime);
367             return;
368         }
369
370         this._startUpdatingCurrentTime(endTime);
371     }
372
373     _update(timestamp)
374     {
375         if (this._waitingToResetCurrentTime) {
376             requestAnimationFrame(this._updateCallback);
377             return;
378         }
379
380         var startTime = this._recording.startTime;
381         var currentTime = this._currentTime || startTime;
382         var endTime = this._recording.endTime;
383         var timespanSinceLastUpdate = (timestamp - this._lastUpdateTimestamp) / 1000 || 0;
384
385         currentTime += timespanSinceLastUpdate;
386
387         this._updateTimes(startTime, currentTime, endTime);
388
389         // Only stop updating if the current time is greater than the end time, or the end time is NaN.
390         // The recording end time will be NaN if no records were added.
391         if (!this._updating && (currentTime >= endTime || isNaN(endTime))) {
392             if (this.visible)
393                 this._lastUpdateTimestamp = NaN;
394             return;
395         }
396
397         this._lastUpdateTimestamp = timestamp;
398
399         requestAnimationFrame(this._updateCallback);
400     }
401
402     _updateTimes(startTime, currentTime, endTime)
403     {
404         if (this._startTimeNeedsReset && !isNaN(startTime)) {
405             this._timelineOverview.startTime = startTime;
406             this._overviewTimelineView.zeroTime = startTime;
407             for (let timelineView of this._timelineViewMap.values())
408                 timelineView.zeroTime = startTime;
409
410             this._startTimeNeedsReset = false;
411         }
412
413         this._timelineOverview.endTime = Math.max(endTime, currentTime);
414
415         this._currentTime = currentTime;
416         this._timelineOverview.currentTime = currentTime;
417
418         if (this.currentTimelineView)
419             this._updateTimelineViewTimes(this.currentTimelineView);
420
421         // Force a layout now since we are already in an animation frame and don't need to delay it until the next.
422         this._timelineOverview.updateLayoutIfNeeded();
423         if (this.currentTimelineView)
424             this.currentTimelineView.updateLayoutIfNeeded();
425     }
426
427     _startUpdatingCurrentTime(startTime)
428     {
429         console.assert(!this._updating);
430         if (this._updating)
431             return;
432
433         // Don't update the current time if the Inspector is not visible, as the requestAnimationFrames won't work.
434         if (!WebInspector.visible)
435             return;
436
437         if (typeof startTime === "number" && !isNaN(this._currentTime))
438             this._currentTime = startTime;
439         else {
440             // This happens when you stop and later restart recording.
441             // COMPATIBILITY (iOS 9): Timeline.recordingStarted events did not include a timestamp.
442             // We likely need to jump into the future to a better current time which we can
443             // ascertained from a new incoming timeline record, so we wait for a Timeline to update.
444             console.assert(!this._waitingToResetCurrentTime);
445             this._waitingToResetCurrentTime = true;
446             this._recording.addEventListener(WebInspector.TimelineRecording.Event.TimesUpdated, this._recordingTimesUpdated, this);
447         }
448
449         this._updating = true;
450
451         if (!this._updateCallback)
452             this._updateCallback = this._update.bind(this);
453
454         requestAnimationFrame(this._updateCallback);
455     }
456
457     _stopUpdatingCurrentTime()
458     {
459         console.assert(this._updating);
460         this._updating = false;
461
462         if (this._waitingToResetCurrentTime) {
463             // Did not get any event while waiting for the current time, but we should stop waiting.
464             this._recording.removeEventListener(WebInspector.TimelineRecording.Event.TimesUpdated, this._recordingTimesUpdated, this);
465             this._waitingToResetCurrentTime = false;
466         }
467     }
468
469     _capturingStarted(event)
470     {
471         this._updateProgressView();
472
473         if (!this._updating)
474             this._startUpdatingCurrentTime(event.data.startTime);
475         this._clearTimelineNavigationItem.enabled = !this._recording.readonly;
476     }
477
478     _capturingStopped(event)
479     {
480         this._updateProgressView();
481
482         if (this._updating)
483             this._stopUpdatingCurrentTime();
484
485         if (this.currentTimelineView)
486             this._updateTimelineViewTimes(this.currentTimelineView);
487     }
488
489     _debuggerPaused(event)
490     {
491         if (WebInspector.replayManager.sessionState === WebInspector.ReplayManager.SessionState.Replaying)
492             return;
493
494         if (this._updating)
495             this._stopUpdatingCurrentTime();
496     }
497
498     _debuggerResumed(event)
499     {
500         if (WebInspector.replayManager.sessionState === WebInspector.ReplayManager.SessionState.Replaying)
501             return;
502
503         if (!this._updating)
504             this._startUpdatingCurrentTime();
505     }
506
507     _recordingTimesUpdated(event)
508     {
509         if (!this._waitingToResetCurrentTime)
510             return;
511
512         // COMPATIBILITY (iOS 9): Timeline.recordingStarted events did not include a new startTime.
513         // Make the current time be the start time of the last added record. This is the best way
514         // currently to jump to the right period of time after recording starts.
515
516         for (var timeline of this._recording.timelines.values()) {
517             var lastRecord = timeline.records.lastValue;
518             if (!lastRecord)
519                 continue;
520             this._currentTime = Math.max(this._currentTime, lastRecord.startTime);
521         }
522
523         this._recording.removeEventListener(WebInspector.TimelineRecording.Event.TimesUpdated, this._recordingTimesUpdated, this);
524         this._waitingToResetCurrentTime = false;
525     }
526
527     _clearTimeline(event)
528     {
529         if (WebInspector.timelineManager.activeRecording === this._recording && WebInspector.timelineManager.isCapturing())
530             WebInspector.timelineManager.stopCapturing();
531
532         this._recording.reset();
533     }
534
535     _updateTimelineOverviewHeight()
536     {
537         if (this._timelineOverview.editingInstruments)
538             this._timelineOverview.element.style.height = "";
539         else {
540             const rulerHeight = 23;
541
542             let styleValue = (rulerHeight + this._timelineOverview.height) + "px";
543             this._timelineOverview.element.style.height = styleValue;
544             this._timelineContentBrowser.element.style.top = styleValue;
545         }
546     }
547
548     _instrumentAdded(instrumentOrEvent)
549     {
550         let instrument = instrumentOrEvent instanceof WebInspector.Instrument ? instrumentOrEvent : instrumentOrEvent.data.instrument;
551         console.assert(instrument instanceof WebInspector.Instrument, instrument);
552
553         let timeline = this._recording.timelineForInstrument(instrument);
554         console.assert(!this._timelineViewMap.has(timeline), timeline);
555
556         this._timelineViewMap.set(timeline, WebInspector.ContentView.createFromRepresentedObject(timeline, {recording: this._recording}));
557         if (timeline.type === WebInspector.TimelineRecord.Type.RenderingFrame)
558             this._renderingFrameTimeline = timeline;
559
560         let displayName = WebInspector.TimelineTabContentView.displayNameForTimelineType(timeline.type);
561         let iconClassName = WebInspector.TimelineTabContentView.iconClassNameForTimelineType(timeline.type);
562         let pathComponent = new WebInspector.HierarchicalPathComponent(displayName, iconClassName, timeline);
563         pathComponent.addEventListener(WebInspector.HierarchicalPathComponent.Event.SiblingWasSelected, this._timelinePathComponentSelected, this);
564         this._pathComponentMap.set(timeline, pathComponent);
565
566         this._timelineCountChanged();
567     }
568
569     _instrumentRemoved(event)
570     {
571         let instrument = event.data.instrument;
572         console.assert(instrument instanceof WebInspector.Instrument);
573
574         let timeline = this._recording.timelineForInstrument(instrument);
575         console.assert(this._timelineViewMap.has(timeline), timeline);
576
577         let timelineView = this._timelineViewMap.take(timeline);
578         if (this.currentTimelineView === timelineView)
579             this.showOverviewTimelineView();
580         if (timeline.type === WebInspector.TimelineRecord.Type.RenderingFrame)
581             this._renderingFrameTimeline = null;
582
583         this._pathComponentMap.delete(timeline);
584
585         this._timelineCountChanged();
586     }
587
588     _timelineCountChanged()
589     {
590         var previousPathComponent = null;
591         for (var pathComponent of this._pathComponentMap.values()) {
592             if (previousPathComponent) {
593                 previousPathComponent.nextSibling = pathComponent;
594                 pathComponent.previousSibling = previousPathComponent;
595             }
596
597             previousPathComponent = pathComponent;
598         }
599
600         this._updateTimelineOverviewHeight();
601     }
602
603     _recordingReset(event)
604     {
605         this._currentTime = NaN;
606
607         if (!this._updating) {
608             // Force the time ruler and views to reset to 0.
609             this._startTimeNeedsReset = true;
610             this._updateTimes(0, 0, 0);
611         }
612
613         this._lastUpdateTimestamp = NaN;
614         this._startTimeNeedsReset = true;
615
616         this._recording.removeEventListener(WebInspector.TimelineRecording.Event.TimesUpdated, this._recordingTimesUpdated, this);
617         this._waitingToResetCurrentTime = false;
618
619         this._timelineOverview.reset();
620         this._overviewTimelineView.reset();
621         for (var timelineView of this._timelineViewMap.values())
622             timelineView.reset();
623         this._clearTimelineNavigationItem.enabled = false;
624     }
625
626     _recordingUnloaded(event)
627     {
628         console.assert(!this._updating);
629
630         WebInspector.timelineManager.removeEventListener(WebInspector.TimelineManager.Event.CapturingStarted, this._capturingStarted, this);
631         WebInspector.timelineManager.removeEventListener(WebInspector.TimelineManager.Event.CapturingStopped, this._capturingStopped, this);
632     }
633
634     _timeRangeSelectionChanged(event)
635     {
636         console.assert(this.currentTimelineView);
637         if (!this.currentTimelineView)
638             return;
639
640         this._updateTimelineViewTimes(this.currentTimelineView);
641
642         let selectedPathComponent;
643         if (this._timelineOverview.timelineRuler.entireRangeSelected)
644             selectedPathComponent = this._entireRecordingPathComponent;
645         else {
646             let timelineRange = this._timelineSelectionPathComponent.representedObject;
647             timelineRange.startValue = this.currentTimelineView.startTime;
648             timelineRange.endValue = this.currentTimelineView.endTime;
649
650             if (!(this.currentTimelineView instanceof WebInspector.RenderingFrameTimelineView)) {
651                 timelineRange.startValue -= this.currentTimelineView.zeroTime;
652                 timelineRange.endValue -= this.currentTimelineView.zeroTime;
653             }
654
655             this._updateTimeRangePathComponents();
656             selectedPathComponent = this._timelineSelectionPathComponent;
657         }
658
659         if (this._selectedTimeRangePathComponent !== selectedPathComponent) {
660             this._selectedTimeRangePathComponent = selectedPathComponent;
661             this.dispatchEventToListeners(WebInspector.ContentView.Event.SelectionPathComponentsDidChange);
662         }
663     }
664
665     _recordSelected(event)
666     {
667         let {record, timeline} = event.data;
668         let timelineView = this._timelineViewMap.get(timeline);
669
670         if (record && timelineView !== this.currentTimelineView)
671             this.showTimelineViewForTimeline(timeline);
672
673         timelineView.selectRecord(record);
674     }
675
676     _timelineSelected()
677     {
678         let timeline = this._timelineOverview.selectedTimeline;
679         if (timeline)
680             this.showTimelineViewForTimeline(timeline);
681         else
682             this.showOverviewTimelineView();
683     }
684
685     _updateTimeRangePathComponents()
686     {
687         let timelineRange = this._timelineSelectionPathComponent.representedObject;
688         let startValue = timelineRange.startValue;
689         let endValue = timelineRange.endValue;
690         if (isNaN(startValue) || isNaN(endValue)) {
691             this._entireRecordingPathComponent.nextSibling = null;
692             return;
693         }
694
695         this._entireRecordingPathComponent.nextSibling = this._timelineSelectionPathComponent;
696
697         let displayName;
698         if (this._timelineOverview.viewMode === WebInspector.TimelineOverview.ViewMode.Timelines) {
699             let selectionStart = Number.secondsToString(startValue, true);
700             let selectionEnd = Number.secondsToString(endValue, true);
701             displayName = WebInspector.UIString("%s \u2013 %s").format(selectionStart, selectionEnd);
702         } else {
703             startValue += 1; // Convert index to frame number.
704             if (startValue === endValue)
705                 displayName = WebInspector.UIString("Frame %d").format(startValue);
706             else
707                 displayName = WebInspector.UIString("Frames %d \u2013 %d").format(startValue, endValue);
708         }
709
710         this._timelineSelectionPathComponent.displayName = displayName;
711         this._timelineSelectionPathComponent.title = displayName;
712     }
713
714     _createTimelineRangePathComponent(title)
715     {
716         let range = new WebInspector.TimelineRange(NaN, NaN);
717         let pathComponent = new WebInspector.HierarchicalPathComponent(title || enDash, "time-icon", range);
718         pathComponent.addEventListener(WebInspector.HierarchicalPathComponent.Event.SiblingWasSelected, this._timeRangePathComponentSelected, this);
719
720         return pathComponent;
721     }
722
723     _updateTimelineViewTimes(timelineView)
724     {
725         let timelineRuler = this._timelineOverview.timelineRuler;
726         let entireRangeSelected = timelineRuler.entireRangeSelected;
727         let endTime = this._timelineOverview.selectionStartTime + this._timelineOverview.selectionDuration;
728
729         if (entireRangeSelected) {
730             if (timelineView instanceof WebInspector.RenderingFrameTimelineView) {
731                 endTime = this._renderingFrameTimeline.records.length;
732             } else {
733                 // Clamp selection to the end of the recording (with padding),
734                 // so graph views will show an auto-sized graph without a lot of
735                 // empty space at the end.
736                 endTime = isNaN(this._recording.endTime) ? this._recording.currentTime : this._recording.endTime;
737                 endTime += timelineRuler.minimumSelectionDuration;
738             }
739         }
740
741         timelineView.startTime = this._timelineOverview.selectionStartTime;
742         timelineView.currentTime = this._currentTime;
743         timelineView.endTime = endTime;
744     }
745
746     _editingInstrumentsDidChange(event)
747     {
748         let editingInstruments = this._timelineOverview.editingInstruments;
749         this.element.classList.toggle(WebInspector.TimelineOverview.EditInstrumentsStyleClassName, editingInstruments);
750
751         this._updateTimelineOverviewHeight();
752     }
753
754     _filterDidChange()
755     {
756         if (!this.currentTimelineView)
757             return;
758
759         this.currentTimelineView.updateFilter(this._filterBarNavigationItem.filterBar.filters);
760     }
761
762     _recordWasFiltered(event)
763     {
764         if (event.target !== this.currentTimelineView)
765             return;
766
767         console.assert(this.currentTimelineView);
768
769         let timeline = this.currentTimelineView.representedObject;
770         if (!(timeline instanceof WebInspector.Timeline))
771             return;
772
773         let record = event.data.record;
774         let filtered = event.data.filtered;
775         this._timelineOverview.recordWasFiltered(timeline, record, filtered);
776     }
777
778     _updateProgressView()
779     {
780         let isCapturing = WebInspector.timelineManager.isCapturing();
781         this._progressView.visible = isCapturing && this.currentTimelineView && !this.currentTimelineView.showsLiveRecordingData;
782     }
783
784     _updateFilterBar()
785     {
786         this._filterBarNavigationItem.hidden = !this.currentTimelineView || !this.currentTimelineView.showsFilterBar;
787     }
788 };