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