| /* |
| * Copyright (C) 2013, 2015 Apple Inc. All rights reserved. |
| * Copyright (C) 2015 University of Washington. |
| * |
| * 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. |
| */ |
| |
| WI.TimelineRecordingContentView = class TimelineRecordingContentView extends WI.ContentView |
| { |
| constructor(recording) |
| { |
| super(recording); |
| |
| this._recording = recording; |
| |
| this.element.classList.add("timeline-recording"); |
| |
| this._timelineOverview = new WI.TimelineOverview(this._recording); |
| this._timelineOverview.addEventListener(WI.TimelineOverview.Event.TimeRangeSelectionChanged, this._timeRangeSelectionChanged, this); |
| this._timelineOverview.addEventListener(WI.TimelineOverview.Event.RecordSelected, this._recordSelected, this); |
| this._timelineOverview.addEventListener(WI.TimelineOverview.Event.TimelineSelected, this._timelineSelected, this); |
| this._timelineOverview.addEventListener(WI.TimelineOverview.Event.EditingInstrumentsDidChange, this._editingInstrumentsDidChange, this); |
| this.addSubview(this._timelineOverview); |
| |
| const disableBackForward = true; |
| const disableFindBanner = true; |
| this._timelineContentBrowser = new WI.ContentBrowser(null, this, disableBackForward, disableFindBanner); |
| this._timelineContentBrowser.addEventListener(WI.ContentBrowser.Event.CurrentContentViewDidChange, this._currentContentViewDidChange, this); |
| |
| this._entireRecordingPathComponent = this._createTimelineRangePathComponent(WI.UIString("Entire Recording")); |
| this._timelineSelectionPathComponent = this._createTimelineRangePathComponent(); |
| this._timelineSelectionPathComponent.previousSibling = this._entireRecordingPathComponent; |
| this._selectedTimeRangePathComponent = this._entireRecordingPathComponent; |
| |
| this._filterBarNavigationItem = new WI.FilterBarNavigationItem; |
| this._filterBarNavigationItem.filterBar.addEventListener(WI.FilterBar.Event.FilterDidChange, this._filterDidChange, this); |
| this._timelineContentBrowser.navigationBar.addNavigationItem(this._filterBarNavigationItem); |
| this.addSubview(this._timelineContentBrowser); |
| |
| if (WI.sharedApp.isWebDebuggable()) { |
| this._autoStopCheckboxNavigationItem = new WI.CheckboxNavigationItem("auto-stop-recording", WI.UIString("Stop recording once page loads"), WI.settings.timelinesAutoStop.value); |
| this._autoStopCheckboxNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low; |
| this._autoStopCheckboxNavigationItem.addEventListener(WI.CheckboxNavigationItem.Event.CheckedDidChange, this._handleAutoStopCheckboxCheckedDidChange, this); |
| |
| WI.settings.timelinesAutoStop.addEventListener(WI.Setting.Event.Changed, this._handleTimelinesAutoStopSettingChanged, this); |
| } |
| |
| this._exportButtonNavigationItem = new WI.ButtonNavigationItem("export", WI.UIString("Export"), "Images/Export.svg", 15, 15); |
| this._exportButtonNavigationItem.toolTip = WI.UIString("Export (%s)").format(WI.saveKeyboardShortcut.displayName); |
| this._exportButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText; |
| this._exportButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low; |
| this._exportButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._exportButtonNavigationItemClicked, this); |
| this._exportButtonNavigationItem.enabled = false; |
| |
| this._importButtonNavigationItem = new WI.ButtonNavigationItem("import", WI.UIString("Import"), "Images/Import.svg", 15, 15); |
| this._importButtonNavigationItem.toolTip = WI.UIString("Import"); |
| this._importButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText; |
| this._importButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low; |
| this._importButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._importButtonNavigationItemClicked, this); |
| |
| this._clearTimelineNavigationItem = new WI.ButtonNavigationItem("clear-timeline", WI.UIString("Clear Timeline (%s)").format(WI.clearKeyboardShortcut.displayName), "Images/NavigationItemTrash.svg", 15, 15); |
| this._clearTimelineNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low; |
| this._clearTimelineNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._clearTimeline, this); |
| |
| this._overviewTimelineView = new WI.OverviewTimelineView(recording); |
| this._overviewTimelineView.secondsPerPixel = this._timelineOverview.secondsPerPixel; |
| |
| this._progressView = new WI.TimelineRecordingProgressView; |
| this._timelineContentBrowser.addSubview(this._progressView); |
| |
| this._timelineViewMap = new Map; |
| this._pathComponentMap = new Map; |
| |
| this._updating = false; |
| this._currentTime = NaN; |
| this._lastUpdateTimestamp = NaN; |
| this._startTimeNeedsReset = true; |
| this._renderingFrameTimeline = null; |
| |
| this._recording.addEventListener(WI.TimelineRecording.Event.InstrumentAdded, this._instrumentAdded, this); |
| this._recording.addEventListener(WI.TimelineRecording.Event.InstrumentRemoved, this._instrumentRemoved, this); |
| this._recording.addEventListener(WI.TimelineRecording.Event.Reset, this._recordingReset, this); |
| this._recording.addEventListener(WI.TimelineRecording.Event.Unloaded, this._recordingUnloaded, this); |
| |
| WI.timelineManager.addEventListener(WI.TimelineManager.Event.CapturingStateChanged, this._handleTimelineCapturingStateChanged, this); |
| |
| WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.Paused, this._debuggerPaused, this); |
| WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.Resumed, this._debuggerResumed, this); |
| |
| WI.ContentView.addEventListener(WI.ContentView.Event.SelectionPathComponentsDidChange, this._contentViewSelectionPathComponentDidChange, this); |
| WI.ContentView.addEventListener(WI.ContentView.Event.SupplementalRepresentedObjectsDidChange, this._contentViewSupplementalRepresentedObjectsDidChange, this); |
| |
| WI.TimelineView.addEventListener(WI.TimelineView.Event.RecordWasFiltered, this._handleTimelineViewRecordFiltered, this); |
| WI.TimelineView.addEventListener(WI.TimelineView.Event.RecordWasSelected, this._handleTimelineViewRecordSelected, this); |
| WI.TimelineView.addEventListener(WI.TimelineView.Event.ScannerShow, this._handleTimelineViewScannerShow, this); |
| WI.TimelineView.addEventListener(WI.TimelineView.Event.ScannerHide, this._handleTimelineViewScannerHide, this); |
| WI.TimelineView.addEventListener(WI.TimelineView.Event.NeedsEntireSelectedRange, this._handleTimelineViewNeedsEntireSelectedRange, this); |
| |
| WI.notifications.addEventListener(WI.Notification.VisibilityStateDidChange, this._inspectorVisibilityStateChanged, this); |
| |
| for (let instrument of this._recording.instruments) |
| this._instrumentAdded(instrument); |
| |
| this.showOverviewTimelineView(); |
| |
| if (this._recording.imported) { |
| let {startTime, endTime} = this._recording; |
| this._updateTimes(startTime, endTime, endTime); |
| } |
| } |
| |
| // Public |
| |
| showOverviewTimelineView() |
| { |
| this._timelineContentBrowser.showContentView(this._overviewTimelineView); |
| } |
| |
| showTimelineViewForTimeline(timeline) |
| { |
| console.assert(timeline instanceof WI.Timeline, timeline); |
| console.assert(this._timelineViewMap.has(timeline), timeline); |
| if (!this._timelineViewMap.has(timeline)) |
| return; |
| |
| let contentView = this._timelineContentBrowser.showContentView(this._timelineViewMap.get(timeline)); |
| |
| // FIXME: `WI.HeapAllocationsTimelineView` relies on it's `_dataGrid` for determining what |
| // object is currently selected. If that `_dataGrid` hasn't yet called `layout()` when first |
| // shown, we will lose the selection. |
| if (!contentView.didInitialLayout) |
| contentView.updateLayout(); |
| } |
| |
| get supportsSplitContentBrowser() |
| { |
| // The layout of the overview and split content browser don't work well. |
| return false; |
| } |
| |
| get selectionPathComponents() |
| { |
| if (!this._timelineContentBrowser.currentContentView) |
| return []; |
| |
| let pathComponents = []; |
| let representedObject = this._timelineContentBrowser.currentContentView.representedObject; |
| if (representedObject instanceof WI.Timeline) |
| pathComponents.push(this._pathComponentMap.get(representedObject)); |
| |
| pathComponents.push(this._selectedTimeRangePathComponent); |
| return pathComponents; |
| } |
| |
| get supplementalRepresentedObjects() |
| { |
| if (!this._timelineContentBrowser.currentContentView) |
| return []; |
| return this._timelineContentBrowser.currentContentView.supplementalRepresentedObjects; |
| } |
| |
| get navigationItems() |
| { |
| let navigationItems = []; |
| if (this._autoStopCheckboxNavigationItem) |
| navigationItems.push(this._autoStopCheckboxNavigationItem); |
| navigationItems.push(new WI.DividerNavigationItem); |
| navigationItems.push(this._importButtonNavigationItem); |
| navigationItems.push(this._exportButtonNavigationItem); |
| navigationItems.push(new WI.DividerNavigationItem); |
| navigationItems.push(this._clearTimelineNavigationItem); |
| return navigationItems; |
| } |
| |
| get handleCopyEvent() |
| { |
| let currentContentView = this._timelineContentBrowser.currentContentView; |
| return currentContentView && typeof currentContentView.handleCopyEvent === "function" ? currentContentView.handleCopyEvent.bind(currentContentView) : null; |
| } |
| |
| get supportsSave() |
| { |
| return this._recording.canExport(); |
| } |
| |
| get saveData() |
| { |
| return {customSaveHandler: () => { this._exportTimelineRecording(); }}; |
| } |
| |
| get currentTimelineView() |
| { |
| return this._timelineContentBrowser.currentContentView; |
| } |
| |
| shown() |
| { |
| super.shown(); |
| |
| this._timelineOverview.shown(); |
| this._timelineContentBrowser.shown(); |
| |
| this._clearTimelineNavigationItem.enabled = !this._recording.readonly && !isNaN(this._recording.startTime); |
| this._exportButtonNavigationItem.enabled = this._recording.canExport(); |
| |
| this._currentContentViewDidChange(); |
| |
| if (!this._updating && WI.timelineManager.activeRecording === this._recording && WI.timelineManager.isCapturing()) |
| this._startUpdatingCurrentTime(this._currentTime); |
| } |
| |
| hidden() |
| { |
| super.hidden(); |
| |
| this._timelineOverview.hidden(); |
| this._timelineContentBrowser.hidden(); |
| |
| if (this._updating) |
| this._stopUpdatingCurrentTime(); |
| } |
| |
| closed() |
| { |
| super.closed(); |
| |
| this._timelineContentBrowser.contentViewContainer.closeAllContentViews(); |
| |
| this._recording.removeEventListener(null, null, this); |
| |
| WI.timelineManager.removeEventListener(null, null, this); |
| WI.debuggerManager.removeEventListener(null, null, this); |
| WI.ContentView.removeEventListener(null, null, this); |
| } |
| |
| canGoBack() |
| { |
| return this._timelineContentBrowser.canGoBack(); |
| } |
| |
| canGoForward() |
| { |
| return this._timelineContentBrowser.canGoForward(); |
| } |
| |
| goBack() |
| { |
| this._timelineContentBrowser.goBack(); |
| } |
| |
| goForward() |
| { |
| this._timelineContentBrowser.goForward(); |
| } |
| |
| handleClearShortcut(event) |
| { |
| this._clearTimeline(); |
| } |
| |
| get canFocusFilterBar() |
| { |
| return !this._filterBarNavigationItem.hidden; |
| } |
| |
| focusFilterBar() |
| { |
| this._filterBarNavigationItem.filterBar.focus(); |
| } |
| |
| // ContentBrowser delegate |
| |
| contentBrowserTreeElementForRepresentedObject(contentBrowser, representedObject) |
| { |
| if (!(representedObject instanceof WI.Timeline) && !(representedObject instanceof WI.TimelineRecording)) |
| return null; |
| |
| let iconClassName; |
| let title; |
| if (representedObject instanceof WI.Timeline) { |
| iconClassName = WI.TimelineTabContentView.iconClassNameForTimelineType(representedObject.type); |
| title = WI.UIString("Details"); |
| } else { |
| iconClassName = WI.TimelineTabContentView.StopwatchIconStyleClass; |
| title = WI.UIString("Overview"); |
| } |
| |
| const hasChildren = false; |
| return new WI.GeneralTreeElement(iconClassName, title, representedObject, hasChildren); |
| } |
| |
| // Private |
| |
| _currentContentViewDidChange(event) |
| { |
| let newViewMode; |
| let timelineView = this.currentTimelineView; |
| if (timelineView && timelineView.representedObject.type === WI.TimelineRecord.Type.RenderingFrame) |
| newViewMode = WI.TimelineOverview.ViewMode.RenderingFrames; |
| else |
| newViewMode = WI.TimelineOverview.ViewMode.Timelines; |
| |
| this._timelineOverview.viewMode = newViewMode; |
| this._updateTimelineOverviewHeight(); |
| this._updateProgressView(); |
| this._updateFilterBar(); |
| |
| if (timelineView) { |
| this._updateTimelineViewTimes(timelineView); |
| this._filterDidChange(); |
| |
| let timeline = null; |
| if (timelineView.representedObject instanceof WI.Timeline) |
| timeline = timelineView.representedObject; |
| |
| this._timelineOverview.selectedTimeline = timeline; |
| } |
| |
| this.dispatchEventToListeners(WI.ContentView.Event.SelectionPathComponentsDidChange); |
| this.dispatchEventToListeners(WI.ContentView.Event.NavigationItemsDidChange); |
| } |
| |
| _timelinePathComponentSelected(event) |
| { |
| let selectedTimeline = event.data.pathComponent.representedObject; |
| this.showTimelineViewForTimeline(selectedTimeline); |
| } |
| |
| _timeRangePathComponentSelected(event) |
| { |
| let selectedPathComponent = event.data.pathComponent; |
| if (selectedPathComponent === this._selectedTimeRangePathComponent) |
| return; |
| |
| let timelineRuler = this._timelineOverview.timelineRuler; |
| if (selectedPathComponent === this._entireRecordingPathComponent) |
| timelineRuler.selectEntireRange(); |
| else { |
| let timelineRange = selectedPathComponent.representedObject; |
| timelineRuler.selectionStartTime = timelineRuler.zeroTime + timelineRange.startValue; |
| timelineRuler.selectionEndTime = timelineRuler.zeroTime + timelineRange.endValue; |
| } |
| } |
| |
| _contentViewSelectionPathComponentDidChange(event) |
| { |
| if (!this.visible) |
| return; |
| |
| if (event.target !== this._timelineContentBrowser.currentContentView) |
| return; |
| |
| this._updateFilterBar(); |
| |
| this.dispatchEventToListeners(WI.ContentView.Event.SelectionPathComponentsDidChange); |
| |
| if (this.currentTimelineView === this._overviewTimelineView) |
| return; |
| |
| let record = null; |
| if (this.currentTimelineView.selectionPathComponents) { |
| let recordPathComponent = this.currentTimelineView.selectionPathComponents.find((element) => element.representedObject instanceof WI.TimelineRecord); |
| record = recordPathComponent ? recordPathComponent.representedObject : null; |
| } |
| |
| this._timelineOverview.selectRecord(event.target.representedObject, record); |
| } |
| |
| _contentViewSupplementalRepresentedObjectsDidChange(event) |
| { |
| if (event.target !== this._timelineContentBrowser.currentContentView) |
| return; |
| this.dispatchEventToListeners(WI.ContentView.Event.SupplementalRepresentedObjectsDidChange); |
| } |
| |
| _inspectorVisibilityStateChanged() |
| { |
| if (WI.timelineManager.activeRecording !== this._recording) |
| return; |
| |
| // Stop updating since the results won't be rendered anyway. |
| if (!WI.visible && this._updating) { |
| this._stopUpdatingCurrentTime(); |
| return; |
| } |
| |
| // Nothing else to do if the current time was not being updated. |
| if (!WI.visible) |
| return; |
| |
| let {startTime, endTime} = this.representedObject; |
| if (!WI.timelineManager.isCapturing()) { |
| // Force the overview to render data from the entire recording. |
| // This is necessary if the recording was started when the inspector was not |
| // visible because the views were never updated with currentTime/endTime. |
| this._updateTimes(startTime, endTime, endTime); |
| return; |
| } |
| |
| this._startUpdatingCurrentTime(endTime); |
| } |
| |
| _update(timestamp) |
| { |
| // FIXME: <https://webkit.org/b/153634> Web Inspector: some background tabs think they are the foreground tab and do unnecessary work |
| if (!(WI.tabBrowser.selectedTabContentView instanceof WI.TimelineTabContentView)) |
| return; |
| |
| if (this._waitingToResetCurrentTime) { |
| requestAnimationFrame(this._updateCallback); |
| return; |
| } |
| |
| var startTime = this._recording.startTime; |
| var currentTime = this._currentTime || startTime; |
| var endTime = this._recording.endTime; |
| var timespanSinceLastUpdate = (timestamp - this._lastUpdateTimestamp) / 1000 || 0; |
| |
| currentTime += timespanSinceLastUpdate; |
| |
| this._updateTimes(startTime, currentTime, endTime); |
| |
| // Only stop updating if the current time is greater than the end time, or the end time is NaN. |
| // The recording end time will be NaN if no records were added. |
| if (!this._updating && (currentTime >= endTime || isNaN(endTime))) { |
| if (this.visible) |
| this._lastUpdateTimestamp = NaN; |
| return; |
| } |
| |
| this._lastUpdateTimestamp = timestamp; |
| |
| requestAnimationFrame(this._updateCallback); |
| } |
| |
| _updateTimes(startTime, currentTime, endTime) |
| { |
| if (this._startTimeNeedsReset && !isNaN(startTime)) { |
| this._timelineOverview.startTime = startTime; |
| this._overviewTimelineView.zeroTime = startTime; |
| for (let timelineView of this._timelineViewMap.values()) |
| timelineView.zeroTime = startTime; |
| |
| this._startTimeNeedsReset = false; |
| } |
| |
| this._timelineOverview.endTime = Math.max(endTime, currentTime); |
| |
| this._currentTime = currentTime; |
| this._timelineOverview.currentTime = currentTime; |
| |
| if (this.currentTimelineView) |
| this._updateTimelineViewTimes(this.currentTimelineView); |
| |
| // Force a layout now since we are already in an animation frame and don't need to delay it until the next. |
| this._timelineOverview.updateLayoutIfNeeded(); |
| if (this.currentTimelineView) |
| this.currentTimelineView.updateLayoutIfNeeded(); |
| } |
| |
| _startUpdatingCurrentTime(startTime) |
| { |
| console.assert(!this._updating); |
| if (this._updating) |
| return; |
| |
| // Don't update the current time if the Inspector is not visible, as the requestAnimationFrames won't work. |
| if (!WI.visible) |
| return; |
| |
| if (typeof startTime === "number") |
| this._currentTime = startTime; |
| else if (!isNaN(this._currentTime)) { |
| // This happens when you stop and later restart recording. |
| // COMPATIBILITY (iOS 9): Timeline.recordingStarted events did not include a timestamp. |
| // We likely need to jump into the future to a better current time which we can |
| // ascertained from a new incoming timeline record, so we wait for a Timeline to update. |
| console.assert(!this._waitingToResetCurrentTime); |
| this._waitingToResetCurrentTime = true; |
| this._recording.addEventListener(WI.TimelineRecording.Event.TimesUpdated, this._recordingTimesUpdated, this); |
| } |
| |
| this._updating = true; |
| |
| if (!this._updateCallback) |
| this._updateCallback = this._update.bind(this); |
| |
| requestAnimationFrame(this._updateCallback); |
| } |
| |
| _stopUpdatingCurrentTime() |
| { |
| console.assert(this._updating); |
| this._updating = false; |
| |
| if (this._waitingToResetCurrentTime) { |
| // Did not get any event while waiting for the current time, but we should stop waiting. |
| this._recording.removeEventListener(WI.TimelineRecording.Event.TimesUpdated, this._recordingTimesUpdated, this); |
| this._waitingToResetCurrentTime = false; |
| } |
| } |
| |
| _handleTimelineCapturingStateChanged(event) |
| { |
| let {startTime, endTime} = event.data; |
| |
| this._updateProgressView(); |
| |
| switch (WI.timelineManager.capturingState) { |
| case WI.TimelineManager.CapturingState.Active: |
| if (!this._updating) |
| this._startUpdatingCurrentTime(startTime); |
| |
| this._clearTimelineNavigationItem.enabled = !this._recording.readonly; |
| this._exportButtonNavigationItem.enabled = false; |
| break; |
| |
| case WI.TimelineManager.CapturingState.Inactive: |
| if (this._updating) |
| this._stopUpdatingCurrentTime(); |
| |
| if (this.currentTimelineView) |
| this._updateTimelineViewTimes(this.currentTimelineView); |
| |
| this._exportButtonNavigationItem.enabled = this._recording.canExport(); |
| break; |
| } |
| } |
| |
| _debuggerPaused(event) |
| { |
| if (this._updating) |
| this._stopUpdatingCurrentTime(); |
| } |
| |
| _debuggerResumed(event) |
| { |
| if (!this._updating) |
| this._startUpdatingCurrentTime(); |
| } |
| |
| _recordingTimesUpdated(event) |
| { |
| if (!this._waitingToResetCurrentTime) |
| return; |
| |
| // COMPATIBILITY (iOS 9): Timeline.recordingStarted events did not include a new startTime. |
| // Make the current time be the start time of the last added record. This is the best way |
| // currently to jump to the right period of time after recording starts. |
| |
| for (var timeline of this._recording.timelines.values()) { |
| var lastRecord = timeline.records.lastValue; |
| if (!lastRecord) |
| continue; |
| this._currentTime = Math.max(this._currentTime, lastRecord.startTime); |
| } |
| |
| this._recording.removeEventListener(WI.TimelineRecording.Event.TimesUpdated, this._recordingTimesUpdated, this); |
| this._waitingToResetCurrentTime = false; |
| } |
| |
| _handleAutoStopCheckboxCheckedDidChange(event) |
| { |
| WI.settings.timelinesAutoStop.value = this._autoStopCheckboxNavigationItem.checked; |
| } |
| |
| _handleTimelinesAutoStopSettingChanged(event) |
| { |
| this._autoStopCheckboxNavigationItem.checked = WI.settings.timelinesAutoStop.value; |
| } |
| |
| _exportTimelineRecording() |
| { |
| let json = { |
| version: WI.TimelineRecording.SerializationVersion, |
| recording: this._recording.exportData(), |
| overview: this._timelineOverview.exportData(), |
| }; |
| if (!json.recording || !json.overview) { |
| InspectorFrontendHost.beep(); |
| return; |
| } |
| |
| let frameName = null; |
| let mainFrame = WI.networkManager.mainFrame; |
| if (mainFrame) |
| frameName = mainFrame.mainResource.urlComponents.host || mainFrame.mainResource.displayName; |
| |
| let filename = frameName ? `${frameName}-recording` : this._recording.displayName; |
| |
| WI.FileUtilities.save({ |
| content: JSON.stringify(json), |
| suggestedName: filename + ".json", |
| forceSaveAs: true, |
| }); |
| } |
| |
| _exportButtonNavigationItemClicked(event) |
| { |
| this._exportTimelineRecording(); |
| } |
| |
| _importButtonNavigationItemClicked(event) |
| { |
| WI.FileUtilities.importJSON((result) => WI.timelineManager.processJSON(result), {multiple: true}); |
| } |
| |
| _clearTimeline(event) |
| { |
| if (this._recording.readonly) |
| return; |
| |
| if (WI.timelineManager.activeRecording === this._recording && WI.timelineManager.isCapturing()) |
| WI.timelineManager.stopCapturing(); |
| |
| this._recording.reset(); |
| } |
| |
| _updateTimelineOverviewHeight() |
| { |
| if (this._timelineOverview.editingInstruments) |
| this._timelineOverview.element.style.height = ""; |
| else { |
| const rulerHeight = 23; |
| |
| let styleValue = (rulerHeight + this._timelineOverview.height) + "px"; |
| this._timelineOverview.element.style.height = styleValue; |
| this._timelineContentBrowser.element.style.top = styleValue; |
| } |
| } |
| |
| _instrumentAdded(instrumentOrEvent) |
| { |
| let instrument = instrumentOrEvent instanceof WI.Instrument ? instrumentOrEvent : instrumentOrEvent.data.instrument; |
| console.assert(instrument instanceof WI.Instrument, instrument); |
| |
| let timeline = this._recording.timelineForInstrument(instrument); |
| console.assert(!this._timelineViewMap.has(timeline), timeline); |
| |
| this._timelineViewMap.set(timeline, WI.ContentView.createFromRepresentedObject(timeline, {recording: this._recording})); |
| if (timeline.type === WI.TimelineRecord.Type.RenderingFrame) |
| this._renderingFrameTimeline = timeline; |
| |
| let displayName = WI.TimelineTabContentView.displayNameForTimelineType(timeline.type); |
| let iconClassName = WI.TimelineTabContentView.iconClassNameForTimelineType(timeline.type); |
| let pathComponent = new WI.HierarchicalPathComponent(displayName, iconClassName, timeline); |
| pathComponent.addEventListener(WI.HierarchicalPathComponent.Event.SiblingWasSelected, this._timelinePathComponentSelected, this); |
| this._pathComponentMap.set(timeline, pathComponent); |
| |
| this._timelineCountChanged(); |
| } |
| |
| _instrumentRemoved(event) |
| { |
| let instrument = event.data.instrument; |
| console.assert(instrument instanceof WI.Instrument); |
| |
| let timeline = this._recording.timelineForInstrument(instrument); |
| console.assert(this._timelineViewMap.has(timeline), timeline); |
| |
| let timelineView = this._timelineViewMap.take(timeline); |
| if (this.currentTimelineView === timelineView) |
| this.showOverviewTimelineView(); |
| if (timeline.type === WI.TimelineRecord.Type.RenderingFrame) |
| this._renderingFrameTimeline = null; |
| |
| this._pathComponentMap.delete(timeline); |
| |
| this._timelineCountChanged(); |
| } |
| |
| _timelineCountChanged() |
| { |
| var previousPathComponent = null; |
| for (var pathComponent of this._pathComponentMap.values()) { |
| if (previousPathComponent) { |
| previousPathComponent.nextSibling = pathComponent; |
| pathComponent.previousSibling = previousPathComponent; |
| } |
| |
| previousPathComponent = pathComponent; |
| } |
| |
| this._updateTimelineOverviewHeight(); |
| } |
| |
| _recordingReset(event) |
| { |
| for (let timelineView of this._timelineViewMap.values()) |
| timelineView.reset(); |
| |
| this._currentTime = NaN; |
| |
| if (!this._updating) { |
| // Force the time ruler and views to reset to 0. |
| this._startTimeNeedsReset = true; |
| this._updateTimes(0, 0, 0); |
| } |
| |
| this._lastUpdateTimestamp = NaN; |
| this._startTimeNeedsReset = true; |
| |
| this._recording.removeEventListener(WI.TimelineRecording.Event.TimesUpdated, this._recordingTimesUpdated, this); |
| this._waitingToResetCurrentTime = false; |
| |
| this._timelineOverview.reset(); |
| this._overviewTimelineView.reset(); |
| this._clearTimelineNavigationItem.enabled = false; |
| this._exportButtonNavigationItem.enabled = false; |
| } |
| |
| _recordingUnloaded(event) |
| { |
| console.assert(!this._updating); |
| |
| WI.timelineManager.removeEventListener(null, null, this); |
| } |
| |
| _timeRangeSelectionChanged(event) |
| { |
| console.assert(this.currentTimelineView); |
| if (!this.currentTimelineView) |
| return; |
| |
| this._updateTimelineViewTimes(this.currentTimelineView); |
| |
| let selectedPathComponent; |
| if (this._timelineOverview.timelineRuler.entireRangeSelected) |
| selectedPathComponent = this._entireRecordingPathComponent; |
| else { |
| let timelineRange = this._timelineSelectionPathComponent.representedObject; |
| timelineRange.startValue = this.currentTimelineView.startTime; |
| timelineRange.endValue = this.currentTimelineView.endTime; |
| |
| if (!(this.currentTimelineView instanceof WI.RenderingFrameTimelineView)) { |
| timelineRange.startValue -= this.currentTimelineView.zeroTime; |
| timelineRange.endValue -= this.currentTimelineView.zeroTime; |
| } |
| |
| this._updateTimeRangePathComponents(); |
| selectedPathComponent = this._timelineSelectionPathComponent; |
| } |
| |
| if (this._selectedTimeRangePathComponent !== selectedPathComponent) { |
| this._selectedTimeRangePathComponent = selectedPathComponent; |
| this.dispatchEventToListeners(WI.ContentView.Event.SelectionPathComponentsDidChange); |
| } |
| } |
| |
| _recordSelected(event) |
| { |
| let {record} = event.data; |
| |
| this._selectRecordInTimelineView(record); |
| } |
| |
| _timelineSelected() |
| { |
| let timeline = this._timelineOverview.selectedTimeline; |
| if (timeline) |
| this.showTimelineViewForTimeline(timeline); |
| else |
| this.showOverviewTimelineView(); |
| } |
| |
| _updateTimeRangePathComponents() |
| { |
| let timelineRange = this._timelineSelectionPathComponent.representedObject; |
| let startValue = timelineRange.startValue; |
| let endValue = timelineRange.endValue; |
| if (isNaN(startValue) || isNaN(endValue)) { |
| this._entireRecordingPathComponent.nextSibling = null; |
| return; |
| } |
| |
| this._entireRecordingPathComponent.nextSibling = this._timelineSelectionPathComponent; |
| |
| let displayName; |
| if (this._timelineOverview.viewMode === WI.TimelineOverview.ViewMode.Timelines) { |
| const higherResolution = true; |
| let selectionStart = Number.secondsToString(startValue, higherResolution); |
| let selectionEnd = Number.secondsToString(endValue, higherResolution); |
| const epsilon = 0.0001; |
| if (startValue < epsilon) |
| displayName = WI.UIString("%s \u2013 %s").format(selectionStart, selectionEnd); |
| else { |
| let duration = Number.secondsToString(endValue - startValue, higherResolution); |
| displayName = WI.UIString("%s \u2013 %s (%s)").format(selectionStart, selectionEnd, duration); |
| } |
| } else { |
| startValue += 1; // Convert index to frame number. |
| if (startValue === endValue) |
| displayName = WI.UIString("Frame %d").format(startValue); |
| else |
| displayName = WI.UIString("Frames %d \u2013 %d").format(startValue, endValue); |
| } |
| |
| this._timelineSelectionPathComponent.displayName = displayName; |
| this._timelineSelectionPathComponent.title = displayName; |
| } |
| |
| _createTimelineRangePathComponent(title) |
| { |
| let range = new WI.TimelineRange(NaN, NaN); |
| let pathComponent = new WI.HierarchicalPathComponent(title || enDash, "time-icon", range); |
| pathComponent.addEventListener(WI.HierarchicalPathComponent.Event.SiblingWasSelected, this._timeRangePathComponentSelected, this); |
| |
| return pathComponent; |
| } |
| |
| _updateTimelineViewTimes(timelineView) |
| { |
| let timelineRuler = this._timelineOverview.timelineRuler; |
| let entireRangeSelected = timelineRuler.entireRangeSelected; |
| let endTime = this._timelineOverview.selectionStartTime + this._timelineOverview.selectionDuration; |
| |
| if (entireRangeSelected) { |
| if (timelineView instanceof WI.RenderingFrameTimelineView) |
| endTime = this._renderingFrameTimeline.records.length; |
| else if (timelineView instanceof WI.HeapAllocationsTimelineView) { |
| // Since heap snapshots can be added at any time, including when not actively recording, |
| // make sure to set the end time to an effectively infinite number so any new records |
| // that are added in the future aren't filtered out. |
| endTime = Number.MAX_VALUE; |
| } else { |
| // Clamp selection to the end of the recording (with padding), |
| // so graph views will show an auto-sized graph without a lot of |
| // empty space at the end. |
| endTime = isNaN(this._recording.endTime) ? this._recording.currentTime : this._recording.endTime; |
| endTime += timelineRuler.minimumSelectionDuration; |
| } |
| } |
| |
| timelineView.startTime = this._timelineOverview.selectionStartTime; |
| timelineView.currentTime = this._currentTime; |
| timelineView.endTime = endTime; |
| } |
| |
| _editingInstrumentsDidChange(event) |
| { |
| let editingInstruments = this._timelineOverview.editingInstruments; |
| this.element.classList.toggle(WI.TimelineOverview.EditInstrumentsStyleClassName, editingInstruments); |
| |
| this._updateTimelineOverviewHeight(); |
| } |
| |
| _filterDidChange() |
| { |
| if (!this.currentTimelineView) |
| return; |
| |
| this.currentTimelineView.updateFilter(this._filterBarNavigationItem.filterBar.filters); |
| } |
| |
| _handleTimelineViewRecordFiltered(event) |
| { |
| if (event.target !== this.currentTimelineView) |
| return; |
| |
| console.assert(this.currentTimelineView); |
| |
| let timeline = this.currentTimelineView.representedObject; |
| if (!(timeline instanceof WI.Timeline)) |
| return; |
| |
| let record = event.data.record; |
| let filtered = event.data.filtered; |
| this._timelineOverview.recordWasFiltered(timeline, record, filtered); |
| } |
| |
| _handleTimelineViewRecordSelected(event) |
| { |
| if (!this.visible) |
| return; |
| |
| let {record} = event.data; |
| |
| this._selectRecordInTimelineOverview(record); |
| this._selectRecordInTimelineView(record); |
| } |
| |
| _selectRecordInTimelineOverview(record) |
| { |
| let timeline = this._recording.timelineForRecordType(record.type); |
| if (!timeline) |
| return; |
| |
| this._timelineOverview.selectRecord(timeline, record); |
| } |
| |
| _selectRecordInTimelineView(record) |
| { |
| for (let timelineView of this._timelineViewMap.values()) { |
| let recordMatchesTimeline = record && timelineView.representedObject.type === record.type; |
| |
| if (recordMatchesTimeline && timelineView !== this.currentTimelineView) |
| this.showTimelineViewForTimeline(timelineView.representedObject); |
| |
| if (!record || recordMatchesTimeline) |
| timelineView.selectRecord(record); |
| } |
| } |
| |
| _handleTimelineViewScannerShow(event) |
| { |
| if (!this.visible) |
| return; |
| |
| let {time} = event.data; |
| this._timelineOverview.showScanner(time); |
| } |
| |
| _handleTimelineViewScannerHide(event) |
| { |
| if (!this.visible) |
| return; |
| |
| this._timelineOverview.hideScanner(); |
| } |
| |
| _handleTimelineViewNeedsEntireSelectedRange(event) |
| { |
| if (!this.visible) |
| return; |
| |
| this._timelineOverview.timelineRuler.selectEntireRange(); |
| } |
| |
| _updateProgressView() |
| { |
| let isCapturing = WI.timelineManager.isCapturing(); |
| this._progressView.visible = isCapturing && this.currentTimelineView && !this.currentTimelineView.showsLiveRecordingData; |
| } |
| |
| _updateFilterBar() |
| { |
| this._filterBarNavigationItem.hidden = !this.currentTimelineView || !this.currentTimelineView.showsFilterBar; |
| } |
| }; |