Files
palemoon27/toolkit/devtools/performance/views/recordings.js
T

242 lines
8.4 KiB
JavaScript

/* 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/. */
"use strict";
/**
* Functions handling the recordings UI.
*/
let RecordingsView = Heritage.extend(WidgetMethods, {
/**
* Initialization function, called when the tool is started.
*/
initialize: function() {
this.widget = new SideMenuWidget($("#recordings-list"));
this._onSelect = this._onSelect.bind(this);
this._onRecordingStarted = this._onRecordingStarted.bind(this);
this._onRecordingStopped = this._onRecordingStopped.bind(this);
this._onRecordingImported = this._onRecordingImported.bind(this);
this._onSaveButtonClick = this._onSaveButtonClick.bind(this);
this._onRecordingsCleared = this._onRecordingsCleared.bind(this);
this.emptyText = L10N.getStr("noRecordingsText");
PerformanceController.on(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
PerformanceController.on(EVENTS.RECORDING_IMPORTED, this._onRecordingImported);
PerformanceController.on(EVENTS.RECORDINGS_CLEARED, this._onRecordingsCleared);
this.widget.addEventListener("select", this._onSelect, false);
},
/**
* Destruction function, called when the tool is closed.
*/
destroy: function() {
PerformanceController.off(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
PerformanceController.off(EVENTS.RECORDING_IMPORTED, this._onRecordingImported);
PerformanceController.off(EVENTS.RECORDINGS_CLEARED, this._onRecordingsCleared);
this.widget.removeEventListener("select", this._onSelect, false);
},
/**
* Adds an empty recording to this container.
*
* @param RecordingModel recording
* A model for the new recording item created.
*/
addEmptyRecording: function (recording) {
let titleNode = document.createElement("label");
titleNode.className = "plain recording-item-title";
titleNode.setAttribute("value", recording.getLabel() ||
L10N.getFormatStr("recordingsList.itemLabel", this.itemCount + 1));
let durationNode = document.createElement("label");
durationNode.className = "plain recording-item-duration";
durationNode.setAttribute("value",
L10N.getStr("recordingsList.recordingLabel"));
let saveNode = document.createElement("label");
saveNode.className = "plain recording-item-save";
saveNode.addEventListener("click", this._onSaveButtonClick);
let hspacer = document.createElement("spacer");
hspacer.setAttribute("flex", "1");
let footerNode = document.createElement("hbox");
footerNode.className = "recording-item-footer";
footerNode.appendChild(durationNode);
footerNode.appendChild(hspacer);
footerNode.appendChild(saveNode);
let vspacer = document.createElement("spacer");
vspacer.setAttribute("flex", "1");
let contentsNode = document.createElement("vbox");
contentsNode.className = "recording-item";
contentsNode.setAttribute("flex", "1");
contentsNode.appendChild(titleNode);
contentsNode.appendChild(vspacer);
contentsNode.appendChild(footerNode);
// Append a recording item to this container.
return this.push([contentsNode], {
// Store the recording model that contains all the data to be
// rendered in the item.
attachment: recording
});
},
/**
* Signals that a recording session has started.
*
* @param RecordingModel recording
* Model of the recording that was started.
*/
_onRecordingStarted: function (_, recording) {
// Insert a "dummy" recording item, to hint that recording has now started.
let recordingItem;
// If a label is specified (e.g due to a call to `console.profile`),
// then try reusing a pre-existing recording item, if there is one.
// This is symmetrical to how `this.handleRecordingEnded` works.
let profileLabel = recording.getLabel();
if (profileLabel) {
recordingItem = this.getItemForAttachment(e => e.getLabel() == profileLabel);
}
// Otherwise, create a new empty recording item.
if (!recordingItem) {
recordingItem = this.addEmptyRecording(recording);
}
// Mark the corresponding item as being a "record in progress".
recordingItem.isRecording = true;
// If this is a manual recording, immediately select it.
if (!recording.getLabel()) {
this.selectedItem = recordingItem;
}
},
/**
* Signals that a recording session has ended.
*
* @param RecordingModel recording
* The model of the recording that just stopped.
*/
_onRecordingStopped: function (_, recording) {
let recordingItem;
// If a label is specified (e.g due to a call to `console.profileEnd`),
// then try reusing a pre-existing recording item, if there is one.
// This is symmetrical to how `this.handleRecordingStarted` works.
let profileLabel = recording.getLabel();
if (profileLabel) {
recordingItem = this.getItemForAttachment(e => e.getLabel() == profileLabel);
}
// Otherwise, just use the first available recording item.
if (!recordingItem) {
recordingItem = this.getItemForPredicate(e => e.isRecording);
}
// Mark the corresponding item as being a "finished recording".
recordingItem.isRecording = false;
// Render the recording item with finalized information (timing, etc)
this.finalizeRecording(recordingItem);
this.forceSelect(recordingItem);
},
/**
* Signals that a recording has been imported.
*
* @param RecordingModel model
* The recording model containing data on the recording session.
*/
_onRecordingImported: function (_, model) {
let recordingItem = this.addEmptyRecording(model);
recordingItem.isRecording = false;
// Immediately select the imported recording
this.selectedItem = recordingItem;
// Render the recording item with finalized information (timing, etc)
this.finalizeRecording(recordingItem);
},
/**
* Clears out all recordings.
*/
_onRecordingsCleared: function () {
this.empty();
},
/**
* Adds recording data to a recording item in this container.
*
* @param Item recordingItem
* An item inserted via `RecordingsView.addEmptyRecording`.
*/
finalizeRecording: function (recordingItem) {
let model = recordingItem.attachment;
let saveNode = $(".recording-item-save", recordingItem.target);
saveNode.setAttribute("value",
L10N.getStr("recordingsList.saveLabel"));
let durationMillis = model.getDuration().toFixed(0);
let durationNode = $(".recording-item-duration", recordingItem.target);
durationNode.setAttribute("value",
L10N.getFormatStr("recordingsList.durationLabel", durationMillis));
},
/**
* The select listener for this container.
*/
_onSelect: Task.async(function*({ detail: recordingItem }) {
// TODO 1120699
// show appropriate empty/recording panels for several scenarios below
if (!recordingItem) {
return;
}
let model = recordingItem.attachment;
// If recording, don't abort completely, as we still want to fire an event
// for selection so we can continue repainting the overview graphs.
if (recordingItem.isRecording) {
this.emit(EVENTS.RECORDING_SELECTED, model);
return;
}
this.emit(EVENTS.RECORDING_SELECTED, model);
}),
/**
* The click listener for the "save" button of each item in this container.
*/
_onSaveButtonClick: function (e) {
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;
}
let recordingItem = this.getItemForElement(e.target);
this.emit(EVENTS.UI_EXPORT_RECORDING, recordingItem.attachment, fp.file);
}});
},
toString: () => "[object RecordingsView]"
});
/**
* Convenient way of emitting events from the RecordingsView.
*/
EventEmitter.decorate(RecordingsView);