Web Inspector: discontinuous recordings should have discontinuities in the timeline...
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / TimelineOverview.js
1 /*
2  * Copyright (C) 2013, 2015-2016 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 WebInspector.TimelineOverview = class TimelineOverview extends WebInspector.View
27 {
28     constructor(timelineRecording, delegate)
29     {
30         super();
31
32         console.assert(timelineRecording instanceof WebInspector.TimelineRecording);
33
34         this._timelinesViewModeSettings = this._createViewModeSettings(WebInspector.TimelineOverview.ViewMode.Timelines, 0.0001, 60, 0.01, 0, 15);
35         this._instrumentTypes = WebInspector.TimelineManager.availableTimelineTypes();
36
37         if (WebInspector.FPSInstrument.supported()) {
38             let minimumDurationPerPixel = 1 / WebInspector.TimelineRecordFrame.MaximumWidthPixels;
39             let maximumDurationPerPixel = 1 / WebInspector.TimelineRecordFrame.MinimumWidthPixels;
40             this._renderingFramesViewModeSettings = this._createViewModeSettings(WebInspector.TimelineOverview.ViewMode.RenderingFrames, minimumDurationPerPixel, maximumDurationPerPixel, minimumDurationPerPixel, 0, 100);
41         }
42
43         this._recording = timelineRecording;
44         this._recording.addEventListener(WebInspector.TimelineRecording.Event.InstrumentAdded, this._instrumentAdded, this);
45         this._recording.addEventListener(WebInspector.TimelineRecording.Event.InstrumentRemoved, this._instrumentRemoved, this);
46         this._recording.addEventListener(WebInspector.TimelineRecording.Event.MarkerAdded, this._markerAdded, this);
47         this._recording.addEventListener(WebInspector.TimelineRecording.Event.Reset, this._recordingReset, this);
48
49         this._delegate = delegate;
50
51         this.element.classList.add("timeline-overview");
52         this._updateWheelAndGestureHandlers();
53
54         this._graphsContainerView = new WebInspector.View;
55         this._graphsContainerView.element.classList.add("graphs-container");
56         this.addSubview(this._graphsContainerView);
57
58         this._overviewGraphsByTypeMap = new Map;
59
60         this._editInstrumentsButton = new WebInspector.ActivateButtonNavigationItem("toggle-edit-instruments", WebInspector.UIString("Edit configuration"), WebInspector.UIString("Save configuration"));
61         this._editInstrumentsButton.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._toggleEditingInstruments, this);
62         this._editingInstruments = false;
63         this._updateEditInstrumentsButton();
64
65         let instrumentsNavigationBar = new WebInspector.NavigationBar;
66         instrumentsNavigationBar.element.classList.add("timelines");
67         instrumentsNavigationBar.addNavigationItem(new WebInspector.FlexibleSpaceNavigationItem);
68         instrumentsNavigationBar.addNavigationItem(this._editInstrumentsButton);
69         this.addSubview(instrumentsNavigationBar);
70
71         this._timelinesTreeOutline = new WebInspector.TreeOutline;
72         this._timelinesTreeOutline.element.classList.add("timelines");
73         this._timelinesTreeOutline.disclosureButtons = false;
74         this._timelinesTreeOutline.large = true;
75         this._timelinesTreeOutline.addEventListener(WebInspector.TreeOutline.Event.SelectionDidChange, this._timelinesTreeSelectionDidChange, this);
76         this.element.appendChild(this._timelinesTreeOutline.element);
77
78         this._treeElementsByTypeMap = new Map;
79
80         this._timelineRuler = new WebInspector.TimelineRuler;
81         this._timelineRuler.allowsClippedLabels = true;
82         this._timelineRuler.allowsTimeRangeSelection = true;
83         this._timelineRuler.element.addEventListener("mousedown", this._timelineRulerMouseDown.bind(this));
84         this._timelineRuler.element.addEventListener("click", this._timelineRulerMouseClicked.bind(this));
85         this._timelineRuler.addEventListener(WebInspector.TimelineRuler.Event.TimeRangeSelectionChanged, this._timeRangeSelectionChanged, this);
86         this.addSubview(this._timelineRuler);
87
88         this._currentTimeMarker = new WebInspector.TimelineMarker(0, WebInspector.TimelineMarker.Type.CurrentTime);
89         this._timelineRuler.addMarker(this._currentTimeMarker);
90
91         this._scrollContainerElement = document.createElement("div");
92         this._scrollContainerElement.classList.add("scroll-container");
93         this._scrollContainerElement.addEventListener("scroll", this._handleScrollEvent.bind(this));
94         this.element.appendChild(this._scrollContainerElement);
95
96         this._scrollWidthSizer = document.createElement("div");
97         this._scrollWidthSizer.classList.add("scroll-width-sizer");
98         this._scrollContainerElement.appendChild(this._scrollWidthSizer);
99
100         this._startTime = 0;
101         this._currentTime = 0;
102         this._revealCurrentTime = false;
103         this._endTime = 0;
104         this._pixelAlignDuration = false;
105         this._mouseWheelDelta = 0;
106         this._cachedScrollContainerWidth = NaN;
107         this._timelineRulerSelectionChanged = false;
108         this._viewMode = WebInspector.TimelineOverview.ViewMode.Timelines;
109         this._selectedTimeline = null;;
110
111         for (let instrument of this._recording.instruments)
112             this._instrumentAdded(instrument);
113
114         if (!WebInspector.timelineManager.isCapturingPageReload())
115             this._resetSelection();
116
117         this._viewModeDidChange();
118
119         WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.CapturingStarted, this._capturingStarted, this);
120         WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.CapturingStopped, this._capturingStopped, this);
121     }
122
123     // Public
124
125     get selectedTimeline()
126     {
127         return this._selectedTimeline;
128     }
129
130     set selectedTimeline(x)
131     {
132         if (this._editingInstruments)
133             return;
134
135         if (this._selectedTimeline === x)
136             return;
137
138         this._selectedTimeline = x;
139         if (this._selectedTimeline) {
140             let treeElement = this._treeElementsByTypeMap.get(this._selectedTimeline.type);
141             console.assert(treeElement, "Missing tree element for timeline", this._selectedTimeline);
142
143             let omitFocus = true;
144             let wasSelectedByUser = false;
145             treeElement.select(omitFocus, wasSelectedByUser);
146         } else if (this._timelinesTreeOutline.selectedTreeElement)
147             this._timelinesTreeOutline.selectedTreeElement.deselect();
148     }
149
150     get editingInstruments()
151     {
152         return this._editingInstruments;
153     }
154
155     get viewMode()
156     {
157         return this._viewMode;
158     }
159
160     set viewMode(x)
161     {
162         if (this._editingInstruments)
163             return;
164
165         if (this._viewMode === x)
166             return;
167
168         this._viewMode = x;
169         this._viewModeDidChange();
170     }
171
172     get startTime()
173     {
174         return this._startTime;
175     }
176
177     set startTime(x)
178     {
179         x = x || 0;
180
181         if (this._startTime === x)
182             return;
183
184         if (this._viewMode !== WebInspector.TimelineOverview.ViewMode.RenderingFrames) {
185             let selectionOffset = this.selectionStartTime - this._startTime;
186             this.selectionStartTime = selectionOffset + x;
187         }
188
189         this._startTime = x;
190
191         this.needsLayout();
192     }
193
194     get currentTime()
195     {
196         return this._currentTime;
197     }
198
199     set currentTime(x)
200     {
201         x = x || 0;
202
203         if (this._currentTime === x)
204             return;
205
206         this._currentTime = x;
207         this._revealCurrentTime = true;
208
209         this.needsLayout();
210     }
211
212     get secondsPerPixel()
213     {
214         return this._currentSettings.durationPerPixelSetting.value;
215     }
216
217     set secondsPerPixel(x)
218     {
219         x = Math.min(this._currentSettings.maximumDurationPerPixel, Math.max(this._currentSettings.minimumDurationPerPixel, x));
220
221         if (this.secondsPerPixel === x)
222             return;
223
224         if (this._pixelAlignDuration) {
225             x = 1 / Math.round(1 / x);
226             if (this.secondsPerPixel === x)
227                 return;
228         }
229
230         this._currentSettings.durationPerPixelSetting.value = x;
231
232         this.needsLayout();
233     }
234
235     get pixelAlignDuration()
236     {
237         return this._pixelAlignDuration;
238     }
239
240     set pixelAlignDuration(x)
241     {
242         if (this._pixelAlignDuration === x)
243             return;
244
245         this._mouseWheelDelta = 0;
246         this._pixelAlignDuration = x;
247         if (this._pixelAlignDuration)
248             this.secondsPerPixel = 1 / Math.round(1 / this.secondsPerPixel);
249     }
250
251     get endTime()
252     {
253         return this._endTime;
254     }
255
256     set endTime(x)
257     {
258         x = x || 0;
259
260         if (this._endTime === x)
261             return;
262
263         this._endTime = x;
264
265         this.needsLayout();
266     }
267
268     get scrollStartTime()
269     {
270         return this._currentSettings.scrollStartTime;
271     }
272
273     set scrollStartTime(x)
274     {
275         x = x || 0;
276
277         if (this.scrollStartTime === x)
278             return;
279
280         this._currentSettings.scrollStartTime = x;
281
282         this.needsLayout();
283     }
284
285     get scrollContainerWidth()
286     {
287         return this._cachedScrollContainerWidth;
288     }
289
290     get visibleDuration()
291     {
292         if (isNaN(this._cachedScrollContainerWidth)) {
293             this._cachedScrollContainerWidth = this._scrollContainerElement.offsetWidth;
294             if (!this._cachedScrollContainerWidth)
295                 this._cachedScrollContainerWidth = NaN;
296         }
297
298         return this._cachedScrollContainerWidth * this.secondsPerPixel;
299     }
300
301     get selectionStartTime()
302     {
303         return this._timelineRuler.selectionStartTime;
304     }
305
306     set selectionStartTime(x)
307     {
308         x = x || 0;
309
310         if (this._timelineRuler.selectionStartTime === x)
311             return;
312
313         let selectionDuration = this.selectionDuration;
314         this._timelineRuler.selectionStartTime = x;
315         this._timelineRuler.selectionEndTime = x + selectionDuration;
316     }
317
318     get selectionDuration()
319     {
320         return this._timelineRuler.selectionEndTime - this._timelineRuler.selectionStartTime;
321     }
322
323     set selectionDuration(x)
324     {
325         x = Math.max(this._timelineRuler.minimumSelectionDuration, x);
326
327         this._timelineRuler.selectionEndTime = this._timelineRuler.selectionStartTime + x;
328     }
329
330     get height()
331     {
332         let height = 0;
333         for (let overviewGraph of this._overviewGraphsByTypeMap.values()) {
334             if (overviewGraph.visible)
335                 height += overviewGraph.height;
336         }
337         return height;
338     }
339
340     get visible()
341     {
342         return this._visible;
343     }
344
345     shown()
346     {
347         this._visible = true;
348
349         for (let [type, overviewGraph] of this._overviewGraphsByTypeMap) {
350             if (this._canShowTimelineType(type))
351                 overviewGraph.shown();
352         }
353
354         this.updateLayout(WebInspector.View.LayoutReason.Resize);
355     }
356
357     hidden()
358     {
359         this._visible = false;
360
361         for (let overviewGraph of this._overviewGraphsByTypeMap.values())
362             overviewGraph.hidden();
363     }
364
365     reset()
366     {
367         for (let overviewGraph of this._overviewGraphsByTypeMap.values())
368             overviewGraph.reset();
369
370         this._mouseWheelDelta = 0;
371
372         this._resetSelection();
373     }
374
375     revealMarker(marker)
376     {
377         this.scrollStartTime = marker.time - (this.visibleDuration / 2);
378     }
379
380     recordWasFiltered(timeline, record, filtered)
381     {
382         let overviewGraph = this._overviewGraphsByTypeMap.get(timeline.type);
383         console.assert(overviewGraph, "Missing overview graph for timeline type " + timeline.type);
384         if (!overviewGraph)
385             return;
386
387         console.assert(overviewGraph.visible, "Record filtered in hidden overview graph", record);
388
389         overviewGraph.recordWasFiltered(record, filtered);
390     }
391
392     selectRecord(timeline, record)
393     {
394         let overviewGraph = this._overviewGraphsByTypeMap.get(timeline.type);
395         console.assert(overviewGraph, "Missing overview graph for timeline type " + timeline.type);
396         if (!overviewGraph)
397             return;
398
399         console.assert(overviewGraph.visible, "Record selected in hidden overview graph", record);
400
401         overviewGraph.selectedRecord = record;
402     }
403
404     userSelectedRecord(record)
405     {
406         if (this._delegate && this._delegate.timelineOverviewUserSelectedRecord)
407             this._delegate.timelineOverviewUserSelectedRecord(this, record);
408     }
409
410     updateLayoutIfNeeded()
411     {
412         if (this.layoutPending) {
413             super.updateLayoutIfNeeded();
414             return;
415         }
416
417         this._timelineRuler.updateLayoutIfNeeded();
418
419         for (let overviewGraph of this._overviewGraphsByTypeMap.values()) {
420             if (overviewGraph.visible)
421                 overviewGraph.updateLayoutIfNeeded();
422         }
423     }
424
425     discontinuitiesInTimeRange(startTime, endTime)
426     {
427         return this._recording.discontinuitiesInTimeRange(startTime, endTime);
428     }
429
430     // Protected
431
432     get timelineRuler()
433     {
434         return this._timelineRuler;
435     }
436
437     layout()
438     {
439         let startTime = this._startTime;
440         let endTime = this._endTime;
441         let currentTime = this._currentTime;
442         if (this._viewMode === WebInspector.TimelineOverview.ViewMode.RenderingFrames) {
443             let renderingFramesTimeline = this._recording.timelines.get(WebInspector.TimelineRecord.Type.RenderingFrame);
444             console.assert(renderingFramesTimeline, "Recoring missing rendering frames timeline");
445
446             startTime = 0;
447             endTime = renderingFramesTimeline.records.length;
448             currentTime = endTime;
449         }
450
451         // Calculate the required width based on the duration and seconds per pixel.
452         let duration = endTime - startTime;
453         let newWidth = Math.ceil(duration / this.secondsPerPixel);
454
455         // Update all relevant elements to the new required width.
456         this._updateElementWidth(this._scrollWidthSizer, newWidth);
457
458         this._currentTimeMarker.time = currentTime;
459
460         if (this._revealCurrentTime) {
461             this.revealMarker(this._currentTimeMarker);
462             this._revealCurrentTime = false;
463         }
464
465         const visibleDuration = this.visibleDuration;
466
467         // Clamp the scroll start time to match what the scroll bar would allow.
468         let scrollStartTime = Math.min(this.scrollStartTime, endTime - visibleDuration);
469         scrollStartTime = Math.max(startTime, scrollStartTime);
470
471         this._timelineRuler.zeroTime = startTime;
472         this._timelineRuler.startTime = scrollStartTime;
473         this._timelineRuler.secondsPerPixel = this.secondsPerPixel;
474
475         if (!this._dontUpdateScrollLeft) {
476             this._ignoreNextScrollEvent = true;
477             let scrollLeft = Math.ceil((scrollStartTime - startTime) / this.secondsPerPixel);
478             if (scrollLeft)
479                 this._scrollContainerElement.scrollLeft = scrollLeft;
480         }
481
482         for (let overviewGraph of this._overviewGraphsByTypeMap.values()) {
483             if (!overviewGraph.visible)
484                 continue;
485
486             overviewGraph.zeroTime = startTime;
487             overviewGraph.startTime = scrollStartTime;
488             overviewGraph.currentTime = currentTime;
489             overviewGraph.endTime = scrollStartTime + visibleDuration;
490         }
491     }
492
493     sizeDidChange()
494     {
495         this._cachedScrollContainerWidth = NaN;
496     }
497
498     // Private
499
500     _updateElementWidth(element, newWidth)
501     {
502         var currentWidth = parseInt(element.style.width);
503         if (currentWidth !== newWidth)
504             element.style.width = newWidth + "px";
505     }
506
507     _handleScrollEvent(event)
508     {
509         if (this._ignoreNextScrollEvent) {
510             this._ignoreNextScrollEvent = false;
511             return;
512         }
513
514         this._dontUpdateScrollLeft = true;
515
516         let scrollOffset = this._scrollContainerElement.scrollLeft;
517         this.scrollStartTime = this._startTime + (scrollOffset * this.secondsPerPixel);
518
519         // Force layout so we can update with the scroll position synchronously.
520         this.updateLayoutIfNeeded();
521
522         this._dontUpdateScrollLeft = false;
523     }
524
525     _handleWheelEvent(event)
526     {
527         // Ignore cloned events that come our way, we already handled the original.
528         if (event.__cloned)
529             return;
530
531         // Ignore wheel events while handing gestures.
532         if (this._handlingGesture)
533             return;
534
535         // Require twice the vertical delta to overcome horizontal scrolling. This prevents most
536         // cases of inadvertent zooming for slightly diagonal scrolls.
537         if (Math.abs(event.deltaX) >= Math.abs(event.deltaY) * 0.5) {
538             // Clone the event to dispatch it on the scroll container. Mark it as cloned so we don't get into a loop.
539             let newWheelEvent = new event.constructor(event.type, event);
540             newWheelEvent.__cloned = true;
541
542             this._scrollContainerElement.dispatchEvent(newWheelEvent);
543             return;
544         }
545
546         // Remember the mouse position in time.
547         let mouseOffset = event.pageX - this.element.totalOffsetLeft;
548         let mousePositionTime = this._currentSettings.scrollStartTime + (mouseOffset * this.secondsPerPixel);
549         let deviceDirection = event.webkitDirectionInvertedFromDevice ? 1 : -1;
550         let delta = event.deltaY * (this.secondsPerPixel / WebInspector.TimelineOverview.ScrollDeltaDenominator) * deviceDirection;
551
552         // Reset accumulated wheel delta when direction changes.
553         if (this._pixelAlignDuration && (delta < 0 && this._mouseWheelDelta >= 0 || delta >= 0 && this._mouseWheelDelta < 0))
554             this._mouseWheelDelta = 0;
555
556         let previousDurationPerPixel = this.secondsPerPixel;
557         this._mouseWheelDelta += delta;
558         this.secondsPerPixel += this._mouseWheelDelta;
559
560         if (this.secondsPerPixel === this._currentSettings.minimumDurationPerPixel && delta < 0 || this.secondsPerPixel === this._currentSettings.maximumDurationPerPixel && delta >= 0)
561             this._mouseWheelDelta = 0;
562         else
563             this._mouseWheelDelta = previousDurationPerPixel + this._mouseWheelDelta - this.secondsPerPixel;
564
565         // Center the zoom around the mouse based on the remembered mouse position time.
566         this.scrollStartTime = mousePositionTime - (mouseOffset * this.secondsPerPixel);
567
568         event.preventDefault();
569         event.stopPropagation();
570     }
571
572     _handleGestureStart(event)
573     {
574         if (this._handlingGesture) {
575             // FIXME: <https://webkit.org/b/151068> [Mac] Unexpected gesturestart events when already handling gesture
576             return;
577         }
578
579         let mouseOffset = event.pageX - this.element.totalOffsetLeft;
580         let mousePositionTime = this._currentSettings.scrollStartTime + (mouseOffset * this.secondsPerPixel);
581
582         this._handlingGesture = true;
583         this._gestureStartStartTime = mousePositionTime;
584         this._gestureStartDurationPerPixel = this.secondsPerPixel;
585
586         event.preventDefault();
587         event.stopPropagation();
588     }
589
590     _handleGestureChange(event)
591     {
592         // Cap zooming out at 5x.
593         let scale = Math.max(1/5, event.scale);
594
595         let mouseOffset = event.pageX - this.element.totalOffsetLeft;
596         let newSecondsPerPixel = this._gestureStartDurationPerPixel / scale;
597
598         this.secondsPerPixel = newSecondsPerPixel;
599         this.scrollStartTime = this._gestureStartStartTime - (mouseOffset * this.secondsPerPixel);
600
601         event.preventDefault();
602         event.stopPropagation();
603     }
604
605     _handleGestureEnd(event)
606     {
607         this._handlingGesture = false;
608         this._gestureStartStartTime = NaN;
609         this._gestureStartDurationPerPixel = NaN;
610     }
611
612     _instrumentAdded(instrumentOrEvent)
613     {
614         let instrument = instrumentOrEvent instanceof WebInspector.Instrument ? instrumentOrEvent : instrumentOrEvent.data.instrument;
615         console.assert(instrument instanceof WebInspector.Instrument, instrument);
616
617         let timeline = this._recording.timelineForInstrument(instrument);
618         console.assert(!this._overviewGraphsByTypeMap.has(timeline.type), timeline);
619         console.assert(!this._treeElementsByTypeMap.has(timeline.type), timeline);
620
621         let treeElement = new WebInspector.TimelineTreeElement(timeline);
622         let insertionIndex = insertionIndexForObjectInListSortedByFunction(treeElement, this._timelinesTreeOutline.children, this._compareTimelineTreeElements.bind(this));
623         this._timelinesTreeOutline.insertChild(treeElement, insertionIndex);
624         this._treeElementsByTypeMap.set(timeline.type, treeElement);
625
626         let overviewGraph = WebInspector.TimelineOverviewGraph.createForTimeline(timeline, this);
627         overviewGraph.addEventListener(WebInspector.TimelineOverviewGraph.Event.RecordSelected, this._recordSelected, this);
628         this._overviewGraphsByTypeMap.set(timeline.type, overviewGraph);
629         this._graphsContainerView.insertSubviewBefore(overviewGraph, this._graphsContainerView.subviews[insertionIndex]);
630
631         treeElement.element.style.height = overviewGraph.height + "px";
632
633         if (!this._canShowTimelineType(timeline.type)) {
634             overviewGraph.hidden();
635             treeElement.hidden = true;
636         }
637     }
638
639     _instrumentRemoved(event)
640     {
641         let instrument = event.data.instrument;
642         console.assert(instrument instanceof WebInspector.Instrument, instrument);
643
644         let timeline = this._recording.timelineForInstrument(instrument);
645         let overviewGraph = this._overviewGraphsByTypeMap.get(timeline.type);
646         console.assert(overviewGraph, "Missing overview graph for timeline type", timeline.type);
647
648         let treeElement = this._treeElementsByTypeMap.get(timeline.type);
649         let shouldSuppressOnDeselect = false;
650         let shouldSuppressSelectSibling = true;
651         this._timelinesTreeOutline.removeChild(treeElement, shouldSuppressOnDeselect, shouldSuppressSelectSibling);
652
653         overviewGraph.removeEventListener(WebInspector.TimelineOverviewGraph.Event.RecordSelected, this._recordSelected, this);
654         this._graphsContainerView.removeSubview(overviewGraph);
655
656         this._overviewGraphsByTypeMap.delete(timeline.type);
657         this._treeElementsByTypeMap.delete(timeline.type);
658     }
659
660     _markerAdded(event)
661     {
662         this._timelineRuler.addMarker(event.data.marker);
663     }
664
665     _timelineRulerMouseDown(event)
666     {
667         this._timelineRulerSelectionChanged = false;
668     }
669
670     _timelineRulerMouseClicked(event)
671     {
672         if (this._timelineRulerSelectionChanged)
673             return;
674
675         for (let overviewGraph of this._overviewGraphsByTypeMap.values()) {
676             let graphRect = overviewGraph.element.getBoundingClientRect();
677             if (!(event.pageX >= graphRect.left && event.pageX <= graphRect.right && event.pageY >= graphRect.top && event.pageY <= graphRect.bottom))
678                 continue;
679
680             // Clone the event to dispatch it on the overview graph element.
681             let newClickEvent = new event.constructor(event.type, event);
682             overviewGraph.element.dispatchEvent(newClickEvent);
683             return;
684         }
685     }
686
687     _timeRangeSelectionChanged(event)
688     {
689         this._timelineRulerSelectionChanged = true;
690
691         let startTime = this._viewMode === WebInspector.TimelineOverview.ViewMode.Timelines ? this._startTime : 0;
692         this._currentSettings.selectionStartValueSetting.value = this.selectionStartTime - startTime;
693         this._currentSettings.selectionDurationSetting.value = this.selectionDuration;
694
695         this.dispatchEventToListeners(WebInspector.TimelineOverview.Event.TimeRangeSelectionChanged);
696     }
697
698     _recordSelected(event)
699     {
700         for (let [type, overviewGraph] of this._overviewGraphsByTypeMap) {
701             if (overviewGraph !== event.target)
702                 continue;
703
704             let timeline = this._recording.timelines.get(type);
705             console.assert(timeline, "Timeline recording missing timeline type", type);
706             this.dispatchEventToListeners(WebInspector.TimelineOverview.Event.RecordSelected, {timeline, record: event.data.record});
707             return;
708         }
709     }
710
711     _resetSelection()
712     {
713         function reset(settings)
714         {
715             settings.durationPerPixelSetting.reset();
716             settings.selectionStartValueSetting.reset();
717             settings.selectionDurationSetting.reset();
718         }
719
720         reset(this._timelinesViewModeSettings);
721         if (this._renderingFramesViewModeSettings)
722             reset(this._renderingFramesViewModeSettings);
723
724         this.secondsPerPixel = this._currentSettings.durationPerPixelSetting.value;
725         this.selectionStartTime = this._currentSettings.selectionStartValueSetting.value;
726         this.selectionDuration = this._currentSettings.selectionDurationSetting.value;
727     }
728
729     _recordingReset(event)
730     {
731         this._timelineRuler.clearMarkers();
732         this._timelineRuler.addMarker(this._currentTimeMarker);
733     }
734
735     _canShowTimelineType(type)
736     {
737         let timelineViewMode = WebInspector.TimelineOverview.ViewMode.Timelines;
738         if (type === WebInspector.TimelineRecord.Type.RenderingFrame)
739             timelineViewMode = WebInspector.TimelineOverview.ViewMode.RenderingFrames;
740
741         return timelineViewMode === this._viewMode;
742     }
743
744     _viewModeDidChange()
745     {
746         let startTime = 0;
747         let isRenderingFramesMode = this._viewMode === WebInspector.TimelineOverview.ViewMode.RenderingFrames;
748         if (isRenderingFramesMode) {
749             this._timelineRuler.minimumSelectionDuration = 1;
750             this._timelineRuler.snapInterval = 1;
751             this._timelineRuler.formatLabelCallback = (value) => value.toFixed(0);
752         } else {
753             this._timelineRuler.minimumSelectionDuration = 0.01;
754             this._timelineRuler.snapInterval = NaN;
755             this._timelineRuler.formatLabelCallback = null;
756
757             startTime = this._startTime;
758         }
759
760         this.pixelAlignDuration = isRenderingFramesMode;
761         this.selectionStartTime = this._currentSettings.selectionStartValueSetting.value + startTime;
762         this.selectionDuration = this._currentSettings.selectionDurationSetting.value;
763
764         for (let [type, overviewGraph] of this._overviewGraphsByTypeMap) {
765             let treeElement = this._treeElementsByTypeMap.get(type);
766             console.assert(treeElement, "Missing tree element for timeline type", type);
767
768             treeElement.hidden = !this._canShowTimelineType(type);
769             if (treeElement.hidden)
770                 overviewGraph.hidden();
771             else
772                 overviewGraph.shown();
773         }
774
775         this.element.classList.toggle("frames", isRenderingFramesMode);
776
777         this.updateLayout(WebInspector.View.LayoutReason.Resize);
778     }
779
780     _createViewModeSettings(viewMode, minimumDurationPerPixel, maximumDurationPerPixel, durationPerPixel, selectionStartValue, selectionDuration)
781     {
782         durationPerPixel = Math.min(maximumDurationPerPixel, Math.max(minimumDurationPerPixel, durationPerPixel));
783
784         let durationPerPixelSetting = new WebInspector.Setting(viewMode + "-duration-per-pixel", durationPerPixel);
785         let selectionStartValueSetting = new WebInspector.Setting(viewMode + "-selection-start-value", selectionStartValue);
786         let selectionDurationSetting = new WebInspector.Setting(viewMode + "-selection-duration", selectionDuration);
787
788         return {
789             scrollStartTime: 0,
790             minimumDurationPerPixel,
791             maximumDurationPerPixel,
792             durationPerPixelSetting,
793             selectionStartValueSetting,
794             selectionDurationSetting
795         };
796     }
797
798     get _currentSettings()
799     {
800         return this._viewMode === WebInspector.TimelineOverview.ViewMode.Timelines ? this._timelinesViewModeSettings : this._renderingFramesViewModeSettings;
801     }
802
803     _timelinesTreeSelectionDidChange(event)
804     {
805         function updateGraphSelectedState(timeline, selected)
806         {
807             let overviewGraph = this._overviewGraphsByTypeMap.get(timeline.type);
808             console.assert(overviewGraph, "Missing overview graph for timeline", timeline);
809             overviewGraph.selected = selected;
810         }
811
812         let selectedTreeElement = event.data.selectedElement;
813         let deselectedTreeElement = event.data.deselectedElement;
814         let timeline = null;
815         if (selectedTreeElement) {
816             timeline = selectedTreeElement.representedObject;
817             console.assert(timeline instanceof WebInspector.Timeline, timeline);
818             console.assert(this._recording.timelines.get(timeline.type) === timeline, timeline);
819
820             updateGraphSelectedState.call(this, timeline, true);
821         }
822
823         if (deselectedTreeElement)
824             updateGraphSelectedState.call(this, deselectedTreeElement.representedObject, false);
825
826         this._selectedTimeline = timeline;
827         this.dispatchEventToListeners(WebInspector.TimelineOverview.Event.TimelineSelected);
828     }
829
830     _toggleEditingInstruments(event)
831     {
832         if (this._editingInstruments)
833             this._stopEditingInstruments();
834         else
835             this._startEditingInstruments();
836     }
837
838     _editingInstrumentsDidChange()
839     {
840         this.element.classList.toggle(WebInspector.TimelineOverview.EditInstrumentsStyleClassName, this._editingInstruments);
841         this._timelineRuler.enabled = !this._editingInstruments;
842
843         this._updateWheelAndGestureHandlers();
844         this._updateEditInstrumentsButton();
845
846         this.dispatchEventToListeners(WebInspector.TimelineOverview.Event.EditingInstrumentsDidChange);
847     }
848
849     _updateEditInstrumentsButton()
850     {
851         let newLabel = this._editingInstruments ? WebInspector.UIString("Done") : WebInspector.UIString("Edit");
852         this._editInstrumentsButton.label = newLabel;
853         this._editInstrumentsButton.activated = this._editingInstruments;
854         this._editInstrumentsButton.enabled = !WebInspector.timelineManager.isCapturing();
855     }
856
857     _updateWheelAndGestureHandlers()
858     {
859         if (this._editingInstruments) {
860             this.element.removeEventListener("wheel", this._handleWheelEventListener);
861             this.element.removeEventListener("gesturestart", this._handleGestureStartEventListener);
862             this.element.removeEventListener("gesturechange", this._handleGestureChangeEventListener);
863             this.element.removeEventListener("gestureend", this._handleGestureEndEventListener);
864             this._handleWheelEventListener = null;
865             this._handleGestureStartEventListener = null;
866             this._handleGestureChangeEventListener = null;
867             this._handleGestureEndEventListener = null;
868         } else {
869             this._handleWheelEventListener = this._handleWheelEvent.bind(this);
870             this._handleGestureStartEventListener = this._handleGestureStart.bind(this);
871             this._handleGestureChangeEventListener = this._handleGestureChange.bind(this);
872             this._handleGestureEndEventListener = this._handleGestureEnd.bind(this);
873             this.element.addEventListener("wheel", this._handleWheelEventListener);
874             this.element.addEventListener("gesturestart", this._handleGestureStartEventListener);
875             this.element.addEventListener("gesturechange", this._handleGestureChangeEventListener);
876             this.element.addEventListener("gestureend", this._handleGestureEndEventListener);
877         }
878     }
879
880     _startEditingInstruments()
881     {
882         console.assert(this._viewMode === WebInspector.TimelineOverview.ViewMode.Timelines);
883
884         if (this._editingInstruments)
885             return;
886
887         this._editingInstruments = true;
888
889         for (let type of this._instrumentTypes) {
890             let treeElement = this._treeElementsByTypeMap.get(type);
891             if (!treeElement) {
892                 let timeline = this._recording.timelines.get(type);
893                 console.assert(timeline, "Missing timeline for type " + type);
894
895                 const placeholder = true;
896                 treeElement = new WebInspector.TimelineTreeElement(timeline, placeholder);
897
898                 let insertionIndex = insertionIndexForObjectInListSortedByFunction(treeElement, this._timelinesTreeOutline.children, this._compareTimelineTreeElements.bind(this));
899                 this._timelinesTreeOutline.insertChild(treeElement, insertionIndex);
900             }
901
902             treeElement.editing = true;
903             treeElement.addEventListener(WebInspector.TimelineTreeElement.Event.EnabledDidChange, this._timelineTreeElementEnabledDidChange, this);
904         }
905
906         this._editingInstrumentsDidChange();
907     }
908
909     _stopEditingInstruments()
910     {
911         if (!this._editingInstruments)
912             return;
913
914         this._editingInstruments = false;
915
916         let instruments = this._recording.instruments;
917         for (let treeElement of this._treeElementsByTypeMap.values()) {
918             if (treeElement.status.checked) {
919                 treeElement.editing = false;
920                 treeElement.removeEventListener(WebInspector.TimelineTreeElement.Event.EnabledDidChange, this._timelineTreeElementEnabledDidChange, this);
921                 continue;
922             }
923
924             let timelineInstrument = instruments.find((instrument) => instrument.timelineRecordType === treeElement.representedObject.type);
925             this._recording.removeInstrument(timelineInstrument);
926         }
927
928         let placeholderTreeElements = this._timelinesTreeOutline.children.filter((treeElement) => treeElement.placeholder);
929         for (let treeElement of placeholderTreeElements) {
930             this._timelinesTreeOutline.removeChild(treeElement);
931
932             if (treeElement.status.checked) {
933                 let instrument = WebInspector.Instrument.createForTimelineType(treeElement.representedObject.type);
934                 this._recording.addInstrument(instrument);
935             }
936         }
937
938         let instrumentTypes = instruments.map((instrument) => instrument.timelineRecordType);
939         WebInspector.timelineManager.enabledTimelineTypes = instrumentTypes;
940
941         this._editingInstrumentsDidChange();
942     }
943
944     _capturingStarted()
945     {
946         this._editInstrumentsButton.enabled = false;
947         this._stopEditingInstruments();
948     }
949
950     _capturingStopped()
951     {
952         this._editInstrumentsButton.enabled = true;
953     }
954
955     _compareTimelineTreeElements(a, b)
956     {
957         let aTimelineType = a.representedObject.type;
958         let bTimelineType = b.representedObject.type;
959
960         // Always sort the Rendering Frames timeline last.
961         if (aTimelineType === WebInspector.TimelineRecord.Type.RenderingFrame)
962             return 1;
963         if (bTimelineType === WebInspector.TimelineRecord.Type.RenderingFrame)
964             return -1;
965
966         if (a.placeholder !== b.placeholder)
967             return a.placeholder ? 1 : -1;
968
969         let aTimelineIndex = this._instrumentTypes.indexOf(aTimelineType);
970         let bTimelineIndex = this._instrumentTypes.indexOf(bTimelineType);
971         return aTimelineIndex - bTimelineIndex;
972     }
973
974     _timelineTreeElementEnabledDidChange(event)
975     {
976         let enabled = this._timelinesTreeOutline.children.some((treeElement) => {
977             let timelineType = treeElement.representedObject.type;
978             return this._canShowTimelineType(timelineType) && treeElement.status.checked;
979         });
980
981         this._editInstrumentsButton.enabled = enabled;
982     }
983 };
984
985 WebInspector.TimelineOverview.ScrollDeltaDenominator = 500;
986 WebInspector.TimelineOverview.EditInstrumentsStyleClassName = "edit-instruments";
987
988 WebInspector.TimelineOverview.ViewMode = {
989     Timelines: "timeline-overview-view-mode-timelines",
990     RenderingFrames: "timeline-overview-view-mode-rendering-frames"
991 };
992
993 WebInspector.TimelineOverview.Event = {
994     EditingInstrumentsDidChange: "editing-instruments-did-change",
995     RecordSelected: "timeline-overview-record-selected",
996     TimelineSelected: "timeline-overview-timeline-selected",
997     TimeRangeSelectionChanged: "timeline-overview-time-range-selection-changed"
998 };