diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebInspectorUI/UserInterface/Views/TimelineOverview.js | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Views/TimelineOverview.js')
-rw-r--r-- | Source/WebInspectorUI/UserInterface/Views/TimelineOverview.js | 1011 |
1 files changed, 1011 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Views/TimelineOverview.js b/Source/WebInspectorUI/UserInterface/Views/TimelineOverview.js new file mode 100644 index 000000000..9f09d01ef --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Views/TimelineOverview.js @@ -0,0 +1,1011 @@ +/* + * Copyright (C) 2013, 2015-2016 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.TimelineOverview = class TimelineOverview extends WebInspector.View +{ + constructor(timelineRecording, delegate) + { + super(); + + console.assert(timelineRecording instanceof WebInspector.TimelineRecording); + + this._timelinesViewModeSettings = this._createViewModeSettings(WebInspector.TimelineOverview.ViewMode.Timelines, WebInspector.TimelineOverview.MinimumDurationPerPixel, WebInspector.TimelineOverview.MaximumDurationPerPixel, 0.01, 0, 15); + this._instrumentTypes = WebInspector.TimelineManager.availableTimelineTypes(); + + if (WebInspector.FPSInstrument.supported()) { + let minimumDurationPerPixel = 1 / WebInspector.TimelineRecordFrame.MaximumWidthPixels; + let maximumDurationPerPixel = 1 / WebInspector.TimelineRecordFrame.MinimumWidthPixels; + this._renderingFramesViewModeSettings = this._createViewModeSettings(WebInspector.TimelineOverview.ViewMode.RenderingFrames, minimumDurationPerPixel, maximumDurationPerPixel, minimumDurationPerPixel, 0, 100); + } + + this._recording = timelineRecording; + this._recording.addEventListener(WebInspector.TimelineRecording.Event.InstrumentAdded, this._instrumentAdded, this); + this._recording.addEventListener(WebInspector.TimelineRecording.Event.InstrumentRemoved, this._instrumentRemoved, this); + this._recording.addEventListener(WebInspector.TimelineRecording.Event.MarkerAdded, this._markerAdded, this); + this._recording.addEventListener(WebInspector.TimelineRecording.Event.Reset, this._recordingReset, this); + + this._delegate = delegate; + + this.element.classList.add("timeline-overview"); + this._updateWheelAndGestureHandlers(); + + this._graphsContainerView = new WebInspector.View; + this._graphsContainerView.element.classList.add("graphs-container"); + this.addSubview(this._graphsContainerView); + + this._overviewGraphsByTypeMap = new Map; + + this._editInstrumentsButton = new WebInspector.ActivateButtonNavigationItem("toggle-edit-instruments", WebInspector.UIString("Edit configuration"), WebInspector.UIString("Save configuration")); + this._editInstrumentsButton.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._toggleEditingInstruments, this); + this._editingInstruments = false; + this._updateEditInstrumentsButton(); + + let instrumentsNavigationBar = new WebInspector.NavigationBar; + instrumentsNavigationBar.element.classList.add("timelines"); + instrumentsNavigationBar.addNavigationItem(new WebInspector.FlexibleSpaceNavigationItem); + instrumentsNavigationBar.addNavigationItem(this._editInstrumentsButton); + this.addSubview(instrumentsNavigationBar); + + this._timelinesTreeOutline = new WebInspector.TreeOutline; + this._timelinesTreeOutline.element.classList.add("timelines"); + this._timelinesTreeOutline.disclosureButtons = false; + this._timelinesTreeOutline.large = true; + this._timelinesTreeOutline.addEventListener(WebInspector.TreeOutline.Event.SelectionDidChange, this._timelinesTreeSelectionDidChange, this); + this.element.appendChild(this._timelinesTreeOutline.element); + + this._treeElementsByTypeMap = new Map; + + this._timelineRuler = new WebInspector.TimelineRuler; + this._timelineRuler.allowsClippedLabels = true; + this._timelineRuler.allowsTimeRangeSelection = true; + this._timelineRuler.element.addEventListener("mousedown", this._timelineRulerMouseDown.bind(this)); + this._timelineRuler.element.addEventListener("click", this._timelineRulerMouseClicked.bind(this)); + this._timelineRuler.addEventListener(WebInspector.TimelineRuler.Event.TimeRangeSelectionChanged, this._timeRangeSelectionChanged, this); + this.addSubview(this._timelineRuler); + + this._currentTimeMarker = new WebInspector.TimelineMarker(0, WebInspector.TimelineMarker.Type.CurrentTime); + this._timelineRuler.addMarker(this._currentTimeMarker); + + this._scrollContainerElement = document.createElement("div"); + this._scrollContainerElement.classList.add("scroll-container"); + this._scrollContainerElement.addEventListener("scroll", this._handleScrollEvent.bind(this)); + this.element.appendChild(this._scrollContainerElement); + + this._scrollWidthSizer = document.createElement("div"); + this._scrollWidthSizer.classList.add("scroll-width-sizer"); + this._scrollContainerElement.appendChild(this._scrollWidthSizer); + + this._startTime = 0; + this._currentTime = 0; + this._revealCurrentTime = false; + this._endTime = 0; + this._pixelAlignDuration = false; + this._mouseWheelDelta = 0; + this._cachedScrollContainerWidth = NaN; + this._timelineRulerSelectionChanged = false; + this._viewMode = WebInspector.TimelineOverview.ViewMode.Timelines; + this._selectedTimeline = null; + + for (let instrument of this._recording.instruments) + this._instrumentAdded(instrument); + + if (!WebInspector.timelineManager.isCapturingPageReload()) + this._resetSelection(); + + this._viewModeDidChange(); + + WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.CapturingStarted, this._capturingStarted, this); + WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.CapturingStopped, this._capturingStopped, this); + } + + // Public + + get selectedTimeline() + { + return this._selectedTimeline; + } + + set selectedTimeline(x) + { + if (this._editingInstruments) + return; + + if (this._selectedTimeline === x) + return; + + this._selectedTimeline = x; + if (this._selectedTimeline) { + let treeElement = this._treeElementsByTypeMap.get(this._selectedTimeline.type); + console.assert(treeElement, "Missing tree element for timeline", this._selectedTimeline); + + let omitFocus = true; + let wasSelectedByUser = false; + treeElement.select(omitFocus, wasSelectedByUser); + } else if (this._timelinesTreeOutline.selectedTreeElement) + this._timelinesTreeOutline.selectedTreeElement.deselect(); + } + + get editingInstruments() + { + return this._editingInstruments; + } + + get viewMode() + { + return this._viewMode; + } + + set viewMode(x) + { + if (this._editingInstruments) + return; + + if (this._viewMode === x) + return; + + this._viewMode = x; + this._viewModeDidChange(); + } + + get startTime() + { + return this._startTime; + } + + set startTime(x) + { + x = x || 0; + + if (this._startTime === x) + return; + + if (this._viewMode !== WebInspector.TimelineOverview.ViewMode.RenderingFrames) { + let selectionOffset = this.selectionStartTime - this._startTime; + this.selectionStartTime = selectionOffset + x; + } + + this._startTime = x; + + this.needsLayout(); + } + + get currentTime() + { + return this._currentTime; + } + + set currentTime(x) + { + x = x || 0; + + if (this._currentTime === x) + return; + + this._currentTime = x; + this._revealCurrentTime = true; + + this.needsLayout(); + } + + get secondsPerPixel() + { + return this._currentSettings.durationPerPixelSetting.value; + } + + set secondsPerPixel(x) + { + x = Math.min(this._currentSettings.maximumDurationPerPixel, Math.max(this._currentSettings.minimumDurationPerPixel, x)); + + if (this.secondsPerPixel === x) + return; + + if (this._pixelAlignDuration) { + x = 1 / Math.round(1 / x); + if (this.secondsPerPixel === x) + return; + } + + this._currentSettings.durationPerPixelSetting.value = x; + + this.needsLayout(); + } + + get pixelAlignDuration() + { + return this._pixelAlignDuration; + } + + set pixelAlignDuration(x) + { + if (this._pixelAlignDuration === x) + return; + + this._mouseWheelDelta = 0; + this._pixelAlignDuration = x; + if (this._pixelAlignDuration) + this.secondsPerPixel = 1 / Math.round(1 / this.secondsPerPixel); + } + + get endTime() + { + return this._endTime; + } + + set endTime(x) + { + x = x || 0; + + if (this._endTime === x) + return; + + this._endTime = x; + + this.needsLayout(); + } + + get scrollStartTime() + { + return this._currentSettings.scrollStartTime; + } + + set scrollStartTime(x) + { + x = x || 0; + + if (this.scrollStartTime === x) + return; + + this._currentSettings.scrollStartTime = x; + + this.needsLayout(); + } + + get scrollContainerWidth() + { + return this._cachedScrollContainerWidth; + } + + get visibleDuration() + { + if (isNaN(this._cachedScrollContainerWidth)) { + this._cachedScrollContainerWidth = this._scrollContainerElement.offsetWidth; + if (!this._cachedScrollContainerWidth) + this._cachedScrollContainerWidth = NaN; + } + + return this._cachedScrollContainerWidth * this.secondsPerPixel; + } + + get selectionStartTime() + { + return this._timelineRuler.selectionStartTime; + } + + set selectionStartTime(x) + { + x = x || 0; + + if (this._timelineRuler.selectionStartTime === x) + return; + + let selectionDuration = this.selectionDuration; + this._timelineRuler.selectionStartTime = x; + this._timelineRuler.selectionEndTime = x + selectionDuration; + } + + get selectionDuration() + { + return this._timelineRuler.selectionEndTime - this._timelineRuler.selectionStartTime; + } + + set selectionDuration(x) + { + x = Math.max(this._timelineRuler.minimumSelectionDuration, x); + + this._timelineRuler.selectionEndTime = this._timelineRuler.selectionStartTime + x; + } + + get height() + { + let height = 0; + for (let overviewGraph of this._overviewGraphsByTypeMap.values()) { + if (overviewGraph.visible) + height += overviewGraph.height; + } + return height; + } + + get visible() + { + return this._visible; + } + + shown() + { + this._visible = true; + + for (let [type, overviewGraph] of this._overviewGraphsByTypeMap) { + if (this._canShowTimelineType(type)) + overviewGraph.shown(); + } + + this.updateLayout(WebInspector.View.LayoutReason.Resize); + } + + hidden() + { + this._visible = false; + + for (let overviewGraph of this._overviewGraphsByTypeMap.values()) + overviewGraph.hidden(); + } + + reset() + { + for (let overviewGraph of this._overviewGraphsByTypeMap.values()) + overviewGraph.reset(); + + this._mouseWheelDelta = 0; + + this._resetSelection(); + } + + revealMarker(marker) + { + this.scrollStartTime = marker.time - (this.visibleDuration / 2); + } + + recordWasFiltered(timeline, record, filtered) + { + let overviewGraph = this._overviewGraphsByTypeMap.get(timeline.type); + console.assert(overviewGraph, "Missing overview graph for timeline type " + timeline.type); + if (!overviewGraph) + return; + + console.assert(overviewGraph.visible, "Record filtered in hidden overview graph", record); + + overviewGraph.recordWasFiltered(record, filtered); + } + + selectRecord(timeline, record) + { + let overviewGraph = this._overviewGraphsByTypeMap.get(timeline.type); + console.assert(overviewGraph, "Missing overview graph for timeline type " + timeline.type); + if (!overviewGraph) + return; + + console.assert(overviewGraph.visible, "Record selected in hidden overview graph", record); + + overviewGraph.selectedRecord = record; + } + + userSelectedRecord(record) + { + if (this._delegate && this._delegate.timelineOverviewUserSelectedRecord) + this._delegate.timelineOverviewUserSelectedRecord(this, record); + } + + updateLayoutIfNeeded() + { + if (this.layoutPending) { + super.updateLayoutIfNeeded(); + return; + } + + this._timelineRuler.updateLayoutIfNeeded(); + + for (let overviewGraph of this._overviewGraphsByTypeMap.values()) { + if (overviewGraph.visible) + overviewGraph.updateLayoutIfNeeded(); + } + } + + discontinuitiesInTimeRange(startTime, endTime) + { + return this._recording.discontinuitiesInTimeRange(startTime, endTime); + } + + // Protected + + get timelineRuler() + { + return this._timelineRuler; + } + + layout() + { + let startTime = this._startTime; + let endTime = this._endTime; + let currentTime = this._currentTime; + if (this._viewMode === WebInspector.TimelineOverview.ViewMode.RenderingFrames) { + let renderingFramesTimeline = this._recording.timelines.get(WebInspector.TimelineRecord.Type.RenderingFrame); + console.assert(renderingFramesTimeline, "Recoring missing rendering frames timeline"); + + startTime = 0; + endTime = renderingFramesTimeline.records.length; + currentTime = endTime; + } + + // Calculate the required width based on the duration and seconds per pixel. + let duration = endTime - startTime; + let newWidth = Math.ceil(duration / this.secondsPerPixel); + + // Update all relevant elements to the new required width. + this._updateElementWidth(this._scrollWidthSizer, newWidth); + + this._currentTimeMarker.time = currentTime; + + if (this._revealCurrentTime) { + this.revealMarker(this._currentTimeMarker); + this._revealCurrentTime = false; + } + + const visibleDuration = this.visibleDuration; + + // Clamp the scroll start time to match what the scroll bar would allow. + let scrollStartTime = Math.min(this.scrollStartTime, endTime - visibleDuration); + scrollStartTime = Math.max(startTime, scrollStartTime); + + this._timelineRuler.zeroTime = startTime; + this._timelineRuler.startTime = scrollStartTime; + this._timelineRuler.secondsPerPixel = this.secondsPerPixel; + + if (!this._dontUpdateScrollLeft) { + this._ignoreNextScrollEvent = true; + let scrollLeft = Math.ceil((scrollStartTime - startTime) / this.secondsPerPixel); + if (scrollLeft) + this._scrollContainerElement.scrollLeft = scrollLeft; + } + + for (let overviewGraph of this._overviewGraphsByTypeMap.values()) { + if (!overviewGraph.visible) + continue; + + overviewGraph.zeroTime = startTime; + overviewGraph.startTime = scrollStartTime; + overviewGraph.currentTime = currentTime; + overviewGraph.endTime = scrollStartTime + visibleDuration; + } + } + + sizeDidChange() + { + this._cachedScrollContainerWidth = NaN; + } + + // Private + + _updateElementWidth(element, newWidth) + { + var currentWidth = parseInt(element.style.width); + if (currentWidth !== newWidth) + element.style.width = newWidth + "px"; + } + + _handleScrollEvent(event) + { + if (this._ignoreNextScrollEvent) { + this._ignoreNextScrollEvent = false; + return; + } + + this._dontUpdateScrollLeft = true; + + let scrollOffset = this._scrollContainerElement.scrollLeft; + this.scrollStartTime = this._startTime + (scrollOffset * this.secondsPerPixel); + + // Force layout so we can update with the scroll position synchronously. + this.updateLayoutIfNeeded(); + + this._dontUpdateScrollLeft = false; + } + + _handleWheelEvent(event) + { + // Ignore cloned events that come our way, we already handled the original. + if (event.__cloned) + return; + + // Ignore wheel events while handing gestures. + if (this._handlingGesture) + return; + + // Require twice the vertical delta to overcome horizontal scrolling. This prevents most + // cases of inadvertent zooming for slightly diagonal scrolls. + if (Math.abs(event.deltaX) >= Math.abs(event.deltaY) * 0.5) { + // Clone the event to dispatch it on the scroll container. Mark it as cloned so we don't get into a loop. + let newWheelEvent = new event.constructor(event.type, event); + newWheelEvent.__cloned = true; + + this._scrollContainerElement.dispatchEvent(newWheelEvent); + return; + } + + // Remember the mouse position in time. + let mouseOffset = event.pageX - this._graphsContainerView.element.totalOffsetLeft; + let mousePositionTime = this._currentSettings.scrollStartTime + (mouseOffset * this.secondsPerPixel); + let deviceDirection = event.webkitDirectionInvertedFromDevice ? 1 : -1; + let delta = event.deltaY * (this.secondsPerPixel / WebInspector.TimelineOverview.ScrollDeltaDenominator) * deviceDirection; + + // Reset accumulated wheel delta when direction changes. + if (this._pixelAlignDuration && (delta < 0 && this._mouseWheelDelta >= 0 || delta >= 0 && this._mouseWheelDelta < 0)) + this._mouseWheelDelta = 0; + + let previousDurationPerPixel = this.secondsPerPixel; + this._mouseWheelDelta += delta; + this.secondsPerPixel += this._mouseWheelDelta; + + if (this.secondsPerPixel === this._currentSettings.minimumDurationPerPixel && delta < 0 || this.secondsPerPixel === this._currentSettings.maximumDurationPerPixel && delta >= 0) + this._mouseWheelDelta = 0; + else + this._mouseWheelDelta = previousDurationPerPixel + this._mouseWheelDelta - this.secondsPerPixel; + + // Center the zoom around the mouse based on the remembered mouse position time. + this.scrollStartTime = mousePositionTime - (mouseOffset * this.secondsPerPixel); + + event.preventDefault(); + event.stopPropagation(); + } + + _handleGestureStart(event) + { + if (this._handlingGesture) { + // FIXME: <https://webkit.org/b/151068> [Mac] Unexpected gesturestart events when already handling gesture + return; + } + + let mouseOffset = event.pageX - this._graphsContainerView.element.totalOffsetLeft; + let mousePositionTime = this._currentSettings.scrollStartTime + (mouseOffset * this.secondsPerPixel); + + this._handlingGesture = true; + this._gestureStartStartTime = mousePositionTime; + this._gestureStartDurationPerPixel = this.secondsPerPixel; + + event.preventDefault(); + event.stopPropagation(); + } + + _handleGestureChange(event) + { + // Cap zooming out at 5x. + let scale = Math.max(1 / 5, event.scale); + + let mouseOffset = event.pageX - this._graphsContainerView.element.totalOffsetLeft; + let newSecondsPerPixel = this._gestureStartDurationPerPixel / scale; + + this.secondsPerPixel = newSecondsPerPixel; + this.scrollStartTime = this._gestureStartStartTime - (mouseOffset * this.secondsPerPixel); + + event.preventDefault(); + event.stopPropagation(); + } + + _handleGestureEnd(event) + { + this._handlingGesture = false; + this._gestureStartStartTime = NaN; + this._gestureStartDurationPerPixel = NaN; + } + + _instrumentAdded(instrumentOrEvent) + { + let instrument = instrumentOrEvent instanceof WebInspector.Instrument ? instrumentOrEvent : instrumentOrEvent.data.instrument; + console.assert(instrument instanceof WebInspector.Instrument, instrument); + + let timeline = this._recording.timelineForInstrument(instrument); + console.assert(!this._overviewGraphsByTypeMap.has(timeline.type), timeline); + console.assert(!this._treeElementsByTypeMap.has(timeline.type), timeline); + + let treeElement = new WebInspector.TimelineTreeElement(timeline); + let insertionIndex = insertionIndexForObjectInListSortedByFunction(treeElement, this._timelinesTreeOutline.children, this._compareTimelineTreeElements.bind(this)); + this._timelinesTreeOutline.insertChild(treeElement, insertionIndex); + this._treeElementsByTypeMap.set(timeline.type, treeElement); + + let overviewGraph = WebInspector.TimelineOverviewGraph.createForTimeline(timeline, this); + overviewGraph.addEventListener(WebInspector.TimelineOverviewGraph.Event.RecordSelected, this._recordSelected, this); + this._overviewGraphsByTypeMap.set(timeline.type, overviewGraph); + this._graphsContainerView.insertSubviewBefore(overviewGraph, this._graphsContainerView.subviews[insertionIndex]); + + treeElement.element.style.height = overviewGraph.height + "px"; + + if (!this._canShowTimelineType(timeline.type)) { + overviewGraph.hidden(); + treeElement.hidden = true; + } + } + + _instrumentRemoved(event) + { + let instrument = event.data.instrument; + console.assert(instrument instanceof WebInspector.Instrument, instrument); + + let timeline = this._recording.timelineForInstrument(instrument); + let overviewGraph = this._overviewGraphsByTypeMap.get(timeline.type); + console.assert(overviewGraph, "Missing overview graph for timeline type", timeline.type); + + let treeElement = this._treeElementsByTypeMap.get(timeline.type); + let shouldSuppressOnDeselect = false; + let shouldSuppressSelectSibling = true; + this._timelinesTreeOutline.removeChild(treeElement, shouldSuppressOnDeselect, shouldSuppressSelectSibling); + + overviewGraph.removeEventListener(WebInspector.TimelineOverviewGraph.Event.RecordSelected, this._recordSelected, this); + this._graphsContainerView.removeSubview(overviewGraph); + + this._overviewGraphsByTypeMap.delete(timeline.type); + this._treeElementsByTypeMap.delete(timeline.type); + } + + _markerAdded(event) + { + this._timelineRuler.addMarker(event.data.marker); + } + + _timelineRulerMouseDown(event) + { + this._timelineRulerSelectionChanged = false; + } + + _timelineRulerMouseClicked(event) + { + if (this._timelineRulerSelectionChanged) + return; + + for (let overviewGraph of this._overviewGraphsByTypeMap.values()) { + let graphRect = overviewGraph.element.getBoundingClientRect(); + if (!(event.pageX >= graphRect.left && event.pageX <= graphRect.right && event.pageY >= graphRect.top && event.pageY <= graphRect.bottom)) + continue; + + // Clone the event to dispatch it on the overview graph element. + let newClickEvent = new event.constructor(event.type, event); + overviewGraph.element.dispatchEvent(newClickEvent); + return; + } + } + + _timeRangeSelectionChanged(event) + { + this._timelineRulerSelectionChanged = true; + + let startTime = this._viewMode === WebInspector.TimelineOverview.ViewMode.Timelines ? this._startTime : 0; + this._currentSettings.selectionStartValueSetting.value = this.selectionStartTime - startTime; + this._currentSettings.selectionDurationSetting.value = this.selectionDuration; + + this.dispatchEventToListeners(WebInspector.TimelineOverview.Event.TimeRangeSelectionChanged); + } + + _recordSelected(event) + { + for (let [type, overviewGraph] of this._overviewGraphsByTypeMap) { + if (overviewGraph !== event.target) + continue; + + let timeline = this._recording.timelines.get(type); + console.assert(timeline, "Timeline recording missing timeline type", type); + this.dispatchEventToListeners(WebInspector.TimelineOverview.Event.RecordSelected, {timeline, record: event.data.record}); + return; + } + } + + _resetSelection() + { + function reset(settings) + { + settings.durationPerPixelSetting.reset(); + settings.selectionStartValueSetting.reset(); + settings.selectionDurationSetting.reset(); + } + + reset(this._timelinesViewModeSettings); + if (this._renderingFramesViewModeSettings) + reset(this._renderingFramesViewModeSettings); + + this.secondsPerPixel = this._currentSettings.durationPerPixelSetting.value; + this.selectionStartTime = this._currentSettings.selectionStartValueSetting.value; + this.selectionDuration = this._currentSettings.selectionDurationSetting.value; + } + + _recordingReset(event) + { + this._timelineRuler.clearMarkers(); + this._timelineRuler.addMarker(this._currentTimeMarker); + } + + _canShowTimelineType(type) + { + let timelineViewMode = WebInspector.TimelineOverview.ViewMode.Timelines; + if (type === WebInspector.TimelineRecord.Type.RenderingFrame) + timelineViewMode = WebInspector.TimelineOverview.ViewMode.RenderingFrames; + + return timelineViewMode === this._viewMode; + } + + _viewModeDidChange() + { + let startTime = 0; + let isRenderingFramesMode = this._viewMode === WebInspector.TimelineOverview.ViewMode.RenderingFrames; + if (isRenderingFramesMode) { + this._timelineRuler.minimumSelectionDuration = 1; + this._timelineRuler.snapInterval = 1; + this._timelineRuler.formatLabelCallback = (value) => value.toFixed(0); + } else { + this._timelineRuler.minimumSelectionDuration = 0.01; + this._timelineRuler.snapInterval = NaN; + this._timelineRuler.formatLabelCallback = null; + + startTime = this._startTime; + } + + this.pixelAlignDuration = isRenderingFramesMode; + this.selectionStartTime = this._currentSettings.selectionStartValueSetting.value + startTime; + this.selectionDuration = this._currentSettings.selectionDurationSetting.value; + + for (let [type, overviewGraph] of this._overviewGraphsByTypeMap) { + let treeElement = this._treeElementsByTypeMap.get(type); + console.assert(treeElement, "Missing tree element for timeline type", type); + + treeElement.hidden = !this._canShowTimelineType(type); + if (treeElement.hidden) + overviewGraph.hidden(); + else + overviewGraph.shown(); + } + + this.element.classList.toggle("frames", isRenderingFramesMode); + + this.updateLayout(WebInspector.View.LayoutReason.Resize); + } + + _createViewModeSettings(viewMode, minimumDurationPerPixel, maximumDurationPerPixel, durationPerPixel, selectionStartValue, selectionDuration) + { + durationPerPixel = Math.min(maximumDurationPerPixel, Math.max(minimumDurationPerPixel, durationPerPixel)); + + let durationPerPixelSetting = new WebInspector.Setting(viewMode + "-duration-per-pixel", durationPerPixel); + let selectionStartValueSetting = new WebInspector.Setting(viewMode + "-selection-start-value", selectionStartValue); + let selectionDurationSetting = new WebInspector.Setting(viewMode + "-selection-duration", selectionDuration); + + return { + scrollStartTime: 0, + minimumDurationPerPixel, + maximumDurationPerPixel, + durationPerPixelSetting, + selectionStartValueSetting, + selectionDurationSetting + }; + } + + get _currentSettings() + { + return this._viewMode === WebInspector.TimelineOverview.ViewMode.Timelines ? this._timelinesViewModeSettings : this._renderingFramesViewModeSettings; + } + + _timelinesTreeSelectionDidChange(event) + { + function updateGraphSelectedState(timeline, selected) + { + let overviewGraph = this._overviewGraphsByTypeMap.get(timeline.type); + console.assert(overviewGraph, "Missing overview graph for timeline", timeline); + overviewGraph.selected = selected; + } + + let selectedTreeElement = event.data.selectedElement; + let deselectedTreeElement = event.data.deselectedElement; + let timeline = null; + if (selectedTreeElement) { + timeline = selectedTreeElement.representedObject; + console.assert(timeline instanceof WebInspector.Timeline, timeline); + console.assert(this._recording.timelines.get(timeline.type) === timeline, timeline); + + updateGraphSelectedState.call(this, timeline, true); + } + + if (deselectedTreeElement) + updateGraphSelectedState.call(this, deselectedTreeElement.representedObject, false); + + this._selectedTimeline = timeline; + this.dispatchEventToListeners(WebInspector.TimelineOverview.Event.TimelineSelected); + } + + _toggleEditingInstruments(event) + { + if (this._editingInstruments) + this._stopEditingInstruments(); + else + this._startEditingInstruments(); + } + + _editingInstrumentsDidChange() + { + this.element.classList.toggle(WebInspector.TimelineOverview.EditInstrumentsStyleClassName, this._editingInstruments); + this._timelineRuler.enabled = !this._editingInstruments; + + this._updateWheelAndGestureHandlers(); + this._updateEditInstrumentsButton(); + + this.dispatchEventToListeners(WebInspector.TimelineOverview.Event.EditingInstrumentsDidChange); + } + + _updateEditInstrumentsButton() + { + let newLabel = this._editingInstruments ? WebInspector.UIString("Done") : WebInspector.UIString("Edit"); + this._editInstrumentsButton.label = newLabel; + this._editInstrumentsButton.activated = this._editingInstruments; + this._editInstrumentsButton.enabled = !WebInspector.timelineManager.isCapturing(); + } + + _updateWheelAndGestureHandlers() + { + if (this._editingInstruments) { + this.element.removeEventListener("wheel", this._handleWheelEventListener); + this.element.removeEventListener("gesturestart", this._handleGestureStartEventListener); + this.element.removeEventListener("gesturechange", this._handleGestureChangeEventListener); + this.element.removeEventListener("gestureend", this._handleGestureEndEventListener); + this._handleWheelEventListener = null; + this._handleGestureStartEventListener = null; + this._handleGestureChangeEventListener = null; + this._handleGestureEndEventListener = null; + } else { + this._handleWheelEventListener = this._handleWheelEvent.bind(this); + this._handleGestureStartEventListener = this._handleGestureStart.bind(this); + this._handleGestureChangeEventListener = this._handleGestureChange.bind(this); + this._handleGestureEndEventListener = this._handleGestureEnd.bind(this); + this.element.addEventListener("wheel", this._handleWheelEventListener); + this.element.addEventListener("gesturestart", this._handleGestureStartEventListener); + this.element.addEventListener("gesturechange", this._handleGestureChangeEventListener); + this.element.addEventListener("gestureend", this._handleGestureEndEventListener); + } + } + + _startEditingInstruments() + { + console.assert(this._viewMode === WebInspector.TimelineOverview.ViewMode.Timelines); + + if (this._editingInstruments) + return; + + this._editingInstruments = true; + + for (let type of this._instrumentTypes) { + let treeElement = this._treeElementsByTypeMap.get(type); + if (!treeElement) { + let timeline = this._recording.timelines.get(type); + console.assert(timeline, "Missing timeline for type " + type); + + const placeholder = true; + treeElement = new WebInspector.TimelineTreeElement(timeline, placeholder); + + let insertionIndex = insertionIndexForObjectInListSortedByFunction(treeElement, this._timelinesTreeOutline.children, this._compareTimelineTreeElements.bind(this)); + this._timelinesTreeOutline.insertChild(treeElement, insertionIndex); + + let placeholderGraph = new WebInspector.View; + placeholderGraph.element.classList.add("timeline-overview-graph"); + treeElement[WebInspector.TimelineOverview.PlaceholderOverviewGraph] = placeholderGraph; + this._graphsContainerView.insertSubviewBefore(placeholderGraph, this._graphsContainerView.subviews[insertionIndex]); + } + + treeElement.editing = true; + treeElement.addEventListener(WebInspector.TimelineTreeElement.Event.EnabledDidChange, this._timelineTreeElementEnabledDidChange, this); + } + + this._editingInstrumentsDidChange(); + } + + _stopEditingInstruments() + { + if (!this._editingInstruments) + return; + + this._editingInstruments = false; + + let instruments = this._recording.instruments; + for (let treeElement of this._treeElementsByTypeMap.values()) { + if (treeElement.status.checked) { + treeElement.editing = false; + treeElement.removeEventListener(WebInspector.TimelineTreeElement.Event.EnabledDidChange, this._timelineTreeElementEnabledDidChange, this); + continue; + } + + let timelineInstrument = instruments.find((instrument) => instrument.timelineRecordType === treeElement.representedObject.type); + this._recording.removeInstrument(timelineInstrument); + } + + let placeholderTreeElements = this._timelinesTreeOutline.children.filter((treeElement) => treeElement.placeholder); + for (let treeElement of placeholderTreeElements) { + this._timelinesTreeOutline.removeChild(treeElement); + + let placeholderGraph = treeElement[WebInspector.TimelineOverview.PlaceholderOverviewGraph]; + console.assert(placeholderGraph); + this._graphsContainerView.removeSubview(placeholderGraph); + + if (treeElement.status.checked) { + let instrument = WebInspector.Instrument.createForTimelineType(treeElement.representedObject.type); + this._recording.addInstrument(instrument); + } + } + + let instrumentTypes = instruments.map((instrument) => instrument.timelineRecordType); + WebInspector.timelineManager.enabledTimelineTypes = instrumentTypes; + + this._editingInstrumentsDidChange(); + } + + _capturingStarted() + { + this._editInstrumentsButton.enabled = false; + this._stopEditingInstruments(); + } + + _capturingStopped() + { + this._editInstrumentsButton.enabled = true; + } + + _compareTimelineTreeElements(a, b) + { + let aTimelineType = a.representedObject.type; + let bTimelineType = b.representedObject.type; + + // Always sort the Rendering Frames timeline last. + if (aTimelineType === WebInspector.TimelineRecord.Type.RenderingFrame) + return 1; + if (bTimelineType === WebInspector.TimelineRecord.Type.RenderingFrame) + return -1; + + if (a.placeholder !== b.placeholder) + return a.placeholder ? 1 : -1; + + let aTimelineIndex = this._instrumentTypes.indexOf(aTimelineType); + let bTimelineIndex = this._instrumentTypes.indexOf(bTimelineType); + return aTimelineIndex - bTimelineIndex; + } + + _timelineTreeElementEnabledDidChange(event) + { + let enabled = this._timelinesTreeOutline.children.some((treeElement) => { + let timelineType = treeElement.representedObject.type; + return this._canShowTimelineType(timelineType) && treeElement.status.checked; + }); + + this._editInstrumentsButton.enabled = enabled; + } +}; + +WebInspector.TimelineOverview.PlaceholderOverviewGraph = Symbol("placeholder-overview-graph"); + +WebInspector.TimelineOverview.ScrollDeltaDenominator = 500; +WebInspector.TimelineOverview.EditInstrumentsStyleClassName = "edit-instruments"; +WebInspector.TimelineOverview.MinimumDurationPerPixel = 0.0001; +WebInspector.TimelineOverview.MaximumDurationPerPixel = 60; + +WebInspector.TimelineOverview.ViewMode = { + Timelines: "timeline-overview-view-mode-timelines", + RenderingFrames: "timeline-overview-view-mode-rendering-frames" +}; + +WebInspector.TimelineOverview.Event = { + EditingInstrumentsDidChange: "editing-instruments-did-change", + RecordSelected: "timeline-overview-record-selected", + TimelineSelected: "timeline-overview-timeline-selected", + TimeRangeSelectionChanged: "timeline-overview-time-range-selection-changed" +}; |