diff options
Diffstat (limited to 'devtools/client/performance/views/recordings.js')
-rw-r--r-- | devtools/client/performance/views/recordings.js | 202 |
1 files changed, 202 insertions, 0 deletions
diff --git a/devtools/client/performance/views/recordings.js b/devtools/client/performance/views/recordings.js new file mode 100644 index 000000000..487ea4f03 --- /dev/null +++ b/devtools/client/performance/views/recordings.js @@ -0,0 +1,202 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ +/* import-globals-from ../performance-controller.js */ +/* import-globals-from ../performance-view.js */ +/* globals document, window */ +"use strict"; + +/** + * Functions handling the recordings UI. + */ +var RecordingsView = { + /** + * Initialization function, called when the tool is started. + */ + initialize: function () { + this._onSelect = this._onSelect.bind(this); + this._onRecordingStateChange = this._onRecordingStateChange.bind(this); + this._onNewRecording = this._onNewRecording.bind(this); + this._onSaveButtonClick = this._onSaveButtonClick.bind(this); + this._onRecordingDeleted = this._onRecordingDeleted.bind(this); + this._onRecordingExported = this._onRecordingExported.bind(this); + + PerformanceController.on(EVENTS.RECORDING_STATE_CHANGE, this._onRecordingStateChange); + PerformanceController.on(EVENTS.RECORDING_ADDED, this._onNewRecording); + PerformanceController.on(EVENTS.RECORDING_DELETED, this._onRecordingDeleted); + PerformanceController.on(EVENTS.RECORDING_EXPORTED, this._onRecordingExported); + + // DE-XUL: Begin migrating the recording sidebar to React. Temporarily hold state + // here. + this._listState = { + recordings: [], + labels: new WeakMap(), + selected: null, + }; + this._listMount = PerformanceUtils.createHtmlMount($("#recording-list-mount")); + this._renderList(); + }, + + /** + * Get the index of the currently selected recording. Only used by tests. + * @return {integer} index + */ + getSelectedIndex() { + const { recordings, selected } = this._listState; + return recordings.indexOf(selected); + }, + + /** + * Set the currently selected recording via its index. Only used by tests. + * @param {integer} index + */ + setSelectedByIndex(index) { + this._onSelect(this._listState.recordings[index]); + this._renderList(); + }, + + /** + * DE-XUL: During the migration, this getter will access the selected recording from + * the private _listState object so that tests will continue to pass. + */ + get selected() { + return this._listState.selected; + }, + + /** + * DE-XUL: During the migration, this getter will access the number of recordings. + */ + get itemCount() { + return this._listState.recordings.length; + }, + + /** + * DE-XUL: Render the recording list using React. + */ + _renderList: function () { + const {recordings, labels, selected} = this._listState; + + const recordingList = RecordingList({ + itemComponent: RecordingListItem, + items: recordings.map(recording => ({ + onSelect: () => this._onSelect(recording), + onSave: () => this._onSaveButtonClick(recording), + isLoading: !recording.isRecording() && !recording.isCompleted(), + isRecording: recording.isRecording(), + isSelected: recording === selected, + duration: recording.getDuration().toFixed(0), + label: labels.get(recording), + })) + }); + + ReactDOM.render(recordingList, this._listMount); + }, + + /** + * Destruction function, called when the tool is closed. + */ + destroy: function () { + PerformanceController.off(EVENTS.RECORDING_STATE_CHANGE, + this._onRecordingStateChange); + PerformanceController.off(EVENTS.RECORDING_ADDED, this._onNewRecording); + PerformanceController.off(EVENTS.RECORDING_DELETED, this._onRecordingDeleted); + PerformanceController.off(EVENTS.RECORDING_EXPORTED, this._onRecordingExported); + }, + + /** + * Called when a new recording is stored in the UI. This handles + * when recordings are lazily loaded (like a console.profile occurring + * before the tool is loaded) or imported. In normal manual recording cases, + * this will also be fired. + */ + _onNewRecording: function (_, recording) { + this._onRecordingStateChange(_, null, recording); + }, + + /** + * Signals that a recording has changed state. + * + * @param string state + * Can be "recording-started", "recording-stopped", "recording-stopping" + * @param RecordingModel recording + * Model of the recording that was started. + */ + _onRecordingStateChange: function (_, state, recording) { + const { recordings, labels } = this._listState; + + if (!recordings.includes(recording)) { + recordings.push(recording); + labels.set(recording, recording.getLabel() || + L10N.getFormatStr("recordingsList.itemLabel", recordings.length)); + + // If this is a manual recording, immediately select it, or + // select a console profile if its the only one + if (!recording.isConsole() || !this._listState.selected) { + this._onSelect(recording); + } + } + + // Determine if the recording needs to be selected. + const isCompletedManualRecording = !recording.isConsole() && recording.isCompleted(); + if (recording.isImported() || isCompletedManualRecording) { + this._onSelect(recording); + } + + this._renderList(); + }, + + /** + * Clears out all non-console recordings. + */ + _onRecordingDeleted: function (_, recording) { + const { recordings } = this._listState; + const index = recordings.indexOf(recording); + if (index === -1) { + throw new Error("Attempting to remove a recording that doesn't exist."); + } + recordings.splice(index, 1); + this._renderList(); + }, + + /** + * The select listener for this container. + */ + _onSelect: Task.async(function* (recording) { + this._listState.selected = recording; + this.emit(EVENTS.UI_RECORDING_SELECTED, recording); + this._renderList(); + }), + + /** + * The click listener for the "save" button of each item in this container. + */ + _onSaveButtonClick: function (recording) { + let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); + fp.init(window, L10N.getStr("recordingsList.saveDialogTitle"), + Ci.nsIFilePicker.modeSave); + fp.appendFilter(L10N.getStr("recordingsList.saveDialogJSONFilter"), "*.json"); + fp.appendFilter(L10N.getStr("recordingsList.saveDialogAllFilter"), "*.*"); + fp.defaultString = "profile.json"; + + fp.open({ done: result => { + if (result == Ci.nsIFilePicker.returnCancel) { + return; + } + this.emit(EVENTS.UI_EXPORT_RECORDING, recording, fp.file); + }}); + }, + + _onRecordingExported: function (_, recording, file) { + if (recording.isConsole()) { + return; + } + const name = file.leafName.replace(/\..+$/, ""); + this._listState.labels.set(recording, name); + this._renderList(); + } +}; + +/** + * Convenient way of emitting events from the RecordingsView. + */ +EventEmitter.decorate(RecordingsView); |