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