summaryrefslogtreecommitdiffstats
path: root/devtools/client/performance/views/recordings.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/performance/views/recordings.js')
-rw-r--r--devtools/client/performance/views/recordings.js202
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);