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