mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-26 22:32:46 +00:00
merge fx-team to mozilla-central a=merge
This commit is contained in:
commit
9a34b72c5f
1
AUTHORS
1
AUTHORS
@ -14,6 +14,7 @@ Aaron Nowack <anowack@mimiru.net>
|
||||
Aaron Reed <aaronr@us.ibm.com>
|
||||
Aaron Spangler <aaron@spangler.ods.org>
|
||||
Aaron Train <aaron.train@gmail.com>
|
||||
Abdelrhman Ahmed <a.ahmed1026@gmail.com>
|
||||
Achim Hasenmueller <achimha@innotek.de>
|
||||
ActiveState Tool Corp.
|
||||
Adam Barth <hk9565@gmail.com>
|
||||
|
@ -26,13 +26,6 @@ pref("app.update.url.details", "https://www.mozilla.org/firefox/aurora/");
|
||||
// app.update.checkInstallTime is true.
|
||||
pref("app.update.checkInstallTime.days", 2);
|
||||
|
||||
// code usage depends on contracts, please contact the Firefox module owner if you have questions
|
||||
pref("browser.search.param.yahoo-fr", "moz35");
|
||||
pref("browser.search.param.yahoo-fr-ja", "mozff");
|
||||
#ifdef MOZ_METRO
|
||||
pref("browser.search.param.yahoo-fr-metro", "");
|
||||
#endif
|
||||
|
||||
// Number of usages of the web console or scratchpad.
|
||||
// If this is less than 5, then pasting code into the web console or scratchpad is disabled
|
||||
pref("devtools.selfxss.count", 5);
|
@ -24,13 +24,6 @@ pref("app.update.url.details", "https://nightly.mozilla.org");
|
||||
// app.update.checkInstallTime is true.
|
||||
pref("app.update.checkInstallTime.days", 2);
|
||||
|
||||
// code usage depends on contracts, please contact the Firefox module owner if you have questions
|
||||
pref("browser.search.param.yahoo-fr", "moz35");
|
||||
pref("browser.search.param.yahoo-fr-ja", "mozff");
|
||||
#ifdef MOZ_METRO
|
||||
pref("browser.search.param.yahoo-fr-metro", "");
|
||||
#endif
|
||||
|
||||
// Number of usages of the web console or scratchpad.
|
||||
// If this is less than 5, then pasting code into the web console or scratchpad is disabled
|
||||
pref("devtools.selfxss.count", 5);
|
@ -23,14 +23,6 @@ pref("app.update.url.details", "https://www.mozilla.org/%LOCALE%/firefox/notes")
|
||||
// app.update.checkInstallTime is true.
|
||||
pref("app.update.checkInstallTime.days", 63);
|
||||
|
||||
// code usage depends on contracts, please contact the Firefox module owner if you have questions
|
||||
pref("browser.search.param.yahoo-fr", "moz35");
|
||||
pref("browser.search.param.yahoo-fr-ja", "mozff");
|
||||
#ifdef MOZ_METRO
|
||||
pref("browser.search.param.ms-pc-metro", "MOZW");
|
||||
pref("browser.search.param.yahoo-fr-metro", "mozilla_metro_search");
|
||||
#endif
|
||||
|
||||
// Number of usages of the web console or scratchpad.
|
||||
// If this is less than 5, then pasting code into the web console or scratchpad is disabled
|
||||
pref("devtools.selfxss.count", 0);
|
@ -23,13 +23,6 @@ pref("app.update.url.details", "https://nightly.mozilla.org");
|
||||
// app.update.checkInstallTime is true.
|
||||
pref("app.update.checkInstallTime.days", 2);
|
||||
|
||||
// code usage depends on contracts, please contact the Firefox module owner if you have questions
|
||||
pref("browser.search.param.yahoo-fr", "moz35");
|
||||
pref("browser.search.param.yahoo-fr-ja", "mozff");
|
||||
#ifdef MOZ_METRO
|
||||
pref("browser.search.param.yahoo-fr-metro", "");
|
||||
#endif
|
||||
|
||||
// Number of usages of the web console or scratchpad.
|
||||
// If this is less than 5, then pasting code into the web console or scratchpad is disabled
|
||||
pref("devtools.selfxss.count", 0);
|
@ -3969,8 +3969,12 @@ OverflowableToolbar.prototype = {
|
||||
},
|
||||
|
||||
onOverflow: function(aEvent) {
|
||||
// The rangeParent check is here because of bug 1111986 and ensuring that
|
||||
// overflow events from the bookmarks toolbar items or similar things that
|
||||
// manage their own overflow don't trigger an overflow on the entire toolbar
|
||||
if (!this._enabled ||
|
||||
(aEvent && aEvent.target != this._toolbar.customizationTarget))
|
||||
(aEvent && aEvent.target != this._toolbar.customizationTarget) ||
|
||||
(aEvent && aEvent.rangeParent))
|
||||
return;
|
||||
|
||||
let child = this._target.lastChild;
|
||||
|
@ -85,6 +85,8 @@ browser.jar:
|
||||
content/browser/devtools/webaudioeditor/views/utils.js (webaudioeditor/views/utils.js)
|
||||
content/browser/devtools/webaudioeditor/views/context.js (webaudioeditor/views/context.js)
|
||||
content/browser/devtools/webaudioeditor/views/inspector.js (webaudioeditor/views/inspector.js)
|
||||
content/browser/devtools/webaudioeditor/views/properties.js (webaudioeditor/views/properties.js)
|
||||
content/browser/devtools/webaudioeditor/views/automation.js (webaudioeditor/views/automation.js)
|
||||
content/browser/devtools/profiler.xul (profiler/profiler.xul)
|
||||
content/browser/devtools/profiler.js (profiler/profiler.js)
|
||||
content/browser/devtools/ui-recordings.js (profiler/ui-recordings.js)
|
||||
@ -98,6 +100,7 @@ browser.jar:
|
||||
content/browser/devtools/performance/views/details-call-tree.js (performance/views/details-call-tree.js)
|
||||
content/browser/devtools/performance/views/details-waterfall.js (performance/views/details-waterfall.js)
|
||||
content/browser/devtools/performance/views/details-flamegraph.js (performance/views/details-flamegraph.js)
|
||||
content/browser/devtools/performance/views/recordings.js (performance/views/recordings.js)
|
||||
#endif
|
||||
content/browser/devtools/responsivedesign/resize-commands.js (responsivedesign/resize-commands.js)
|
||||
content/browser/devtools/commandline.css (commandline/commandline.css)
|
||||
|
248
browser/devtools/performance/modules/recording-model.js
Normal file
248
browser/devtools/performance/modules/recording-model.js
Normal file
@ -0,0 +1,248 @@
|
||||
/* 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";
|
||||
|
||||
const { PerformanceIO } = require("devtools/performance/io");
|
||||
|
||||
const RECORDING_IN_PROGRESS = exports.RECORDING_IN_PROGRESS = -1;
|
||||
const RECORDING_UNAVAILABLE = exports.RECORDING_UNAVAILABLE = null;
|
||||
/**
|
||||
* Model for a wholistic profile, containing start/stop times, profiling data, frames data,
|
||||
* timeline (marker, tick, memory) data, and methods to start/stop recording.
|
||||
*/
|
||||
|
||||
const RecordingModel = function (options={}) {
|
||||
this._front = options.front;
|
||||
this._performance = options.performance;
|
||||
this._label = options.label || "";
|
||||
};
|
||||
|
||||
RecordingModel.prototype = {
|
||||
_localStartTime: RECORDING_UNAVAILABLE,
|
||||
_startTime: RECORDING_UNAVAILABLE,
|
||||
_endTime: RECORDING_UNAVAILABLE,
|
||||
_markers: [],
|
||||
_frames: [],
|
||||
_ticks: [],
|
||||
_memory: [],
|
||||
_profilerData: {},
|
||||
_label: "",
|
||||
_imported: false,
|
||||
_isRecording: false,
|
||||
|
||||
/**
|
||||
* Loads a recording from a file.
|
||||
*
|
||||
* @param nsILocalFile file
|
||||
* The file to import the data form.
|
||||
*/
|
||||
importRecording: Task.async(function *(file) {
|
||||
let recordingData = yield PerformanceIO.loadRecordingFromFile(file);
|
||||
|
||||
this._imported = true;
|
||||
this._label = recordingData.profilerData.profilerLabel || "";
|
||||
this._startTime = recordingData.interval.startTime;
|
||||
this._endTime = recordingData.interval.endTime;
|
||||
this._markers = recordingData.markers;
|
||||
this._frames = recordingData.frames;
|
||||
this._memory = recordingData.memory;
|
||||
this._ticks = recordingData.ticks;
|
||||
this._profilerData = recordingData.profilerData;
|
||||
|
||||
return recordingData;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Saves the current recording to a file.
|
||||
*
|
||||
* @param nsILocalFile file
|
||||
* The file to stream the data into.
|
||||
*/
|
||||
exportRecording: Task.async(function *(file) {
|
||||
let recordingData = this.getAllData();
|
||||
yield PerformanceIO.saveRecordingToFile(recordingData, file);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Starts recording with the PerformanceFront, storing the start times
|
||||
* on the model.
|
||||
*/
|
||||
startRecording: Task.async(function *() {
|
||||
// Times must come from the actor in order to be self-consistent.
|
||||
// However, we also want to update the view with the elapsed time
|
||||
// even when the actor is not generating data. To do this we get
|
||||
// the local time and use it to compute a reasonable elapsed time.
|
||||
this._localStartTime = this._performance.now();
|
||||
|
||||
let { startTime } = yield this._front.startRecording({
|
||||
withTicks: true,
|
||||
withMemory: true
|
||||
});
|
||||
this._isRecording = true;
|
||||
|
||||
this._startTime = startTime;
|
||||
this._endTime = RECORDING_IN_PROGRESS;
|
||||
this._markers = [];
|
||||
this._frames = [];
|
||||
this._memory = [];
|
||||
this._ticks = [];
|
||||
}),
|
||||
|
||||
/**
|
||||
* Stops recording with the PerformanceFront, storing the end times
|
||||
* on the model.
|
||||
*/
|
||||
stopRecording: Task.async(function *() {
|
||||
let results = yield this._front.stopRecording();
|
||||
this._isRecording = false;
|
||||
|
||||
// If `endTime` is not yielded from timeline actor (< Fx36), fake it.
|
||||
if (!results.endTime) {
|
||||
results.endTime = this._startTime + this.getLocalElapsedTime();
|
||||
}
|
||||
|
||||
this._endTime = results.endTime;
|
||||
this._profilerData = results.profilerData;
|
||||
this._markers = this._markers.sort((a,b) => (a.start > b.start));
|
||||
|
||||
return results;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Returns the profile's label, from `console.profile(LABEL)`.
|
||||
*/
|
||||
getLabel: function () {
|
||||
return this._label;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the amount of time elapsed locally after starting a recording.
|
||||
*/
|
||||
getLocalElapsedTime: function () {
|
||||
return this._performance.now() - this._localStartTime;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns duration of this recording, in milliseconds.
|
||||
*/
|
||||
getDuration: function () {
|
||||
let { startTime, endTime } = this.getInterval();
|
||||
return endTime - startTime;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the time interval for the current recording.
|
||||
* @return object
|
||||
*/
|
||||
getInterval: function() {
|
||||
let startTime = this._startTime;
|
||||
let endTime = this._endTime;
|
||||
|
||||
// Compute an approximate ending time for the current recording. This is
|
||||
// needed to ensure that the view updates even when new data is
|
||||
// not being generated.
|
||||
if (endTime == RECORDING_IN_PROGRESS) {
|
||||
endTime = startTime + this.getLocalElapsedTime();
|
||||
}
|
||||
|
||||
return { startTime, endTime };
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the accumulated markers in the current recording.
|
||||
* @return array
|
||||
*/
|
||||
getMarkers: function() {
|
||||
return this._markers;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the accumulated stack frames in the current recording.
|
||||
* @return array
|
||||
*/
|
||||
getFrames: function() {
|
||||
return this._frames;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the accumulated memory measurements in this recording.
|
||||
* @return array
|
||||
*/
|
||||
getMemory: function() {
|
||||
return this._memory;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the accumulated refresh driver ticks in this recording.
|
||||
* @return array
|
||||
*/
|
||||
getTicks: function() {
|
||||
return this._ticks;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the profiler data in this recording.
|
||||
* @return array
|
||||
*/
|
||||
getProfilerData: function() {
|
||||
return this._profilerData;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets all the data in this recording.
|
||||
*/
|
||||
getAllData: function() {
|
||||
let interval = this.getInterval();
|
||||
let markers = this.getMarkers();
|
||||
let frames = this.getFrames();
|
||||
let memory = this.getMemory();
|
||||
let ticks = this.getTicks();
|
||||
let profilerData = this.getProfilerData();
|
||||
return { interval, markers, frames, memory, ticks, profilerData };
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a boolean indicating whether or not this recording model
|
||||
* is recording.
|
||||
*/
|
||||
isRecording: function () {
|
||||
return this._isRecording;
|
||||
},
|
||||
|
||||
/**
|
||||
* Fired whenever the PerformanceFront emits markers, memory or ticks.
|
||||
*/
|
||||
addTimelineData: function (eventName, ...data) {
|
||||
// If this model isn't currently recording,
|
||||
// ignore the timeline data.
|
||||
if (!this.isRecording()) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (eventName) {
|
||||
// Accumulate markers into an array.
|
||||
case "markers":
|
||||
let [markers] = data;
|
||||
Array.prototype.push.apply(this._markers, markers);
|
||||
break;
|
||||
// Accumulate stack frames into an array.
|
||||
case "frames":
|
||||
let [, frames] = data;
|
||||
Array.prototype.push.apply(this._frames, frames);
|
||||
break;
|
||||
// Accumulate memory measurements into an array.
|
||||
case "memory":
|
||||
let [delta, measurement] = data;
|
||||
this._memory.push({ delta, value: measurement.total / 1024 / 1024 });
|
||||
break;
|
||||
// Save the accumulated refresh driver ticks.
|
||||
case "ticks":
|
||||
let [, timestamps] = data;
|
||||
this._ticks = timestamps;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.RecordingModel = RecordingModel;
|
@ -6,6 +6,7 @@
|
||||
EXTRA_JS_MODULES.devtools.performance += [
|
||||
'modules/front.js',
|
||||
'modules/io.js',
|
||||
'modules/recording-model.js',
|
||||
'panel.js'
|
||||
]
|
||||
|
||||
|
@ -41,6 +41,11 @@ devtools.lazyImporter(this, "CanvasGraphUtils",
|
||||
"resource:///modules/devtools/Graphs.jsm");
|
||||
devtools.lazyImporter(this, "LineGraphWidget",
|
||||
"resource:///modules/devtools/Graphs.jsm");
|
||||
devtools.lazyImporter(this, "SideMenuWidget",
|
||||
"resource:///modules/devtools/SideMenuWidget.jsm");
|
||||
|
||||
const { RecordingModel, RECORDING_IN_PROGRESS, RECORDING_UNAVAILABLE } =
|
||||
devtools.require("devtools/performance/recording-model");
|
||||
|
||||
devtools.lazyImporter(this, "FlameGraphUtils",
|
||||
"resource:///modules/devtools/FlameGraph.jsm");
|
||||
@ -49,12 +54,17 @@ devtools.lazyImporter(this, "FlameGraph",
|
||||
|
||||
// Events emitted by various objects in the panel.
|
||||
const EVENTS = {
|
||||
// Emitted by the PerformanceController or RecordingView
|
||||
// when a recording model is selected
|
||||
RECORDING_SELECTED: "Performance:RecordingSelected",
|
||||
|
||||
// Emitted by the PerformanceView on record button click
|
||||
UI_START_RECORDING: "Performance:UI:StartRecording",
|
||||
UI_STOP_RECORDING: "Performance:UI:StopRecording",
|
||||
|
||||
// Emitted by the PerformanceView on import or export button click
|
||||
// Emitted by the PerformanceView on import button click
|
||||
UI_IMPORT_RECORDING: "Performance:UI:ImportRecording",
|
||||
// Emitted by the RecordingsView on export button click
|
||||
UI_EXPORT_RECORDING: "Performance:UI:ExportRecording",
|
||||
|
||||
// When a recording is started or stopped via the PerformanceController
|
||||
@ -96,11 +106,6 @@ const EVENTS = {
|
||||
FLAMEGRAPH_RENDERED: "Performance:UI:FlameGraphRendered"
|
||||
};
|
||||
|
||||
// Constant defining the end time for a recording that hasn't finished
|
||||
// or is not yet available.
|
||||
const RECORDING_IN_PROGRESS = -1;
|
||||
const RECORDING_UNAVAILABLE = null;
|
||||
|
||||
/**
|
||||
* The current target and the profiler connection, set by this tool's host.
|
||||
*/
|
||||
@ -150,18 +155,8 @@ let PrefObserver = {
|
||||
* UI interaction.
|
||||
*/
|
||||
let PerformanceController = {
|
||||
/**
|
||||
* Permanent storage for the markers and the memory measurements streamed by
|
||||
* the backend, along with the start and end timestamps.
|
||||
*/
|
||||
_localStartTime: RECORDING_UNAVAILABLE,
|
||||
_startTime: RECORDING_UNAVAILABLE,
|
||||
_endTime: RECORDING_UNAVAILABLE,
|
||||
_markers: [],
|
||||
_frames: [],
|
||||
_memory: [],
|
||||
_ticks: [],
|
||||
_profilerData: {},
|
||||
_recordings: [],
|
||||
_currentRecording: null,
|
||||
|
||||
/**
|
||||
* Listen for events emitted by the current tab target and
|
||||
@ -173,11 +168,13 @@ let PerformanceController = {
|
||||
this.importRecording = this.importRecording.bind(this);
|
||||
this.exportRecording = this.exportRecording.bind(this);
|
||||
this._onTimelineData = this._onTimelineData.bind(this);
|
||||
this._onRecordingSelectFromView = this._onRecordingSelectFromView.bind(this);
|
||||
|
||||
PerformanceView.on(EVENTS.UI_START_RECORDING, this.startRecording);
|
||||
PerformanceView.on(EVENTS.UI_STOP_RECORDING, this.stopRecording);
|
||||
PerformanceView.on(EVENTS.UI_EXPORT_RECORDING, this.exportRecording);
|
||||
PerformanceView.on(EVENTS.UI_IMPORT_RECORDING, this.importRecording);
|
||||
RecordingsView.on(EVENTS.UI_EXPORT_RECORDING, this.exportRecording);
|
||||
RecordingsView.on(EVENTS.RECORDING_SELECTED, this._onRecordingSelectFromView);
|
||||
|
||||
gFront.on("ticks", this._onTimelineData); // framerate
|
||||
gFront.on("markers", this._onTimelineData); // timeline markers
|
||||
@ -191,8 +188,9 @@ let PerformanceController = {
|
||||
destroy: function() {
|
||||
PerformanceView.off(EVENTS.UI_START_RECORDING, this.startRecording);
|
||||
PerformanceView.off(EVENTS.UI_STOP_RECORDING, this.stopRecording);
|
||||
PerformanceView.off(EVENTS.UI_EXPORT_RECORDING, this.exportRecording);
|
||||
PerformanceView.off(EVENTS.UI_IMPORT_RECORDING, this.importRecording);
|
||||
RecordingsView.off(EVENTS.UI_EXPORT_RECORDING, this.exportRecording);
|
||||
RecordingsView.off(EVENTS.RECORDING_SELECTED, this._onRecordingSelectFromView);
|
||||
|
||||
gFront.off("ticks", this._onTimelineData);
|
||||
gFront.off("markers", this._onTimelineData);
|
||||
@ -205,25 +203,11 @@ let PerformanceController = {
|
||||
* when the front has started to record.
|
||||
*/
|
||||
startRecording: Task.async(function *() {
|
||||
// Times must come from the actor in order to be self-consistent.
|
||||
// However, we also want to update the view with the elapsed time
|
||||
// even when the actor is not generating data. To do this we get
|
||||
// the local time and use it to compute a reasonable elapsed time.
|
||||
this._localStartTime = performance.now();
|
||||
let model = this.createNewRecording();
|
||||
this.setCurrentRecording(model);
|
||||
yield model.startRecording();
|
||||
|
||||
let { startTime } = yield gFront.startRecording({
|
||||
withTicks: true,
|
||||
withMemory: true
|
||||
});
|
||||
|
||||
this._startTime = startTime;
|
||||
this._endTime = RECORDING_IN_PROGRESS;
|
||||
this._markers = [];
|
||||
this._frames = [];
|
||||
this._memory = [];
|
||||
this._ticks = [];
|
||||
|
||||
this.emit(EVENTS.RECORDING_STARTED);
|
||||
this.emit(EVENTS.RECORDING_STARTED, model);
|
||||
}),
|
||||
|
||||
/**
|
||||
@ -231,63 +215,76 @@ let PerformanceController = {
|
||||
* when the front has stopped recording.
|
||||
*/
|
||||
stopRecording: Task.async(function *() {
|
||||
let results = yield gFront.stopRecording();
|
||||
let recording = this._getLatest();
|
||||
yield recording.stopRecording();
|
||||
|
||||
// If `endTime` is not yielded from timeline actor (< Fx36), fake it.
|
||||
if (!results.endTime) {
|
||||
results.endTime = this._startTime + this.getLocalElapsedTime();
|
||||
}
|
||||
|
||||
this._endTime = results.endTime;
|
||||
this._profilerData = results.profilerData;
|
||||
this._markers = this._markers.sort((a,b) => (a.start > b.start));
|
||||
|
||||
this.emit(EVENTS.RECORDING_STOPPED);
|
||||
this.emit(EVENTS.RECORDING_STOPPED, recording);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Saves the current recording to a file.
|
||||
*
|
||||
* @param RecordingModel recording
|
||||
* The model that holds the recording data.
|
||||
* @param nsILocalFile file
|
||||
* The file to stream the data into.
|
||||
*/
|
||||
exportRecording: Task.async(function*(_, file) {
|
||||
let recordingData = this.getAllData();
|
||||
exportRecording: Task.async(function*(_, recording, file) {
|
||||
let recordingData = recording.getAllData();
|
||||
yield PerformanceIO.saveRecordingToFile(recordingData, file);
|
||||
|
||||
this.emit(EVENTS.RECORDING_EXPORTED, recordingData);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Loads a recording from a file, replacing the current one.
|
||||
* XXX: Handle multiple recordings, bug 1111004.
|
||||
* Loads a recording from a file, adding it to the recordings list.
|
||||
*
|
||||
* @param nsILocalFile file
|
||||
* The file to import the data from.
|
||||
*/
|
||||
importRecording: Task.async(function*(_, file) {
|
||||
let recordingData = yield PerformanceIO.loadRecordingFromFile(file);
|
||||
let model = this.createNewRecording();
|
||||
yield model.importRecording(file);
|
||||
|
||||
this._startTime = recordingData.interval.startTime;
|
||||
this._endTime = recordingData.interval.endTime;
|
||||
this._markers = recordingData.markers;
|
||||
this._frames = recordingData.frames;
|
||||
this._memory = recordingData.memory;
|
||||
this._ticks = recordingData.ticks;
|
||||
this._profilerData = recordingData.profilerData;
|
||||
|
||||
this.emit(EVENTS.RECORDING_IMPORTED, recordingData);
|
||||
|
||||
// Flush the current recording.
|
||||
this.emit(EVENTS.RECORDING_STARTED);
|
||||
this.emit(EVENTS.RECORDING_STOPPED);
|
||||
this.emit(EVENTS.RECORDING_IMPORTED, model.getAllData(), model);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Creates a new RecordingModel, fires events and stores it
|
||||
* internally in the controller.
|
||||
*/
|
||||
createNewRecording: function () {
|
||||
let model = new RecordingModel({
|
||||
front: gFront,
|
||||
performance: performance
|
||||
});
|
||||
this._recordings.push(model);
|
||||
this.emit(EVENTS.RECORDING_CREATED, model);
|
||||
return model;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the active RecordingModel to `recording`.
|
||||
*/
|
||||
setCurrentRecording: function (recording) {
|
||||
if (this._currentRecording !== recording) {
|
||||
this._currentRecording = recording;
|
||||
this.emit(EVENTS.RECORDING_SELECTED, recording);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the current active RecordingModel.
|
||||
*/
|
||||
getCurrentRecording: function () {
|
||||
return this._currentRecording;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the amount of time elapsed locally after starting a recording.
|
||||
*/
|
||||
getLocalElapsedTime: function() {
|
||||
return performance.now() - this._localStartTime;
|
||||
getLocalElapsedTime: function () {
|
||||
return this.getCurrentRecording().getLocalElapsedTime;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -295,17 +292,7 @@ let PerformanceController = {
|
||||
* @return object
|
||||
*/
|
||||
getInterval: function() {
|
||||
let startTime = this._startTime;
|
||||
let endTime = this._endTime;
|
||||
|
||||
// Compute an approximate ending time for the current recording. This is
|
||||
// needed to ensure that the view updates even when new data is
|
||||
// not being generated.
|
||||
if (endTime == RECORDING_IN_PROGRESS) {
|
||||
endTime = startTime + this.getLocalElapsedTime();
|
||||
}
|
||||
|
||||
return { startTime, endTime };
|
||||
return this.getCurrentRecording().getInterval();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -313,7 +300,7 @@ let PerformanceController = {
|
||||
* @return array
|
||||
*/
|
||||
getMarkers: function() {
|
||||
return this._markers;
|
||||
return this.getCurrentRecording().getMarkers();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -321,7 +308,7 @@ let PerformanceController = {
|
||||
* @return array
|
||||
*/
|
||||
getFrames: function() {
|
||||
return this._frames;
|
||||
return this.getCurrentRecording().getFrames();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -329,7 +316,7 @@ let PerformanceController = {
|
||||
* @return array
|
||||
*/
|
||||
getMemory: function() {
|
||||
return this._memory;
|
||||
return this.getCurrentRecording().getMemory();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -337,7 +324,7 @@ let PerformanceController = {
|
||||
* @return array
|
||||
*/
|
||||
getTicks: function() {
|
||||
return this._ticks;
|
||||
return this.getCurrentRecording().getTicks();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -345,48 +332,41 @@ let PerformanceController = {
|
||||
* @return array
|
||||
*/
|
||||
getProfilerData: function() {
|
||||
return this._profilerData;
|
||||
return this.getCurrentRecording().getProfilerData();
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets all the data in this recording.
|
||||
*/
|
||||
getAllData: function() {
|
||||
let interval = this.getInterval();
|
||||
let markers = this.getMarkers();
|
||||
let frames = this.getFrames();
|
||||
let memory = this.getMemory();
|
||||
let ticks = this.getTicks();
|
||||
let profilerData = this.getProfilerData();
|
||||
return { interval, markers, frames, memory, ticks, profilerData };
|
||||
return this.getCurrentRecording().getAllData();
|
||||
},
|
||||
|
||||
/**
|
||||
/**
|
||||
* Get most recently added profile that was triggered manually (via UI)
|
||||
*/
|
||||
_getLatest: function () {
|
||||
for (let i = this._recordings.length - 1; i >= 0; i--) {
|
||||
return this._recordings[i];
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Fired whenever the PerformanceFront emits markers, memory or ticks.
|
||||
*/
|
||||
_onTimelineData: function (eventName, ...data) {
|
||||
// Accumulate markers into an array.
|
||||
if (eventName == "markers") {
|
||||
let [markers] = data;
|
||||
Array.prototype.push.apply(this._markers, markers);
|
||||
}
|
||||
// Accumulate stack frames into an array.
|
||||
else if (eventName == "frames") {
|
||||
let [delta, frames] = data;
|
||||
Array.prototype.push.apply(this._frames, frames);
|
||||
}
|
||||
// Accumulate memory measurements into an array.
|
||||
else if (eventName == "memory") {
|
||||
let [delta, measurement] = data;
|
||||
this._memory.push({ delta, value: measurement.total / 1024 / 1024 });
|
||||
}
|
||||
// Save the accumulated refresh driver ticks.
|
||||
else if (eventName == "ticks") {
|
||||
let [delta, timestamps] = data;
|
||||
this._ticks = timestamps;
|
||||
}
|
||||
_onTimelineData: function (...data) {
|
||||
this._recordings.forEach(profile => profile.addTimelineData.apply(profile, data));
|
||||
this.emit(EVENTS.TIMELINE_DATA, ...data);
|
||||
},
|
||||
|
||||
this.emit(EVENTS.TIMELINE_DATA, eventName, ...data);
|
||||
/**
|
||||
* Fired from RecordingsView, we listen on the PerformanceController
|
||||
* so we can set it here and re-emit on the controller, where all views can listen.
|
||||
*/
|
||||
_onRecordingSelectFromView: function (_, recording) {
|
||||
this.setCurrentRecording(recording);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -13,23 +13,21 @@ let PerformanceView = {
|
||||
initialize: function () {
|
||||
this._recordButton = $("#record-button");
|
||||
this._importButton = $("#import-button");
|
||||
this._exportButton = $("#export-button");
|
||||
|
||||
this._onRecordButtonClick = this._onRecordButtonClick.bind(this);
|
||||
this._onImportButtonClick = this._onImportButtonClick.bind(this);
|
||||
this._onExportButtonClick = this._onExportButtonClick.bind(this);
|
||||
this._lockRecordButton = this._lockRecordButton.bind(this);
|
||||
this._unlockRecordButton = this._unlockRecordButton.bind(this);
|
||||
|
||||
this._recordButton.addEventListener("click", this._onRecordButtonClick);
|
||||
this._importButton.addEventListener("click", this._onImportButtonClick);
|
||||
this._exportButton.addEventListener("click", this._onExportButtonClick);
|
||||
|
||||
// Bind to controller events to unlock the record button
|
||||
PerformanceController.on(EVENTS.RECORDING_STARTED, this._unlockRecordButton);
|
||||
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._unlockRecordButton);
|
||||
|
||||
return promise.all([
|
||||
RecordingsView.initialize(),
|
||||
OverviewView.initialize(),
|
||||
DetailsView.initialize()
|
||||
]);
|
||||
@ -41,12 +39,12 @@ let PerformanceView = {
|
||||
destroy: function () {
|
||||
this._recordButton.removeEventListener("click", this._onRecordButtonClick);
|
||||
this._importButton.removeEventListener("click", this._onImportButtonClick);
|
||||
this._exportButton.removeEventListener("click", this._onExportButtonClick);
|
||||
|
||||
PerformanceController.off(EVENTS.RECORDING_STARTED, this._unlockRecordButton);
|
||||
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._unlockRecordButton);
|
||||
|
||||
return promise.all([
|
||||
RecordingsView.destroy(),
|
||||
OverviewView.destroy(),
|
||||
DetailsView.destroy()
|
||||
]);
|
||||
@ -94,23 +92,6 @@ let PerformanceView = {
|
||||
if (fp.show() == Ci.nsIFilePicker.returnOK) {
|
||||
this.emit(EVENTS.UI_IMPORT_RECORDING, fp.file);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for clicking the export button.
|
||||
*/
|
||||
_onExportButtonClick: 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) {
|
||||
this.emit(EVENTS.UI_EXPORT_RECORDING, fp.file);
|
||||
}
|
||||
}});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -16,93 +16,96 @@
|
||||
<script src="chrome://browser/content/devtools/theme-switching.js"/>
|
||||
<script type="application/javascript" src="performance/performance-controller.js"/>
|
||||
<script type="application/javascript" src="performance/performance-view.js"/>
|
||||
<script type="application/javascript" src="performance/recording-model.js"/>
|
||||
<script type="application/javascript" src="performance/views/overview.js"/>
|
||||
<script type="application/javascript" src="performance/views/details.js"/>
|
||||
<script type="application/javascript" src="performance/views/details-call-tree.js"/>
|
||||
<script type="application/javascript" src="performance/views/details-waterfall.js"/>
|
||||
<script type="application/javascript" src="performance/views/details-flamegraph.js"/>
|
||||
<script type="application/javascript" src="performance/views/recordings.js"/>
|
||||
|
||||
<vbox class="theme-body" flex="1">
|
||||
<toolbar id="performance-toolbar" class="devtools-toolbar">
|
||||
<hbox id="performance-toolbar-controls-recordings" class="devtools-toolbarbutton-group">
|
||||
<toolbarbutton id="record-button"
|
||||
class="devtools-toolbarbutton"
|
||||
tooltiptext="&profilerUI.recordButton.tooltip;"/>
|
||||
</hbox>
|
||||
<hbox id="performance-toolbar-controls-detail-views" class="devtools-toolbarbutton-group">
|
||||
<toolbarbutton id="select-waterfall-view"
|
||||
class="devtools-toolbarbutton"
|
||||
data-view="waterfall" />
|
||||
<toolbarbutton id="select-calltree-view"
|
||||
class="devtools-toolbarbutton"
|
||||
data-view="calltree" />
|
||||
<toolbarbutton id="select-flamegraph-view"
|
||||
class="devtools-toolbarbutton"
|
||||
data-view="flamegraph" />
|
||||
</hbox>
|
||||
<spacer flex="1"></spacer>
|
||||
<hbox id="performance-toolbar-controls-storage" class="devtools-toolbarbutton-group">
|
||||
<toolbarbutton id="import-button"
|
||||
class="devtools-toolbarbutton"
|
||||
label="&profilerUI.importButton;"/>
|
||||
<toolbarbutton id="export-button"
|
||||
class="devtools-toolbarbutton"
|
||||
label="&profilerUI.exportButton;"/>
|
||||
<toolbarbutton id="clear-button"
|
||||
class="devtools-toolbarbutton"
|
||||
label="&profilerUI.clearButton;"/>
|
||||
</hbox>
|
||||
</toolbar>
|
||||
|
||||
<vbox id="overview-pane">
|
||||
<hbox id="markers-overview"/>
|
||||
<hbox id="memory-overview"/>
|
||||
<hbox id="time-framerate"/>
|
||||
</vbox>
|
||||
|
||||
<deck id="details-pane" flex="1">
|
||||
|
||||
<hbox id="waterfall-view" flex="1">
|
||||
<vbox id="waterfall-breakdown" flex="1" />
|
||||
<splitter class="devtools-side-splitter"/>
|
||||
<vbox id="waterfall-details"
|
||||
class="theme-sidebar"
|
||||
width="150"
|
||||
height="150"/>
|
||||
</hbox>
|
||||
|
||||
<vbox id="calltree-view" flex="1">
|
||||
<hbox class="call-tree-headers-container">
|
||||
<label class="plain call-tree-header"
|
||||
type="duration"
|
||||
crop="end"
|
||||
value="&profilerUI.table.totalDuration;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="percentage"
|
||||
crop="end"
|
||||
value="&profilerUI.table.totalPercentage;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="self-duration"
|
||||
crop="end"
|
||||
value="&profilerUI.table.selfDuration;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="self-percentage"
|
||||
crop="end"
|
||||
value="&profilerUI.table.selfPercentage;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="samples"
|
||||
crop="end"
|
||||
value="&profilerUI.table.samples;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="function"
|
||||
crop="end"
|
||||
value="&profilerUI.table.function;"/>
|
||||
<hbox class="theme-body" flex="1">
|
||||
<vbox id="recordings-pane">
|
||||
<toolbar id="recordings-toolbar"
|
||||
class="devtools-toolbar">
|
||||
<hbox id="recordings-controls"
|
||||
class="devtools-toolbarbutton-group">
|
||||
<toolbarbutton id="record-button"
|
||||
class="devtools-toolbarbutton"
|
||||
tooltiptext="&profilerUI.recordButton.tooltip;"/>
|
||||
<toolbarbutton id="import-button"
|
||||
class="devtools-toolbarbutton"
|
||||
label="&profilerUI.importButton;"/>
|
||||
<toolbarbutton id="clear-button"
|
||||
class="devtools-toolbarbutton"
|
||||
label="&profilerUI.clearButton;"/>
|
||||
</hbox>
|
||||
<vbox class="call-tree-cells-container" flex="1"/>
|
||||
</vbox>
|
||||
</toolbar>
|
||||
<vbox id="recordings-list" flex="1"/>
|
||||
</vbox>
|
||||
<vbox flex="1">
|
||||
<toolbar id="performance-toolbar" class="devtools-toolbar">
|
||||
<hbox id="performance-toolbar-controls-detail-views" class="devtools-toolbarbutton-group">
|
||||
<toolbarbutton id="select-waterfall-view"
|
||||
class="devtools-toolbarbutton"
|
||||
data-view="waterfall" />
|
||||
<toolbarbutton id="select-calltree-view"
|
||||
class="devtools-toolbarbutton"
|
||||
data-view="calltree" />
|
||||
<toolbarbutton id="select-flamegraph-view"
|
||||
class="devtools-toolbarbutton"
|
||||
data-view="flamegraph" />
|
||||
</hbox>
|
||||
<spacer flex="1"></spacer>
|
||||
</toolbar>
|
||||
|
||||
<hbox id="flamegraph-view" flex="1">
|
||||
</hbox>
|
||||
</deck>
|
||||
</vbox>
|
||||
<vbox id="overview-pane">
|
||||
<hbox id="markers-overview"/>
|
||||
<hbox id="memory-overview"/>
|
||||
<hbox id="time-framerate"/>
|
||||
</vbox>
|
||||
<deck id="details-pane" flex="1">
|
||||
<hbox id="waterfall-view" flex="1">
|
||||
<vbox id="waterfall-breakdown" flex="1" />
|
||||
<splitter class="devtools-side-splitter"/>
|
||||
<vbox id="waterfall-details"
|
||||
class="theme-sidebar"
|
||||
width="150"
|
||||
height="150"/>
|
||||
</hbox>
|
||||
|
||||
<vbox id="calltree-view" flex="1">
|
||||
<hbox class="call-tree-headers-container">
|
||||
<label class="plain call-tree-header"
|
||||
type="duration"
|
||||
crop="end"
|
||||
value="&profilerUI.table.totalDuration;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="percentage"
|
||||
crop="end"
|
||||
value="&profilerUI.table.totalPercentage;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="self-duration"
|
||||
crop="end"
|
||||
value="&profilerUI.table.selfDuration;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="self-percentage"
|
||||
crop="end"
|
||||
value="&profilerUI.table.selfPercentage;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="samples"
|
||||
crop="end"
|
||||
value="&profilerUI.table.samples;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="function"
|
||||
crop="end"
|
||||
value="&profilerUI.table.function;"/>
|
||||
</hbox>
|
||||
<vbox class="call-tree-cells-container" flex="1"/>
|
||||
</vbox>
|
||||
<hbox id="flamegraph-view" flex="1">
|
||||
</hbox>
|
||||
</deck>
|
||||
</vbox>
|
||||
</hbox>
|
||||
</window>
|
||||
|
@ -39,4 +39,7 @@ support-files =
|
||||
[browser_perf_recordings-io-01.js]
|
||||
[browser_perf_recordings-io-02.js]
|
||||
[browser_perf_recordings-io-03.js]
|
||||
[browser_perf-recording-selected-01.js]
|
||||
[browser_perf-recording-selected-02.js]
|
||||
[browser_perf-recording-selected-03.js]
|
||||
[browser_perf_recordings-io-04.js]
|
||||
|
@ -0,0 +1,35 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler correctly handles multiple recordings and can
|
||||
* successfully switch between them.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL);
|
||||
let { EVENTS, PerformanceController, RecordingsView } = panel.panelWin;
|
||||
|
||||
yield startRecording(panel);
|
||||
yield stopRecording(panel);
|
||||
|
||||
yield startRecording(panel);
|
||||
yield stopRecording(panel);
|
||||
|
||||
is(RecordingsView.itemCount, 2,
|
||||
"There should be two recordings visible.");
|
||||
is(RecordingsView.selectedIndex, 1,
|
||||
"The second recording item should be selected.");
|
||||
|
||||
let select = once(PerformanceController, EVENTS.RECORDING_SELECTED);
|
||||
RecordingsView.selectedIndex = 0;
|
||||
yield select;
|
||||
|
||||
is(RecordingsView.itemCount, 2,
|
||||
"There should still be two recordings visible.");
|
||||
is(RecordingsView.selectedIndex, 0,
|
||||
"The first recording item should be selected.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
@ -0,0 +1,45 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler correctly handles multiple recordings and can
|
||||
* successfully switch between them, even when one of them is in progress.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL);
|
||||
let { EVENTS, PerformanceController, RecordingsView } = panel.panelWin;
|
||||
|
||||
yield startRecording(panel);
|
||||
yield stopRecording(panel);
|
||||
|
||||
yield startRecording(panel);
|
||||
|
||||
is(RecordingsView.itemCount, 2,
|
||||
"There should be two recordings visible.");
|
||||
is(RecordingsView.selectedIndex, 1,
|
||||
"The new recording item should be selected.");
|
||||
|
||||
let select = once(PerformanceController, EVENTS.RECORDING_SELECTED);
|
||||
RecordingsView.selectedIndex = 0;
|
||||
yield select;
|
||||
|
||||
is(RecordingsView.itemCount, 2,
|
||||
"There should still be two recordings visible.");
|
||||
is(RecordingsView.selectedIndex, 0,
|
||||
"The first recording item should be selected now.");
|
||||
|
||||
select = once(PerformanceController, EVENTS.RECORDING_SELECTED);
|
||||
RecordingsView.selectedIndex = 1;
|
||||
yield select;
|
||||
|
||||
is(RecordingsView.itemCount, 2,
|
||||
"There should still be two recordings visible.");
|
||||
is(RecordingsView.selectedIndex, 1,
|
||||
"The second recording item should be selected again.");
|
||||
|
||||
yield stopRecording(panel);
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
@ -0,0 +1,33 @@
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler UI does not forget that recording is active when
|
||||
* selected recording changes. Bug 1060885.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL);
|
||||
let { $, EVENTS, PerformanceController, RecordingsView } = panel.panelWin;
|
||||
|
||||
yield startRecording(panel);
|
||||
yield stopRecording(panel);
|
||||
|
||||
yield startRecording(panel);
|
||||
|
||||
info("Selecting recording #0 and waiting for it to be displayed.");
|
||||
let select = once(PerformanceController, EVENTS.RECORDING_SELECTED);
|
||||
RecordingsView.selectedIndex = 0;
|
||||
yield select;
|
||||
|
||||
ok($("#record-button").hasAttribute("checked"),
|
||||
"Button is still checked after selecting another item.");
|
||||
|
||||
ok(!$("#record-button").hasAttribute("locked"),
|
||||
"Button is not locked after selecting another item.");
|
||||
|
||||
yield stopRecording(panel);
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
@ -23,7 +23,7 @@ let test = Task.async(function*() {
|
||||
file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
|
||||
|
||||
let exported = once(PerformanceController, EVENTS.RECORDING_EXPORTED);
|
||||
yield PerformanceController.exportRecording("", file);
|
||||
yield PerformanceController.exportRecording("", PerformanceController.getCurrentRecording(), file);
|
||||
|
||||
yield exported;
|
||||
ok(true, "The recording data appears to have been successfully saved.");
|
||||
|
@ -13,10 +13,12 @@ let CallTreeView = {
|
||||
initialize: function () {
|
||||
this._callTree = $(".call-tree-cells-container");
|
||||
this._onRecordingStopped = this._onRecordingStopped.bind(this);
|
||||
this._onRecordingSelected = this._onRecordingSelected.bind(this);
|
||||
this._onRangeChange = this._onRangeChange.bind(this);
|
||||
this._onLink = this._onLink.bind(this);
|
||||
|
||||
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
|
||||
PerformanceController.on(EVENTS.RECORDING_SELECTED, this._onRecordingSelected);
|
||||
OverviewView.on(EVENTS.OVERVIEW_RANGE_SELECTED, this._onRangeChange);
|
||||
OverviewView.on(EVENTS.OVERVIEW_RANGE_CLEARED, this._onRangeChange);
|
||||
},
|
||||
@ -26,6 +28,7 @@ let CallTreeView = {
|
||||
*/
|
||||
destroy: function () {
|
||||
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
|
||||
PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingSelected);
|
||||
OverviewView.off(EVENTS.OVERVIEW_RANGE_SELECTED, this._onRangeChange);
|
||||
OverviewView.off(EVENTS.OVERVIEW_RANGE_CLEARED, this._onRangeChange);
|
||||
},
|
||||
@ -51,6 +54,19 @@ let CallTreeView = {
|
||||
this.render(profilerData);
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when a recording has been selected.
|
||||
*/
|
||||
_onRecordingSelected: function (_, recording) {
|
||||
// If not recording, then this recording is done and we can render all of it
|
||||
// Otherwise, TODO in bug 1120699 will hide the details view altogether if
|
||||
// this is still recording.
|
||||
if (!recording.isRecording()) {
|
||||
let profilerData = recording.getProfilerData();
|
||||
this.render(profilerData);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fired when a range is selected or cleared in the OverviewView.
|
||||
*/
|
||||
|
@ -13,6 +13,7 @@ let WaterfallView = {
|
||||
initialize: Task.async(function *() {
|
||||
this._onRecordingStarted = this._onRecordingStarted.bind(this);
|
||||
this._onRecordingStopped = this._onRecordingStopped.bind(this);
|
||||
this._onRecordingSelected = this._onRecordingSelected.bind(this);
|
||||
this._onMarkerSelected = this._onMarkerSelected.bind(this);
|
||||
this._onResize = this._onResize.bind(this);
|
||||
|
||||
@ -25,6 +26,7 @@ let WaterfallView = {
|
||||
|
||||
PerformanceController.on(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
|
||||
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
|
||||
PerformanceController.on(EVENTS.RECORDING_SELECTED, this._onRecordingSelected);
|
||||
|
||||
this.waterfall.recalculateBounds();
|
||||
}),
|
||||
@ -39,6 +41,7 @@ let WaterfallView = {
|
||||
|
||||
PerformanceController.off(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
|
||||
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
|
||||
PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingSelected);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -67,6 +70,15 @@ let WaterfallView = {
|
||||
this.render();
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when a recording is selected.
|
||||
*/
|
||||
_onRecordingSelected: function (_, recording) {
|
||||
if (!recording.isRecording()) {
|
||||
this.render();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when a marker is selected in the waterfall view,
|
||||
* updating the markers detail view.
|
||||
|
@ -30,6 +30,7 @@ let OverviewView = {
|
||||
initialize: Task.async(function *() {
|
||||
this._onRecordingStarted = this._onRecordingStarted.bind(this);
|
||||
this._onRecordingStopped = this._onRecordingStopped.bind(this);
|
||||
this._onRecordingSelected = this._onRecordingSelected.bind(this);
|
||||
this._onRecordingTick = this._onRecordingTick.bind(this);
|
||||
this._onGraphMouseUp = this._onGraphMouseUp.bind(this);
|
||||
this._onGraphScroll = this._onGraphScroll.bind(this);
|
||||
@ -47,6 +48,7 @@ let OverviewView = {
|
||||
|
||||
PerformanceController.on(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
|
||||
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
|
||||
PerformanceController.on(EVENTS.RECORDING_SELECTED, this._onRecordingSelected);
|
||||
}),
|
||||
|
||||
/**
|
||||
@ -63,6 +65,7 @@ let OverviewView = {
|
||||
clearNamedTimeout("graph-scroll");
|
||||
PerformanceController.off(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
|
||||
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
|
||||
PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingSelected);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -185,27 +188,42 @@ let OverviewView = {
|
||||
/**
|
||||
* Called when recording starts.
|
||||
*/
|
||||
_onRecordingStarted: function () {
|
||||
_onRecordingStarted: function (_, recording) {
|
||||
this._checkSelection(recording);
|
||||
this._timeoutId = setTimeout(this._onRecordingTick, OVERVIEW_UPDATE_INTERVAL);
|
||||
|
||||
this.framerateGraph.dropSelection();
|
||||
this.framerateGraph.selectionEnabled = false;
|
||||
this.markersOverview.selectionEnabled = false;
|
||||
this.memoryOverview.selectionEnabled = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when recording stops.
|
||||
*/
|
||||
_onRecordingStopped: function () {
|
||||
_onRecordingStopped: function (_, recording) {
|
||||
this._checkSelection(recording);
|
||||
clearTimeout(this._timeoutId);
|
||||
this._timeoutId = null;
|
||||
|
||||
this.render(FRAMERATE_GRAPH_HIGH_RES_INTERVAL);
|
||||
},
|
||||
|
||||
this.framerateGraph.selectionEnabled = true;
|
||||
this.markersOverview.selectionEnabled = true;
|
||||
this.memoryOverview.selectionEnabled = true;
|
||||
/**
|
||||
* Called when a new recording is selected.
|
||||
*/
|
||||
_onRecordingSelected: function (_, recording) {
|
||||
this.framerateGraph.dropSelection();
|
||||
this._checkSelection(recording);
|
||||
|
||||
// If timeout exists, we have something recording, so
|
||||
// this will still tick away at rendering. Otherwise, force a render.
|
||||
if (!this._timeoutId) {
|
||||
this.render(FRAMERATE_GRAPH_HIGH_RES_INTERVAL);
|
||||
}
|
||||
},
|
||||
|
||||
_checkSelection: function (recording) {
|
||||
let selectionEnabled = !recording.isRecording();
|
||||
this.framerateGraph.selectionEnabled = selectionEnabled;
|
||||
this.markersOverview.selectionEnabled = selectionEnabled;
|
||||
this.memoryOverview.selectionEnabled = selectionEnabled;
|
||||
}
|
||||
};
|
||||
|
||||
|
237
browser/devtools/performance/views/recordings.js
Normal file
237
browser/devtools/performance/views/recordings.js
Normal file
@ -0,0 +1,237 @@
|
||||
/* 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.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);
|
||||
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);
|
||||
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.
|
||||
if (recording.getLabel()) {
|
||||
recordingItem = this.getItemForAttachment(e =>
|
||||
e.getLabel() === recording.getLabel());
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
this.emit(EVENTS.RECORDING_SELECTED, recording);
|
||||
},
|
||||
|
||||
/**
|
||||
* Signals that a recording session has ended.
|
||||
*
|
||||
* @param RecordingModel recording
|
||||
* The model of the recording that just stopped.
|
||||
*/
|
||||
_onRecordingStopped: function (_, recording) {
|
||||
let profileLabel = recording.getLabel();
|
||||
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.
|
||||
if (profileLabel) {
|
||||
recordingItem = this.getItemForAttachment(e =>
|
||||
e.profilerData.profileLabel == 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 object recordingData
|
||||
* The profiler and refresh driver ticks data received from the front.
|
||||
* @param RecordingModel model
|
||||
* The recording model containing data on the recording session.
|
||||
*/
|
||||
_onRecordingImported: function (_, recordingData, 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);
|
||||
|
||||
// Fire the selection and allow to propogate.
|
||||
this.emit(EVENTS.RECORDING_SELECTED, model);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Convenient way of emitting events from the RecordingsView.
|
||||
*/
|
||||
EventEmitter.decorate(RecordingsView);
|
@ -755,7 +755,7 @@ var ProjectEditor = Class({
|
||||
if (this.hasUnsavedResources) {
|
||||
return confirm(
|
||||
getLocalizedString("projecteditor.confirmUnsavedTitle"),
|
||||
getLocalizedString("projecteditor.confirmUnsavedLabel")
|
||||
getLocalizedString("projecteditor.confirmUnsavedLabel2")
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,9 @@ function startupWebAudioEditor() {
|
||||
return all([
|
||||
WebAudioEditorController.initialize(),
|
||||
ContextView.initialize(),
|
||||
InspectorView.initialize()
|
||||
InspectorView.initialize(),
|
||||
PropertiesView.initialize(),
|
||||
AutomationView.initialize()
|
||||
]);
|
||||
}
|
||||
|
||||
@ -27,6 +29,8 @@ function shutdownWebAudioEditor() {
|
||||
WebAudioEditorController.destroy(),
|
||||
ContextView.destroy(),
|
||||
InspectorView.destroy(),
|
||||
PropertiesView.destroy(),
|
||||
AutomationView.destroy()
|
||||
]);
|
||||
}
|
||||
|
||||
@ -83,6 +87,7 @@ let WebAudioEditorController = {
|
||||
$("#content").hidden = true;
|
||||
ContextView.resetUI();
|
||||
InspectorView.resetUI();
|
||||
PropertiesView.resetUI();
|
||||
},
|
||||
|
||||
// Since node create and connect are probably executed back to back,
|
||||
|
@ -10,7 +10,8 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
Cu.import("resource:///modules/devtools/gDevTools.jsm");
|
||||
|
||||
const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
|
||||
const devtools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
|
||||
const { require } = devtools;
|
||||
|
||||
let { console } = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
|
||||
let { EventTarget } = require("sdk/event/target");
|
||||
@ -21,6 +22,8 @@ const STRINGS_URI = "chrome://browser/locale/devtools/webaudioeditor.properties"
|
||||
const L10N = new ViewHelpers.L10N(STRINGS_URI);
|
||||
const Telemetry = require("devtools/shared/telemetry");
|
||||
const telemetry = new Telemetry();
|
||||
devtools.lazyImporter(this, "LineGraphWidget",
|
||||
"resource:///modules/devtools/Graphs.jsm");
|
||||
|
||||
// Override DOM promises with Promise.jsm helpers
|
||||
const { defer, all } = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
|
||||
@ -53,11 +56,17 @@ const EVENTS = {
|
||||
// When an audio node is finished loading in the Properties tab.
|
||||
UI_PROPERTIES_TAB_RENDERED: "WebAudioEditor:UIPropertiesTabRendered",
|
||||
|
||||
// When an audio node is finished loading in the Automation tab.
|
||||
UI_AUTOMATION_TAB_RENDERED: "WebAudioEditor:UIAutomationTabRendered",
|
||||
|
||||
// When the Audio Context graph finishes rendering.
|
||||
// Is called with two arguments, first representing number of nodes
|
||||
// rendered, second being the number of edge connections rendering (not counting
|
||||
// param edges), followed by the count of the param edges rendered.
|
||||
UI_GRAPH_RENDERED: "WebAudioEditor:UIGraphRendered"
|
||||
UI_GRAPH_RENDERED: "WebAudioEditor:UIGraphRendered",
|
||||
|
||||
// Called when the inspector splitter is moved and resized.
|
||||
UI_INSPECTOR_RESIZE: "WebAudioEditor:UIInspectorResize"
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -85,6 +85,17 @@ const AudioNodeModel = Class({
|
||||
return this.actor.getParams();
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a promise that resolves to an object containing an
|
||||
* array of event information and an array of automation data.
|
||||
*
|
||||
* @param String paramName
|
||||
* @return Promise->Array
|
||||
*/
|
||||
getAutomationData: function (paramName) {
|
||||
return this.actor.getAutomationData(paramName);
|
||||
},
|
||||
|
||||
/**
|
||||
* Takes a `dagreD3.Digraph` object and adds this node to
|
||||
* the graph to be rendered.
|
||||
|
@ -61,3 +61,6 @@ skip-if = true # bug 1010423
|
||||
[browser_wa_properties-view-media-nodes.js]
|
||||
[browser_wa_properties-view-params.js]
|
||||
[browser_wa_properties-view-params-objects.js]
|
||||
|
||||
[browser_wa_automation-view-01.js]
|
||||
[browser_wa_automation-view-02.js]
|
||||
|
@ -39,8 +39,6 @@ add_task(function*() {
|
||||
}
|
||||
else if (param === "curve") {
|
||||
is(flags["Float32Array"], true, "`curve` param has Float32Array flag");
|
||||
} else {
|
||||
is(Object.keys(flags), 0, type + "-" + param + " has no flags set")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,8 +36,6 @@ add_task(function*() {
|
||||
}
|
||||
else if (param === "curve") {
|
||||
is(flags["Float32Array"], true, "`curve` param has Float32Array flag");
|
||||
} else {
|
||||
is(Object.keys(flags), 0, type + "-" + param + " has no flags set")
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -0,0 +1,55 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that automation view shows the correct view depending on if events
|
||||
* or params exist.
|
||||
*/
|
||||
|
||||
add_task(function*() {
|
||||
let { target, panel } = yield initWebAudioEditor(AUTOMATION_URL);
|
||||
let { panelWin } = panel;
|
||||
let { gFront, $, $$, EVENTS } = panelWin;
|
||||
|
||||
let started = once(gFront, "start-context");
|
||||
|
||||
reload(target);
|
||||
|
||||
let [actors] = yield Promise.all([
|
||||
get3(gFront, "create-node"),
|
||||
waitForGraphRendered(panelWin, 3, 2)
|
||||
]);
|
||||
let nodeIds = actors.map(actor => actor.actorID);
|
||||
|
||||
// Oscillator node
|
||||
click(panelWin, findGraphNode(panelWin, nodeIds[1]));
|
||||
yield waitForInspectorRender(panelWin, EVENTS);
|
||||
click(panelWin, $("#automation-tab"));
|
||||
|
||||
ok(isVisible($("#automation-graph-container")), "graph container should be visible");
|
||||
ok(isVisible($("#automation-content")), "automation content should be visible");
|
||||
ok(!isVisible($("#automation-no-events")), "no-events panel should not be visible");
|
||||
ok(!isVisible($("#automation-empty")), "empty panel should not be visible");
|
||||
|
||||
// Gain node
|
||||
click(panelWin, findGraphNode(panelWin, nodeIds[2]));
|
||||
yield waitForInspectorRender(panelWin, EVENTS);
|
||||
click(panelWin, $("#automation-tab"));
|
||||
|
||||
ok(!isVisible($("#automation-graph-container")), "graph container should be visible");
|
||||
ok(isVisible($("#automation-content")), "automation content should not be visible");
|
||||
ok(isVisible($("#automation-no-events")), "no-events panel should be visible");
|
||||
ok(!isVisible($("#automation-empty")), "empty panel should not be visible");
|
||||
|
||||
// destination node
|
||||
click(panelWin, findGraphNode(panelWin, nodeIds[0]));
|
||||
yield waitForInspectorRender(panelWin, EVENTS);
|
||||
click(panelWin, $("#automation-tab"));
|
||||
|
||||
ok(!isVisible($("#automation-graph-container")), "graph container should not be visible");
|
||||
ok(!isVisible($("#automation-content")), "automation content should not be visible");
|
||||
ok(!isVisible($("#automation-no-events")), "no-events panel should not be visible");
|
||||
ok(isVisible($("#automation-empty")), "empty panel should be visible");
|
||||
|
||||
yield teardown(target);
|
||||
});
|
@ -0,0 +1,56 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that automation view selects the first parameter by default and
|
||||
* switching between AudioParam rerenders the graph.
|
||||
*/
|
||||
|
||||
add_task(function*() {
|
||||
let { target, panel } = yield initWebAudioEditor(AUTOMATION_URL);
|
||||
let { panelWin } = panel;
|
||||
let { gFront, $, $$, EVENTS, AutomationView } = panelWin;
|
||||
|
||||
let started = once(gFront, "start-context");
|
||||
|
||||
reload(target);
|
||||
|
||||
let [actors] = yield Promise.all([
|
||||
get3(gFront, "create-node"),
|
||||
waitForGraphRendered(panelWin, 3, 2)
|
||||
]);
|
||||
let nodeIds = actors.map(actor => actor.actorID);
|
||||
|
||||
|
||||
// Oscillator node
|
||||
click(panelWin, findGraphNode(panelWin, nodeIds[1]));
|
||||
yield waitForInspectorRender(panelWin, EVENTS);
|
||||
click(panelWin, $("#automation-tab"));
|
||||
|
||||
ok(AutomationView._selectedParamName, "frequency",
|
||||
"AutomatioView is set on 'frequency'");
|
||||
ok($(".automation-param-button[data-param='frequency']").getAttribute("selected"),
|
||||
"frequency param should be selected on load");
|
||||
ok(!$(".automation-param-button[data-param='detune']").getAttribute("selected"),
|
||||
"detune param should not be selected on load");
|
||||
ok(isVisible($("#automation-content")), "automation content should be visible");
|
||||
ok(isVisible($("#automation-graph-container")), "graph container should be visible");
|
||||
ok(!isVisible($("#automation-no-events")), "no-events panel should not be visible");
|
||||
|
||||
click(panelWin, $(".automation-param-button[data-param='detune']"));
|
||||
yield once(panelWin, EVENTS.UI_AUTOMATION_TAB_RENDERED);
|
||||
|
||||
ok(true, "automation tab rerendered");
|
||||
|
||||
ok(AutomationView._selectedParamName, "detune",
|
||||
"AutomatioView is set on 'detune'");
|
||||
ok(!$(".automation-param-button[data-param='frequency']").getAttribute("selected"),
|
||||
"frequency param should not be selected after clicking detune");
|
||||
ok($(".automation-param-button[data-param='detune']").getAttribute("selected"),
|
||||
"detune param should be selected after clicking detune");
|
||||
ok(isVisible($("#automation-content")), "automation content should be visible");
|
||||
ok(!isVisible($("#automation-graph-container")), "graph container should not be visible");
|
||||
ok(isVisible($("#automation-no-events")), "no-events panel should be visible");
|
||||
|
||||
yield teardown(target);
|
||||
});
|
@ -51,8 +51,9 @@ add_task(function*() {
|
||||
ok(!isVisible($("#web-audio-editor-tabs")),
|
||||
"InspectorView tabs are still hidden.");
|
||||
|
||||
let nodeSet = once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET);
|
||||
click(panelWin, findGraphNode(panelWin, nodeIds[1]));
|
||||
yield once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET);
|
||||
yield nodeSet;
|
||||
|
||||
ok(!isVisible($("#web-audio-editor-details-pane-empty")),
|
||||
"Empty message hides even when loading node while open.");
|
||||
|
@ -30,12 +30,13 @@ add_task(function*() {
|
||||
is($("#web-audio-inspector-title").value, "AudioNode Inspector",
|
||||
"Inspector should have default title when empty.");
|
||||
|
||||
click(panelWin, findGraphNode(panelWin, nodeIds[1]));
|
||||
// Wait for the node to be set as well as the inspector to come fully into the view
|
||||
yield Promise.all([
|
||||
let nodeSet = Promise.all([
|
||||
once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET),
|
||||
once(panelWin, EVENTS.UI_INSPECTOR_TOGGLED)
|
||||
]);
|
||||
click(panelWin, findGraphNode(panelWin, nodeIds[1]));
|
||||
yield nodeSet;
|
||||
|
||||
ok(InspectorView.isVisible(), "InspectorView shown once node selected.");
|
||||
ok(!isVisible($("#web-audio-editor-details-pane-empty")),
|
||||
@ -49,8 +50,9 @@ add_task(function*() {
|
||||
is($("#web-audio-editor-tabs").selectedIndex, 0,
|
||||
"default tab selected should be the parameters tab.");
|
||||
|
||||
nodeSet = once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET);
|
||||
click(panelWin, findGraphNode(panelWin, nodeIds[2]));
|
||||
yield once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET);
|
||||
yield nodeSet;
|
||||
|
||||
is($("#web-audio-inspector-title").value, "Gain",
|
||||
"Inspector title updates when a new node is selected.");
|
||||
|
@ -8,8 +8,8 @@
|
||||
add_task(function*() {
|
||||
let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
|
||||
let { panelWin } = panel;
|
||||
let { gFront, $, $$, EVENTS, InspectorView } = panelWin;
|
||||
let gVars = InspectorView._propsView;
|
||||
let { gFront, $, $$, EVENTS, PropertiesView } = panelWin;
|
||||
let gVars = PropertiesView._propsView;
|
||||
|
||||
let started = once(gFront, "start-context");
|
||||
|
||||
@ -24,7 +24,7 @@ add_task(function*() {
|
||||
click(panelWin, findGraphNode(panelWin, nodeIds[1]));
|
||||
// Wait for the node to be set as well as the inspector to come fully into the view
|
||||
yield Promise.all([
|
||||
once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET),
|
||||
waitForInspectorRender(panelWin, EVENTS),
|
||||
once(panelWin, EVENTS.UI_INSPECTOR_TOGGLED)
|
||||
]);
|
||||
|
||||
@ -37,17 +37,17 @@ add_task(function*() {
|
||||
}, "default loaded string");
|
||||
|
||||
click(panelWin, findGraphNode(panelWin, nodeIds[2]));
|
||||
yield once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET);
|
||||
yield waitForInspectorRender(panelWin, EVENTS),
|
||||
checkVariableView(gVars, 0, {
|
||||
"gain": 0
|
||||
}, "default loaded number");
|
||||
|
||||
click(panelWin, findGraphNode(panelWin, nodeIds[1]));
|
||||
yield once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET);
|
||||
yield waitForInspectorRender(panelWin, EVENTS),
|
||||
yield setAndCheck(0, "type", "square", "square", "sets string as string");
|
||||
|
||||
click(panelWin, findGraphNode(panelWin, nodeIds[2]));
|
||||
yield once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET);
|
||||
yield waitForInspectorRender(panelWin, EVENTS),
|
||||
yield setAndCheck(0, "gain", "0.005", 0.005, "sets number as number");
|
||||
yield setAndCheck(0, "gain", "0.1", 0.1, "sets float as float");
|
||||
yield setAndCheck(0, "gain", ".2", 0.2, "sets float without leading zero as float");
|
||||
|
@ -8,8 +8,8 @@
|
||||
add_task(function*() {
|
||||
let { target, panel } = yield initWebAudioEditor(COMPLEX_CONTEXT_URL);
|
||||
let { panelWin } = panel;
|
||||
let { gFront, $, $$, EVENTS, InspectorView } = panelWin;
|
||||
let gVars = InspectorView._propsView;
|
||||
let { gFront, $, $$, EVENTS, PropertiesView } = panelWin;
|
||||
let gVars = PropertiesView._propsView;
|
||||
|
||||
let started = once(gFront, "start-context");
|
||||
|
||||
@ -24,7 +24,7 @@ add_task(function*() {
|
||||
click(panelWin, findGraphNode(panelWin, nodeIds[3]));
|
||||
// Wait for the node to be set as well as the inspector to come fully into the view
|
||||
yield Promise.all([
|
||||
once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET),
|
||||
waitForInspectorRender(panelWin, EVENTS),
|
||||
once(panelWin, EVENTS.UI_INSPECTOR_TOGGLED),
|
||||
]);
|
||||
|
||||
|
@ -37,8 +37,8 @@ function waitForDeviceClosed() {
|
||||
add_task(function*() {
|
||||
let { target, panel } = yield initWebAudioEditor(MEDIA_NODES_URL);
|
||||
let { panelWin } = panel;
|
||||
let { gFront, $, $$, EVENTS, InspectorView } = panelWin;
|
||||
let gVars = InspectorView._propsView;
|
||||
let { gFront, $, $$, EVENTS, PropertiesView } = panelWin;
|
||||
let gVars = PropertiesView._propsView;
|
||||
|
||||
// Auto enable getUserMedia
|
||||
let mediaPermissionPref = Services.prefs.getBoolPref(MEDIA_PERMISSION);
|
||||
@ -59,7 +59,7 @@ add_task(function*() {
|
||||
|
||||
for (let i = 0; i < types.length; i++) {
|
||||
click(panelWin, findGraphNode(panelWin, nodeIds[i]));
|
||||
yield once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET);
|
||||
yield waitForInspectorRender(panelWin, EVENTS);
|
||||
checkVariableView(gVars, 0, NODE_DEFAULT_VALUES[types[i]], types[i]);
|
||||
}
|
||||
|
||||
|
@ -9,8 +9,8 @@
|
||||
add_task(function*() {
|
||||
let { target, panel } = yield initWebAudioEditor(BUFFER_AND_ARRAY_URL);
|
||||
let { panelWin } = panel;
|
||||
let { gFront, $, $$, EVENTS, InspectorView } = panelWin;
|
||||
let gVars = InspectorView._propsView;
|
||||
let { gFront, $, $$, EVENTS, PropertiesView } = panelWin;
|
||||
let gVars = PropertiesView._propsView;
|
||||
|
||||
let started = once(gFront, "start-context");
|
||||
|
||||
@ -23,7 +23,7 @@ add_task(function*() {
|
||||
let nodeIds = actors.map(actor => actor.actorID);
|
||||
|
||||
click(panelWin, findGraphNode(panelWin, nodeIds[2]));
|
||||
yield once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET);
|
||||
yield waitForInspectorRender(panelWin, EVENTS);
|
||||
checkVariableView(gVars, 0, {
|
||||
"curve": "Float32Array"
|
||||
}, "WaveShaper's `curve` is listed as an `Float32Array`.");
|
||||
@ -33,7 +33,7 @@ add_task(function*() {
|
||||
ok(state, "Float32Array property should not have a dropdown.");
|
||||
|
||||
click(panelWin, findGraphNode(panelWin, nodeIds[1]));
|
||||
yield once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET);
|
||||
yield waitForInspectorRender(panelWin, EVENTS);
|
||||
checkVariableView(gVars, 0, {
|
||||
"buffer": "AudioBuffer"
|
||||
}, "AudioBufferSourceNode's `buffer` is listed as an `AudioBuffer`.");
|
||||
|
@ -9,8 +9,8 @@
|
||||
add_task(function*() {
|
||||
let { target, panel } = yield initWebAudioEditor(SIMPLE_NODES_URL);
|
||||
let { panelWin } = panel;
|
||||
let { gFront, $, $$, EVENTS, InspectorView } = panelWin;
|
||||
let gVars = InspectorView._propsView;
|
||||
let { gFront, $, $$, EVENTS, PropertiesView } = panelWin;
|
||||
let gVars = PropertiesView._propsView;
|
||||
|
||||
let started = once(gFront, "start-context");
|
||||
|
||||
@ -30,7 +30,7 @@ add_task(function*() {
|
||||
|
||||
for (let i = 0; i < types.length; i++) {
|
||||
click(panelWin, findGraphNode(panelWin, nodeIds[i]));
|
||||
yield once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET);
|
||||
yield waitForInspectorRender(panelWin, EVENTS);
|
||||
checkVariableView(gVars, 0, NODE_DEFAULT_VALUES[types[i]], types[i]);
|
||||
}
|
||||
|
||||
|
@ -8,8 +8,8 @@
|
||||
add_task(function*() {
|
||||
let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
|
||||
let { panelWin } = panel;
|
||||
let { gFront, $, $$, EVENTS, InspectorView } = panelWin;
|
||||
let gVars = InspectorView._propsView;
|
||||
let { gFront, $, $$, EVENTS, PropertiesView } = panelWin;
|
||||
let gVars = PropertiesView._propsView;
|
||||
|
||||
let started = once(gFront, "start-context");
|
||||
|
||||
@ -23,19 +23,19 @@ add_task(function*() {
|
||||
|
||||
// Gain node
|
||||
click(panelWin, findGraphNode(panelWin, nodeIds[2]));
|
||||
yield once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET);
|
||||
yield waitForInspectorRender(panelWin, EVENTS);
|
||||
|
||||
ok(isVisible($("#properties-tabpanel-content")), "Parameters shown when they exist.");
|
||||
ok(!isVisible($("#properties-tabpanel-content-empty")),
|
||||
ok(isVisible($("#properties-content")), "Parameters shown when they exist.");
|
||||
ok(!isVisible($("#properties-empty")),
|
||||
"Empty message hidden when AudioParams exist.");
|
||||
|
||||
// Destination node
|
||||
click(panelWin, findGraphNode(panelWin, nodeIds[0]));
|
||||
yield once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET);
|
||||
yield waitForInspectorRender(panelWin, EVENTS);
|
||||
|
||||
ok(!isVisible($("#properties-tabpanel-content")),
|
||||
ok(!isVisible($("#properties-content")),
|
||||
"Parameters hidden when they don't exist.");
|
||||
ok(isVisible($("#properties-tabpanel-content-empty")),
|
||||
ok(isVisible($("#properties-empty")),
|
||||
"Empty message shown when no AudioParams exist.");
|
||||
|
||||
yield teardown(target);
|
||||
|
@ -35,12 +35,13 @@ add_task(function*() {
|
||||
is(eventName, exp[0], "correct eventName in event");
|
||||
is(paramName, "frequency", "correct paramName in event");
|
||||
is(args.length, exp.length - 1, "correct length in args");
|
||||
|
||||
args.forEach((a, i) => {
|
||||
// In the case of an array
|
||||
if (typeof a === "object") {
|
||||
a.forEach((f, j) => is(f, exp[i + 1][j], "correct argument in args"));
|
||||
a.forEach((f, j) => is(f, exp[i + 1][j], `correct argument in Float32Array: ${f}`));
|
||||
} else {
|
||||
is(a, exp[i + 1], "correct argument in args");
|
||||
is(a, exp[i + 1], `correct ${i+1}th argument in args: ${a}`);
|
||||
}
|
||||
});
|
||||
events.push([eventName].concat(args));
|
||||
|
@ -231,7 +231,7 @@ function checkVariableView (view, index, hash, description = "") {
|
||||
// If node shouldn't display any properties, ensure that the 'empty' message is
|
||||
// visible
|
||||
if (!variables.length) {
|
||||
ok(isVisible(scope.window.$("#properties-tabpanel-content-empty")),
|
||||
ok(isVisible(scope.window.$("#properties-empty")),
|
||||
description + " should show the empty properties tab.");
|
||||
return;
|
||||
}
|
||||
@ -413,10 +413,10 @@ function checkAutomationValue (values, time, expected) {
|
||||
*/
|
||||
function getValueAt (values, time) {
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
if (values[i].t === time) {
|
||||
if (values[i].delta === time) {
|
||||
return values[i].value;
|
||||
}
|
||||
if (values[i].t > time) {
|
||||
if (values[i].delta > time) {
|
||||
return (values[i - 1].value + values[i].value) / 2;
|
||||
}
|
||||
}
|
||||
@ -424,6 +424,16 @@ function checkAutomationValue (values, time, expected) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for all inspector tabs to complete rendering.
|
||||
*/
|
||||
function waitForInspectorRender (panelWin, EVENTS) {
|
||||
return Promise.all([
|
||||
once(panelWin, EVENTS.UI_PROPERTIES_TAB_RENDERED),
|
||||
once(panelWin, EVENTS.UI_AUTOMATION_TAB_RENDERED)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* List of audio node properties to test against expectations of the AudioNode actor
|
||||
*/
|
||||
|
159
browser/devtools/webaudioeditor/views/automation.js
Normal file
159
browser/devtools/webaudioeditor/views/automation.js
Normal file
@ -0,0 +1,159 @@
|
||||
/* 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 audio node inspector UI.
|
||||
*/
|
||||
|
||||
let AutomationView = {
|
||||
|
||||
/**
|
||||
* Initialization function called when the tool starts up.
|
||||
*/
|
||||
initialize: function () {
|
||||
this._buttons = $("#automation-param-toolbar-buttons");
|
||||
this.graph = new LineGraphWidget($("#automation-graph"), { avg: false });
|
||||
this.graph.selectionEnabled = false;
|
||||
|
||||
this._onButtonClick = this._onButtonClick.bind(this);
|
||||
this._onNodeSet = this._onNodeSet.bind(this);
|
||||
this._onResize = this._onResize.bind(this);
|
||||
|
||||
this._buttons.addEventListener("click", this._onButtonClick);
|
||||
window.on(EVENTS.UI_INSPECTOR_RESIZE, this._onResize);
|
||||
window.on(EVENTS.UI_INSPECTOR_NODE_SET, this._onNodeSet);
|
||||
},
|
||||
|
||||
/**
|
||||
* Destruction function called when the tool cleans up.
|
||||
*/
|
||||
destroy: function () {
|
||||
this._buttons.removeEventListener("click", this._onButtonClick);
|
||||
window.off(EVENTS.UI_INSPECTOR_RESIZE, this._onResize);
|
||||
window.off(EVENTS.UI_INSPECTOR_NODE_SET, this._onNodeSet);
|
||||
},
|
||||
|
||||
/**
|
||||
* Empties out the props view.
|
||||
*/
|
||||
resetUI: function () {
|
||||
this._currentNode = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* On a new node selection, create the Automation panel for
|
||||
* that specific node.
|
||||
*/
|
||||
build: Task.async(function* () {
|
||||
let node = this._currentNode;
|
||||
|
||||
let props = yield node.getParams();
|
||||
let params = props.filter(({ flags }) => flags && flags.param);
|
||||
|
||||
this._createParamButtons(params);
|
||||
|
||||
this._selectedParamName = params[0] ? params[0].param : null;
|
||||
this.render();
|
||||
}),
|
||||
|
||||
/**
|
||||
* Renders the graph for specified `paramName`. Called when
|
||||
* the parameter view is changed, or when new param data events
|
||||
* are fired for the currently specified param.
|
||||
*/
|
||||
render: Task.async(function *() {
|
||||
let node = this._currentNode;
|
||||
let paramName = this._selectedParamName;
|
||||
// Escape if either node or parameter name does not exist.
|
||||
if (!node || !paramName) {
|
||||
this._setState("no-params");
|
||||
window.emit(EVENTS.UI_AUTOMATION_TAB_RENDERED, null);
|
||||
return;
|
||||
}
|
||||
|
||||
let { values, events } = yield node.getAutomationData(paramName);
|
||||
this._setState(events.length ? "show" : "no-events");
|
||||
yield this.graph.setDataWhenReady(values);
|
||||
window.emit(EVENTS.UI_AUTOMATION_TAB_RENDERED, node.id);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Create the buttons for each AudioParam, that when clicked,
|
||||
* render the graph for that AudioParam.
|
||||
*/
|
||||
_createParamButtons: function (params) {
|
||||
this._buttons.innerHTML = "";
|
||||
params.forEach((param, i) => {
|
||||
let button = document.createElement("toolbarbutton");
|
||||
button.setAttribute("class", "devtools-toolbarbutton automation-param-button");
|
||||
button.setAttribute("data-param", param.param);
|
||||
// Set label to the parameter name, should not be L10N'd
|
||||
button.setAttribute("label", param.param);
|
||||
|
||||
// If first button, set to 'selected' for styling
|
||||
if (i === 0) {
|
||||
button.setAttribute("selected", true);
|
||||
}
|
||||
|
||||
this._buttons.appendChild(button);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Internally sets the current audio node and rebuilds appropriate
|
||||
* views.
|
||||
*/
|
||||
_setAudioNode: function (node) {
|
||||
this._currentNode = node;
|
||||
if (this._currentNode) {
|
||||
this.build();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles the subviews to display messages whether or not
|
||||
* the audio node has no AudioParams, no automation events, or
|
||||
* shows the graph.
|
||||
*/
|
||||
_setState: function (state) {
|
||||
let contentView = $("#automation-content");
|
||||
let emptyView = $("#automation-empty");
|
||||
|
||||
let graphView = $("#automation-graph-container");
|
||||
let noEventsView = $("#automation-no-events");
|
||||
|
||||
contentView.hidden = state === "no-params";
|
||||
emptyView.hidden = state !== "no-params";
|
||||
|
||||
graphView.hidden = state !== "show";
|
||||
noEventsView.hidden = state !== "no-events";
|
||||
},
|
||||
|
||||
/**
|
||||
* Event handlers
|
||||
*/
|
||||
|
||||
_onButtonClick: function (e) {
|
||||
Array.forEach($$(".automation-param-button"), $btn => $btn.removeAttribute("selected"));
|
||||
let paramName = e.target.getAttribute("data-param");
|
||||
e.target.setAttribute("selected", true);
|
||||
this._selectedParamName = paramName;
|
||||
this.render();
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the inspector is resized.
|
||||
*/
|
||||
_onResize: function () {
|
||||
this.graph.refresh();
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the inspector view determines a node is selected.
|
||||
*/
|
||||
_onNodeSet: function (_, id) {
|
||||
this._setAudioNode(id != null ? gAudioNodes.get(id) : null);
|
||||
}
|
||||
};
|
@ -38,7 +38,6 @@ let ContextView = {
|
||||
initialize: function() {
|
||||
this._onGraphNodeClick = this._onGraphNodeClick.bind(this);
|
||||
this._onThemeChange = this._onThemeChange.bind(this);
|
||||
this._onNodeSelect = this._onNodeSelect.bind(this);
|
||||
this._onStartContext = this._onStartContext.bind(this);
|
||||
this._onEvent = this._onEvent.bind(this);
|
||||
|
||||
@ -46,7 +45,6 @@ let ContextView = {
|
||||
$('#graph-target').addEventListener('click', this._onGraphNodeClick, false);
|
||||
|
||||
window.on(EVENTS.THEME_CHANGE, this._onThemeChange);
|
||||
window.on(EVENTS.UI_INSPECTOR_NODE_SET, this._onNodeSelect);
|
||||
window.on(EVENTS.START_CONTEXT, this._onStartContext);
|
||||
gAudioNodes.on("*", this._onEvent);
|
||||
},
|
||||
@ -62,7 +60,6 @@ let ContextView = {
|
||||
}
|
||||
$('#graph-target').removeEventListener('click', this._onGraphNodeClick, false);
|
||||
window.off(EVENTS.THEME_CHANGE, this._onThemeChange);
|
||||
window.off(EVENTS.UI_INSPECTOR_NODE_SET, this._onNodeSelect);
|
||||
window.off(EVENTS.START_CONTEXT, this._onStartContext);
|
||||
gAudioNodes.off("*", this._onEvent);
|
||||
},
|
||||
@ -112,7 +109,6 @@ let ContextView = {
|
||||
* Makes the corresponding graph node appear "focused", removing
|
||||
* focused styles from all other nodes. If no `actorID` specified,
|
||||
* make all nodes appear unselected.
|
||||
* Called from UI_INSPECTOR_NODE_SELECT.
|
||||
*/
|
||||
focusNode: function (actorID) {
|
||||
// Remove class "selected" from all nodes
|
||||
@ -272,10 +268,6 @@ let ContextView = {
|
||||
}
|
||||
},
|
||||
|
||||
_onNodeSelect: function (eventName, id) {
|
||||
this.focusNode(id);
|
||||
},
|
||||
|
||||
/**
|
||||
* Fired when the devtools theme changes.
|
||||
*/
|
||||
@ -300,6 +292,9 @@ let ContextView = {
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
window.emit(EVENTS.UI_SELECT_NODE, node.getAttribute("data-id"));
|
||||
let id = node.getAttribute("data-id");
|
||||
|
||||
this.focusNode(id);
|
||||
window.emit(EVENTS.UI_SELECT_NODE, id);
|
||||
}
|
||||
};
|
||||
|
@ -3,25 +3,13 @@
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource:///modules/devtools/VariablesView.jsm");
|
||||
Cu.import("resource:///modules/devtools/VariablesViewController.jsm");
|
||||
|
||||
// Strings for rendering
|
||||
const EXPAND_INSPECTOR_STRING = L10N.getStr("expandInspector");
|
||||
const COLLAPSE_INSPECTOR_STRING = L10N.getStr("collapseInspector");
|
||||
|
||||
// Store width as a preference rather than hardcode
|
||||
// TODO bug 1009056
|
||||
const INSPECTOR_WIDTH = 300;
|
||||
|
||||
const GENERIC_VARIABLES_VIEW_SETTINGS = {
|
||||
searchEnabled: false,
|
||||
editableValueTooltip: "",
|
||||
editableNameTooltip: "",
|
||||
preventDisableOnChange: true,
|
||||
preventDescriptorModifiers: false,
|
||||
eval: () => {}
|
||||
};
|
||||
// Strings for rendering
|
||||
const EXPAND_INSPECTOR_STRING = L10N.getStr("expandInspector");
|
||||
const COLLAPSE_INSPECTOR_STRING = L10N.getStr("collapseInspector");
|
||||
|
||||
/**
|
||||
* Functions handling the audio node inspector UI.
|
||||
@ -41,10 +29,9 @@ let InspectorView = {
|
||||
* Initialization function called when the tool starts up.
|
||||
*/
|
||||
initialize: function () {
|
||||
this._tabsPane = $("#web-audio-editor-tabs");
|
||||
|
||||
// Set up view controller
|
||||
this.el = $("#web-audio-inspector");
|
||||
this.splitter = $("#inspector-splitter");
|
||||
this.el.setAttribute("width", INSPECTOR_WIDTH);
|
||||
this.button = $("#inspector-pane-toggle");
|
||||
mixin(this, ToggleMixin);
|
||||
@ -53,13 +40,11 @@ let InspectorView = {
|
||||
// Hide inspector view on startup
|
||||
this.hideImmediately();
|
||||
|
||||
this._onEval = this._onEval.bind(this);
|
||||
this._onNodeSelect = this._onNodeSelect.bind(this);
|
||||
this._onDestroyNode = this._onDestroyNode.bind(this);
|
||||
this._onResize = this._onResize.bind(this);
|
||||
|
||||
this._propsView = new VariablesView($("#properties-tabpanel-content"), GENERIC_VARIABLES_VIEW_SETTINGS);
|
||||
this._propsView.eval = this._onEval;
|
||||
|
||||
this.splitter.addEventListener("mouseup", this._onResize);
|
||||
window.on(EVENTS.UI_SELECT_NODE, this._onNodeSelect);
|
||||
gAudioNodes.on("remove", this._onDestroyNode);
|
||||
},
|
||||
@ -69,12 +54,13 @@ let InspectorView = {
|
||||
*/
|
||||
destroy: function () {
|
||||
this.unbindToggle();
|
||||
this.splitter.removeEventListener("mouseup", this._onResize);
|
||||
window.off(EVENTS.UI_SELECT_NODE, this._onNodeSelect);
|
||||
gAudioNodes.off("remove", this._onDestroyNode);
|
||||
|
||||
this.el = null;
|
||||
this.button = null;
|
||||
this._tabsPane = null;
|
||||
this.splitter = null;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -96,8 +82,7 @@ let InspectorView = {
|
||||
$("#web-audio-editor-details-pane-empty").setAttribute("hidden", "true");
|
||||
$("#web-audio-editor-tabs").removeAttribute("hidden");
|
||||
this._setTitle();
|
||||
this._buildPropertiesView()
|
||||
.then(() => window.emit(EVENTS.UI_INSPECTOR_NODE_SET, this._currentNode.id));
|
||||
window.emit(EVENTS.UI_INSPECTOR_NODE_SET, this._currentNode.id);
|
||||
}
|
||||
},
|
||||
|
||||
@ -112,7 +97,6 @@ let InspectorView = {
|
||||
* Empties out the props view.
|
||||
*/
|
||||
resetUI: function () {
|
||||
this._propsView.empty();
|
||||
// Set current node to empty to load empty view
|
||||
this.setCurrentAudioNode();
|
||||
|
||||
@ -129,97 +113,10 @@ let InspectorView = {
|
||||
$("#web-audio-inspector-title").setAttribute("value", title);
|
||||
},
|
||||
|
||||
/**
|
||||
* Reconstructs the `Properties` tab in the inspector
|
||||
* with the `this._currentNode` as it's source.
|
||||
*/
|
||||
_buildPropertiesView: Task.async(function* () {
|
||||
let propsView = this._propsView;
|
||||
let node = this._currentNode;
|
||||
propsView.empty();
|
||||
|
||||
let audioParamsScope = propsView.addScope("AudioParams");
|
||||
let props = yield node.getParams();
|
||||
|
||||
// Disable AudioParams VariableView expansion
|
||||
// when there are no props i.e. AudioDestinationNode
|
||||
this._togglePropertiesView(!!props.length);
|
||||
|
||||
props.forEach(({ param, value, flags }) => {
|
||||
let descriptor = {
|
||||
value: value,
|
||||
writable: !flags || !flags.readonly,
|
||||
};
|
||||
let item = audioParamsScope.addItem(param, descriptor);
|
||||
|
||||
// No items should currently display a dropdown
|
||||
item.twisty = false;
|
||||
});
|
||||
|
||||
audioParamsScope.expanded = true;
|
||||
|
||||
window.emit(EVENTS.UI_PROPERTIES_TAB_RENDERED, node.id);
|
||||
}),
|
||||
|
||||
_togglePropertiesView: function (show) {
|
||||
let propsView = $("#properties-tabpanel-content");
|
||||
let emptyView = $("#properties-tabpanel-content-empty");
|
||||
(show ? propsView : emptyView).removeAttribute("hidden");
|
||||
(show ? emptyView : propsView).setAttribute("hidden", "true");
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the scope for AudioParams in the
|
||||
* VariablesView.
|
||||
*
|
||||
* @return Scope
|
||||
*/
|
||||
_getAudioPropertiesScope: function () {
|
||||
return this._propsView.getScopeAtIndex(0);
|
||||
},
|
||||
|
||||
/**
|
||||
* Event handlers
|
||||
*/
|
||||
|
||||
/**
|
||||
* Executed when an audio prop is changed in the UI.
|
||||
*/
|
||||
_onEval: Task.async(function* (variable, value) {
|
||||
let ownerScope = variable.ownerView;
|
||||
let node = this._currentNode;
|
||||
let propName = variable.name;
|
||||
let error;
|
||||
|
||||
if (!variable._initialDescriptor.writable) {
|
||||
error = new Error("Variable " + propName + " is not writable.");
|
||||
} else {
|
||||
// Cast value to proper type
|
||||
try {
|
||||
let number = parseFloat(value);
|
||||
if (!isNaN(number)) {
|
||||
value = number;
|
||||
} else {
|
||||
value = JSON.parse(value);
|
||||
}
|
||||
error = yield node.actor.setParam(propName, value);
|
||||
}
|
||||
catch (e) {
|
||||
error = e;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO figure out how to handle and display set prop errors
|
||||
// and enable `test/brorwser_wa_properties-view-edit.js`
|
||||
// Bug 994258
|
||||
if (!error) {
|
||||
ownerScope.get(propName).setGrip(value);
|
||||
window.emit(EVENTS.UI_SET_PARAM, node.id, propName, value);
|
||||
} else {
|
||||
window.emit(EVENTS.UI_SET_PARAM_ERROR, node.id, propName, value);
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Called on EVENTS.UI_SELECT_NODE, and takes an actorID `id`
|
||||
* and calls `setCurrentAudioNode` to scaffold the inspector view.
|
||||
@ -231,6 +128,10 @@ let InspectorView = {
|
||||
this.show();
|
||||
},
|
||||
|
||||
_onResize: function () {
|
||||
window.emit(EVENTS.UI_INSPECTOR_RESIZE);
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when `DESTROY_NODE` is fired to remove the node from props view if
|
||||
* it's currently selected.
|
||||
|
164
browser/devtools/webaudioeditor/views/properties.js
Normal file
164
browser/devtools/webaudioeditor/views/properties.js
Normal file
@ -0,0 +1,164 @@
|
||||
/* 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";
|
||||
|
||||
Cu.import("resource:///modules/devtools/VariablesView.jsm");
|
||||
Cu.import("resource:///modules/devtools/VariablesViewController.jsm");
|
||||
|
||||
const GENERIC_VARIABLES_VIEW_SETTINGS = {
|
||||
searchEnabled: false,
|
||||
editableValueTooltip: "",
|
||||
editableNameTooltip: "",
|
||||
preventDisableOnChange: true,
|
||||
preventDescriptorModifiers: false,
|
||||
eval: () => {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Functions handling the audio node inspector UI.
|
||||
*/
|
||||
|
||||
let PropertiesView = {
|
||||
|
||||
/**
|
||||
* Initialization function called when the tool starts up.
|
||||
*/
|
||||
initialize: function () {
|
||||
this._onEval = this._onEval.bind(this);
|
||||
this._onNodeSet = this._onNodeSet.bind(this);
|
||||
|
||||
window.on(EVENTS.UI_INSPECTOR_NODE_SET, this._onNodeSet);
|
||||
this._propsView = new VariablesView($("#properties-content"), GENERIC_VARIABLES_VIEW_SETTINGS);
|
||||
this._propsView.eval = this._onEval;
|
||||
},
|
||||
|
||||
/**
|
||||
* Destruction function called when the tool cleans up.
|
||||
*/
|
||||
destroy: function () {
|
||||
window.off(EVENTS.UI_INSPECTOR_NODE_SET, this._onNodeSet);
|
||||
this._propsView = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Empties out the props view.
|
||||
*/
|
||||
resetUI: function () {
|
||||
this._propsView.empty();
|
||||
this._currentNode = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Internally sets the current audio node and rebuilds appropriate
|
||||
* views.
|
||||
*/
|
||||
_setAudioNode: function (node) {
|
||||
this._currentNode = node;
|
||||
if (this._currentNode) {
|
||||
this._buildPropertiesView();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Reconstructs the `Properties` tab in the inspector
|
||||
* with the `this._currentNode` as it's source.
|
||||
*/
|
||||
_buildPropertiesView: Task.async(function* () {
|
||||
let propsView = this._propsView;
|
||||
let node = this._currentNode;
|
||||
propsView.empty();
|
||||
|
||||
let audioParamsScope = propsView.addScope("AudioParams");
|
||||
let props = yield node.getParams();
|
||||
|
||||
// Disable AudioParams VariableView expansion
|
||||
// when there are no props i.e. AudioDestinationNode
|
||||
this._togglePropertiesView(!!props.length);
|
||||
|
||||
props.forEach(({ param, value, flags }) => {
|
||||
let descriptor = {
|
||||
value: value,
|
||||
writable: !flags || !flags.readonly,
|
||||
};
|
||||
let item = audioParamsScope.addItem(param, descriptor);
|
||||
|
||||
// No items should currently display a dropdown
|
||||
item.twisty = false;
|
||||
});
|
||||
|
||||
audioParamsScope.expanded = true;
|
||||
|
||||
window.emit(EVENTS.UI_PROPERTIES_TAB_RENDERED, node.id);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Toggles the display of the "empty" properties view when
|
||||
* node has no properties to display.
|
||||
*/
|
||||
_togglePropertiesView: function (show) {
|
||||
let propsView = $("#properties-content");
|
||||
let emptyView = $("#properties-empty");
|
||||
(show ? propsView : emptyView).removeAttribute("hidden");
|
||||
(show ? emptyView : propsView).setAttribute("hidden", "true");
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the scope for AudioParams in the
|
||||
* VariablesView.
|
||||
*
|
||||
* @return Scope
|
||||
*/
|
||||
_getAudioPropertiesScope: function () {
|
||||
return this._propsView.getScopeAtIndex(0);
|
||||
},
|
||||
|
||||
/**
|
||||
* Event handlers
|
||||
*/
|
||||
|
||||
/**
|
||||
* Called when the inspector view determines a node is selected.
|
||||
*/
|
||||
_onNodeSet: function (_, id) {
|
||||
this._setAudioNode(gAudioNodes.get(id));
|
||||
},
|
||||
|
||||
/**
|
||||
* Executed when an audio prop is changed in the UI.
|
||||
*/
|
||||
_onEval: Task.async(function* (variable, value) {
|
||||
let ownerScope = variable.ownerView;
|
||||
let node = this._currentNode;
|
||||
let propName = variable.name;
|
||||
let error;
|
||||
|
||||
if (!variable._initialDescriptor.writable) {
|
||||
error = new Error("Variable " + propName + " is not writable.");
|
||||
} else {
|
||||
// Cast value to proper type
|
||||
try {
|
||||
let number = parseFloat(value);
|
||||
if (!isNaN(number)) {
|
||||
value = number;
|
||||
} else {
|
||||
value = JSON.parse(value);
|
||||
}
|
||||
error = yield node.actor.setParam(propName, value);
|
||||
}
|
||||
catch (e) {
|
||||
error = e;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO figure out how to handle and display set prop errors
|
||||
// and enable `test/brorwser_wa_properties-view-edit.js`
|
||||
// Bug 994258
|
||||
if (!error) {
|
||||
ownerScope.get(propName).setGrip(value);
|
||||
window.emit(EVENTS.UI_SET_PARAM, node.id, propName, value);
|
||||
} else {
|
||||
window.emit(EVENTS.UI_SET_PARAM_ERROR, node.id, propName, value);
|
||||
}
|
||||
})
|
||||
};
|
@ -25,6 +25,8 @@
|
||||
<script type="application/javascript" src="webaudioeditor/views/utils.js"/>
|
||||
<script type="application/javascript" src="webaudioeditor/views/context.js"/>
|
||||
<script type="application/javascript" src="webaudioeditor/views/inspector.js"/>
|
||||
<script type="application/javascript" src="webaudioeditor/views/properties.js"/>
|
||||
<script type="application/javascript" src="webaudioeditor/views/automation.js"/>
|
||||
|
||||
<vbox class="theme-body" flex="1">
|
||||
<hbox id="reload-notice"
|
||||
@ -75,7 +77,7 @@
|
||||
</vbox>
|
||||
</box>
|
||||
</hbox>
|
||||
<splitter class="devtools-side-splitter"/>
|
||||
<splitter id="inspector-splitter" class="devtools-side-splitter"/>
|
||||
<vbox id="web-audio-inspector" hidden="true">
|
||||
<hbox class="devtools-toolbar">
|
||||
<label id="web-audio-inspector-title" value="&webAudioEditorUI.inspectorTitle;"></label>
|
||||
@ -90,16 +92,39 @@
|
||||
<tabs>
|
||||
<tab id="properties-tab"
|
||||
label="&webAudioEditorUI.tab.properties;"/>
|
||||
<tab id="automation-tab"
|
||||
label="&webAudioEditorUI.tab.automation;"/>
|
||||
</tabs>
|
||||
<tabpanels flex="1">
|
||||
<!-- Properties Panel -->
|
||||
<tabpanel id="properties-tabpanel"
|
||||
class="tabpanel-content">
|
||||
<vbox id="properties-tabpanel-content" flex="1">
|
||||
<vbox id="properties-content" flex="1" hidden="true">
|
||||
</vbox>
|
||||
<vbox id="properties-tabpanel-content-empty" flex="1" hidden="true">
|
||||
<vbox id="properties-empty" flex="1" hidden="true">
|
||||
<label value="&webAudioEditorUI.propertiesEmpty;"></label>
|
||||
</vbox>
|
||||
</tabpanel>
|
||||
|
||||
<!-- Automation Panel -->
|
||||
<tabpanel id="automation-tabpanel"
|
||||
class="tabpanel-content">
|
||||
<vbox id="automation-content" flex="1" hidden="true">
|
||||
<toolbar id="automation-param-toolbar" class="devtools-toolbar">
|
||||
<hbox id="automation-param-toolbar-buttons" class="devtools-toolbarbutton-group">
|
||||
</hbox>
|
||||
</toolbar>
|
||||
<box id="automation-graph-container" flex="1">
|
||||
<canvas id="automation-graph"></canvas>
|
||||
</box>
|
||||
<vbox id="automation-no-events" flex="1" hidden="true">
|
||||
<label value="&webAudioEditorUI.automationNoEvents;"></label>
|
||||
</vbox>
|
||||
</vbox>
|
||||
<vbox id="automation-empty" flex="1" hidden="true">
|
||||
<label value="&webAudioEditorUI.automationEmpty;"></label>
|
||||
</vbox>
|
||||
</tabpanel>
|
||||
</tabpanels>
|
||||
</tabbox>
|
||||
</deck>
|
||||
|
@ -118,7 +118,7 @@ let UI = {
|
||||
this._telemetry.toolClosed("webide");
|
||||
},
|
||||
|
||||
canWindowClose: function() {
|
||||
canCloseProject: function() {
|
||||
if (this.projecteditor) {
|
||||
return this.projecteditor.confirmUnsaved();
|
||||
}
|
||||
@ -155,6 +155,11 @@ let UI = {
|
||||
this.updateCommands();
|
||||
this.updateConnectionTelemetry();
|
||||
break;
|
||||
case "before-project":
|
||||
if (!this.canCloseProject()) {
|
||||
details.cancel();
|
||||
}
|
||||
break;
|
||||
case "project":
|
||||
this._updatePromise = Task.spawn(function() {
|
||||
UI.updateTitle();
|
||||
@ -971,7 +976,7 @@ let UI = {
|
||||
|
||||
let Cmds = {
|
||||
quit: function() {
|
||||
if (UI.canWindowClose()) {
|
||||
if (UI.canCloseProject()) {
|
||||
window.close();
|
||||
}
|
||||
},
|
||||
|
@ -14,7 +14,7 @@
|
||||
<?xml-stylesheet href="chrome://global/skin/global.css"?>
|
||||
<?xml-stylesheet href="chrome://webide/skin/webide.css"?>
|
||||
|
||||
<window id="webide" onclose="return UI.canWindowClose();"
|
||||
<window id="webide" onclose="return UI.canCloseProject();"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
title="&windowTitle;"
|
||||
|
@ -276,6 +276,13 @@ let AppManager = exports.AppManager = {
|
||||
// A regular comparison still sees a difference when equal in some cases
|
||||
if (JSON.stringify(this._selectedProject) !==
|
||||
JSON.stringify(value)) {
|
||||
|
||||
let cancelled = false;
|
||||
this.update("before-project", { cancel: () => { cancelled = true; } });
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._selectedProject = value;
|
||||
|
||||
// Clear out tab store's selected state, if any
|
||||
@ -303,6 +310,10 @@ let AppManager = exports.AppManager = {
|
||||
removeSelectedProject: function() {
|
||||
let location = this.selectedProject.location;
|
||||
AppManager.selectedProject = null;
|
||||
// If the user cancels the removeProject operation, don't remove the project
|
||||
if (AppManager.selectedProject != null) {
|
||||
return;
|
||||
}
|
||||
return AppProjects.remove(location);
|
||||
},
|
||||
|
||||
|
@ -13,13 +13,13 @@
|
||||
|
||||
# LOCALIZATION NOTE (projecteditor.confirmUnsavedTitle):
|
||||
# This string is displayed as as the title of the confirm prompt that checks
|
||||
# to make sure if the project editor can be closed without saving changes
|
||||
# to make sure if the project can be closed/switched without saving changes
|
||||
projecteditor.confirmUnsavedTitle=Unsaved Changes
|
||||
|
||||
# LOCALIZATION NOTE (projecteditor.confirmUnsavedLabel):
|
||||
# LOCALIZATION NOTE (projecteditor.confirmUnsavedLabel2):
|
||||
# This string is displayed as the message of the confirm prompt that checks
|
||||
# to make sure if the project editor can be closed without saving changes
|
||||
projecteditor.confirmUnsavedLabel=You have unsaved changes that will be lost if you exit. Are you sure you want to continue?
|
||||
# to make sure if the project can be closed/switched without saving changes
|
||||
projecteditor.confirmUnsavedLabel2=You have unsaved changes that will be lost. Are you sure you want to continue?
|
||||
|
||||
# LOCALIZATION NOTE (projecteditor.deleteLabel):
|
||||
# This string is displayed as a context menu item for allowing the selected
|
||||
|
@ -27,6 +27,10 @@
|
||||
- for the properties tab view. -->
|
||||
<!ENTITY webAudioEditorUI.tab.properties "Parameters">
|
||||
|
||||
<!-- LOCALIZATION NOTE (webAudioEditorUI.tab.automation): This is the label shown
|
||||
- for the automation tab view. -->
|
||||
<!ENTITY webAudioEditorUI.tab.automation "Automation">
|
||||
|
||||
<!-- LOCALIZATION NOTE (webAudioEditorUI.inspectorTitle): This is the title for the
|
||||
- AudioNode inspector view. -->
|
||||
<!ENTITY webAudioEditorUI.inspectorTitle "AudioNode Inspector">
|
||||
@ -38,3 +42,12 @@
|
||||
<!-- LOCALIZATION NOTE (webAudioEditorUI.propertiesEmpty): This is the title for the
|
||||
- AudioNode inspector view properties tab empty message. -->
|
||||
<!ENTITY webAudioEditorUI.propertiesEmpty "Node does not have any properties.">
|
||||
|
||||
<!-- LOCALIZATION NOTE (webAudioEditorUI.automationEmpty): This is the title for the
|
||||
- AudioNode inspector view automation tab empty message. -->
|
||||
<!ENTITY webAudioEditorUI.automationEmpty "Node does not have any AudioParams.">
|
||||
|
||||
<!-- LOCALIZATION NOTE (webAudioEditorUI.automationNoEvents): This is the title for the
|
||||
- AudioNode inspector view automation tab message when there are no automation
|
||||
- events. -->
|
||||
<!ENTITY webAudioEditorUI.automationNoEvents "AudioParam does not have any automation events.">
|
||||
|
@ -362,3 +362,33 @@
|
||||
.marker-details-duration {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Recording items */
|
||||
|
||||
.recording-item {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.recording-item-title {
|
||||
font-size: 110%;
|
||||
}
|
||||
|
||||
.recording-item-footer {
|
||||
padding-top: 4px;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
.recording-item-save {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.recording-item-duration,
|
||||
.recording-item-save {
|
||||
color: var(--theme-body-color-alt);
|
||||
}
|
||||
|
||||
#recordings-list .selected label {
|
||||
/* Text inside a selected item should not be custom colored. */
|
||||
color: inherit !important;
|
||||
}
|
||||
|
@ -145,6 +145,20 @@ text {
|
||||
-moz-image-region: rect(0px,32px,16px,16px);
|
||||
}
|
||||
|
||||
/**
|
||||
* Automation Styles
|
||||
*/
|
||||
|
||||
#automation-param-toolbar .automation-param-button[selected] {
|
||||
color: var(--theme-selection-color);
|
||||
background-color: var(--theme-selection-background);
|
||||
}
|
||||
|
||||
#automation-graph {
|
||||
overflow: hidden;
|
||||
-moz-box-flex: 1;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
#inspector-pane-toggle {
|
||||
list-style-image: url(debugger-collapse@2x.png);
|
||||
|
@ -51,11 +51,10 @@ static RedirEntry kRedirMap[] = {
|
||||
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
|
||||
nsIAboutModule::ALLOW_SCRIPT |
|
||||
nsIAboutModule::HIDE_FROM_ABOUTABOUT },
|
||||
{ "compartments", "chrome://global/content/aboutCompartments.xhtml",
|
||||
nsIAboutModule::ALLOW_SCRIPT |
|
||||
nsIAboutModule::HIDE_FROM_ABOUTABOUT },
|
||||
{ "memory", "chrome://global/content/aboutMemory.xhtml",
|
||||
nsIAboutModule::ALLOW_SCRIPT },
|
||||
{ "compartments", "chrome://global/content/aboutCompartments.xhtml",
|
||||
nsIAboutModule::ALLOW_SCRIPT },
|
||||
{ "addons", "chrome://mozapps/content/extensions/extensions.xul",
|
||||
nsIAboutModule::ALLOW_SCRIPT },
|
||||
{ "newaddon", "chrome://mozapps/content/extensions/newaddon.xul",
|
||||
|
@ -168,6 +168,7 @@ const mozilla::Module::ContractIDEntry kDocShellContracts[] = {
|
||||
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "neterror", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
|
||||
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "compartments", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
|
||||
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "memory", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
|
||||
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "compartments", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
|
||||
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "addons", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
|
||||
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "newaddon", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
|
||||
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "support", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
|
||||
|
@ -167,6 +167,30 @@ JS_GetEmptyString(JSRuntime *rt)
|
||||
return rt->emptyString;
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(bool)
|
||||
JS_GetCompartmentStats(JSRuntime *rt, CompartmentStatsVector &stats)
|
||||
{
|
||||
if (!stats.resizeUninitialized(rt->numCompartments))
|
||||
return false;
|
||||
|
||||
size_t pos = 0;
|
||||
for (CompartmentsIter c(rt, WithAtoms); !c.done(); c.next()) {
|
||||
CompartmentTimeStats *stat = &stats[pos];
|
||||
stat->time = c.get()->totalTime;
|
||||
stat->compartment = c.get();
|
||||
stat->addonId = c.get()->addonId;
|
||||
if (rt->compartmentNameCallback) {
|
||||
(*rt->compartmentNameCallback)(rt, stat->compartment,
|
||||
stat->compartmentName,
|
||||
MOZ_ARRAY_LENGTH(stat->compartmentName));
|
||||
} else {
|
||||
strcpy(stat->compartmentName, "<unknown>");
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace js {
|
||||
|
||||
void
|
||||
|
@ -1042,6 +1042,19 @@ JS_GetEmptyStringValue(JSContext *cx);
|
||||
extern JS_PUBLIC_API(JSString *)
|
||||
JS_GetEmptyString(JSRuntime *rt);
|
||||
|
||||
struct CompartmentTimeStats {
|
||||
char compartmentName[1024];
|
||||
JSAddonId *addonId;
|
||||
JSCompartment *compartment;
|
||||
uint64_t time; // microseconds
|
||||
uint64_t cpowTime; // microseconds
|
||||
};
|
||||
|
||||
typedef js::Vector<CompartmentTimeStats, 0, js::SystemAllocPolicy> CompartmentStatsVector;
|
||||
|
||||
extern JS_PUBLIC_API(bool)
|
||||
JS_GetCompartmentStats(JSRuntime *rt, CompartmentStatsVector &stats);
|
||||
|
||||
/*
|
||||
* Format is a string of the following characters (spaces are insignificant),
|
||||
* specifying the tabulated type conversions:
|
||||
|
@ -345,8 +345,16 @@ xpc::TraceXPCGlobal(JSTracer *trc, JSObject *obj)
|
||||
compartmentPrivate->scope->TraceInside(trc);
|
||||
}
|
||||
|
||||
|
||||
namespace xpc {
|
||||
|
||||
uint64_t
|
||||
GetCompartmentCPOWMicroseconds(JSCompartment *compartment)
|
||||
{
|
||||
xpc::CompartmentPrivate *compartmentPrivate = xpc::CompartmentPrivate::Get(compartment);
|
||||
return compartmentPrivate ? PR_IntervalToMicroseconds(compartmentPrivate->CPOWTime) : 0;
|
||||
}
|
||||
|
||||
JSObject*
|
||||
CreateGlobalObject(JSContext *cx, const JSClass *clasp, nsIPrincipal *principal,
|
||||
JS::CompartmentOptions& aOptions)
|
||||
|
@ -139,6 +139,9 @@ XrayAwareCalleeGlobal(JSObject *fun);
|
||||
void
|
||||
TraceXPCGlobal(JSTracer *trc, JSObject *obj);
|
||||
|
||||
uint64_t
|
||||
GetCompartmentCPOWMicroseconds(JSCompartment *compartment);
|
||||
|
||||
} /* namespace xpc */
|
||||
|
||||
namespace JS {
|
||||
|
@ -24,54 +24,55 @@ let TabMirror = function(deviceId, window) {
|
||||
this.RTCIceCandidate = window.mozRTCIceCandidate;
|
||||
|
||||
Services.obs.addObserver((aSubject, aTopic, aData) => this._processMessage(aData), "MediaPlayer:Response", false);
|
||||
|
||||
this._sendMessage({ start: true });
|
||||
this._window = window;
|
||||
this._pc = new window.mozRTCPeerConnection(CONFIG, {});
|
||||
|
||||
if (!this._pc) {
|
||||
throw "Failure creating Webrtc object";
|
||||
}
|
||||
|
||||
this._pc.onicecandidate = this._onIceCandidate.bind(this);
|
||||
|
||||
let windowId = window.BrowserApp.selectedBrowser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
|
||||
let viewport = window.BrowserApp.selectedTab.getViewport();
|
||||
let maxWidth = Math.max(viewport.cssWidth, viewport.width);
|
||||
let maxHeight = Math.max(viewport.cssHeight, viewport.height);
|
||||
let minRatio = Math.sqrt((maxWidth * maxHeight) / (640 * 480));
|
||||
|
||||
let screenWidth = 640;
|
||||
let screenHeight = 480;
|
||||
let videoWidth = 0;
|
||||
let videoHeight = 0;
|
||||
if (screenWidth/screenHeight > maxWidth / maxHeight) {
|
||||
videoWidth = screenWidth;
|
||||
videoHeight = Math.ceil(videoWidth * maxHeight / maxWidth);
|
||||
} else {
|
||||
videoHeight = screenHeight;
|
||||
videoWidth = Math.ceil(videoHeight * maxWidth / maxHeight);
|
||||
}
|
||||
|
||||
let constraints = {
|
||||
video: {
|
||||
mediaSource: "browser",
|
||||
browserWindow: windowId,
|
||||
scrollWithPage: true,
|
||||
advanced: [
|
||||
{ width: { min: videoWidth, max: videoWidth },
|
||||
height: { min: videoHeight, max: videoHeight }
|
||||
},
|
||||
{ aspectRatio: maxWidth / maxHeight }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
window.navigator.mozGetUserMedia(constraints, this._onGumSuccess.bind(this), this._onGumFailure.bind(this));
|
||||
};
|
||||
|
||||
TabMirror.prototype = {
|
||||
_window: null,
|
||||
_screenSize: { width: 1280, height: 720 },
|
||||
_pc: null,
|
||||
_start: function() {
|
||||
this._pc.onicecandidate = this._onIceCandidate.bind(this);
|
||||
|
||||
let windowId = this._window.BrowserApp.selectedBrowser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
|
||||
let viewport = this._window.BrowserApp.selectedTab.getViewport();
|
||||
let maxWidth = Math.max(viewport.cssWidth, viewport.width);
|
||||
let maxHeight = Math.max(viewport.cssHeight, viewport.height);
|
||||
|
||||
let videoWidth = 0;
|
||||
let videoHeight = 0;
|
||||
if (this._screenSize.width/this._screenSize.height < maxWidth / maxHeight) {
|
||||
videoWidth = this._screenSize.width;
|
||||
videoHeight = Math.ceil(videoWidth * maxHeight / maxWidth);
|
||||
} else {
|
||||
videoHeight = this._screenSize.height;
|
||||
videoWidth = Math.ceil(videoHeight * maxWidth / maxHeight);
|
||||
}
|
||||
|
||||
let constraints = {
|
||||
video: {
|
||||
mediaSource: "browser",
|
||||
browserWindow: windowId,
|
||||
scrollWithPage: true,
|
||||
advanced: [
|
||||
{ width: { min: videoWidth, max: videoWidth },
|
||||
height: { min: videoHeight, max: videoHeight }
|
||||
},
|
||||
{ aspectRatio: maxWidth / maxHeight }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
this._window.navigator.mozGetUserMedia(constraints, this._onGumSuccess.bind(this), this._onGumFailure.bind(this));
|
||||
},
|
||||
|
||||
_processMessage: function(data) {
|
||||
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
@ -82,21 +83,27 @@ TabMirror.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.sdp) {
|
||||
if (msg.type === "answer") {
|
||||
this._processAnswer(msg);
|
||||
} else {
|
||||
log("Unandled sdp message type: " + msg.type);
|
||||
if (msg.sdp && msg.type === "answer") {
|
||||
this._processAnswer(msg);
|
||||
} else if (msg.type == "size") {
|
||||
if (msg.height) {
|
||||
this._screenSize.height = msg.height;
|
||||
}
|
||||
} else {
|
||||
if (msg.width) {
|
||||
this._screenSize.width = msg.width;
|
||||
}
|
||||
this._start();
|
||||
} else if (msg.candidate) {
|
||||
this._processIceCandidate(msg);
|
||||
} else {
|
||||
log("dropping unrecognized message: " + JSON.stringify(msg));
|
||||
}
|
||||
},
|
||||
|
||||
// Signaling methods
|
||||
_processAnswer: function(msg) {
|
||||
this._pc.setRemoteDescription(new this.RTCSessionDescription(msg),
|
||||
this._setRemoteAnswerSuccess.bind(this), failure);
|
||||
this._setRemoteAnswerSuccess.bind(this), failure);
|
||||
},
|
||||
|
||||
_processIceCandidate: function(msg) {
|
||||
|
@ -0,0 +1,75 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-*/
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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";
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
|
||||
|
||||
function go() {
|
||||
let compartmentInfo = Cc["@mozilla.org/compartment-info;1"]
|
||||
.getService(Ci.nsICompartmentInfo);
|
||||
let compartments = compartmentInfo.getCompartments();
|
||||
let count = compartments.length;
|
||||
let addons = {};
|
||||
for (let i = 0; i < count; i++) {
|
||||
let compartment = compartments.queryElementAt(i, Ci.nsICompartment);
|
||||
if (addons[compartment.addonId]) {
|
||||
addons[compartment.addonId].time += compartment.time;
|
||||
addons[compartment.addonId].CPOWTime += compartment.CPOWTime;
|
||||
addons[compartment.addonId].compartments.push(compartment);
|
||||
} else {
|
||||
addons[compartment.addonId] = {
|
||||
time: compartment.time,
|
||||
CPOWTime: compartment.CPOWTime,
|
||||
compartments: [compartment]
|
||||
};
|
||||
}
|
||||
}
|
||||
let dataDiv = document.getElementById("data");
|
||||
for (let addon in addons) {
|
||||
let el = document.createElement("tr");
|
||||
let name = document.createElement("td");
|
||||
let time = document.createElement("td");
|
||||
let cpow = document.createElement("td");
|
||||
name.className = "addon";
|
||||
time.className = "time";
|
||||
cpow.className = "cpow";
|
||||
name.textContent = addon;
|
||||
AddonManager.getAddonByID(addon, function(a) {
|
||||
if (a) {
|
||||
name.textContent = a.name;
|
||||
}
|
||||
});
|
||||
time.textContent = addons[addon].time +"μs";
|
||||
cpow.textContent = addons[addon].CPOWTime +"μs";
|
||||
el.appendChild(time);
|
||||
el.appendChild(cpow);
|
||||
el.appendChild(name);
|
||||
let div = document.createElement("tr");
|
||||
for (let comp of addons[addon].compartments) {
|
||||
let c = document.createElement("tr");
|
||||
let name = document.createElement("td");
|
||||
let time = document.createElement("td");
|
||||
let cpow = document.createElement("td");
|
||||
name.className = "addon";
|
||||
time.className = "time";
|
||||
cpow.className = "cpow";
|
||||
name.textContent = comp.label;
|
||||
time.textContent = comp.time +"μs";
|
||||
cpow.textContent = comp.CPOWTime +"μs";
|
||||
c.appendChild(time);
|
||||
c.appendChild(cpow);
|
||||
c.appendChild(name);
|
||||
div.appendChild(c);
|
||||
div.className = "details";
|
||||
}
|
||||
el.addEventListener("click", function() { div.style.display = (div.style.display != "block" ? "block" : "none"); });
|
||||
el.appendChild(div);
|
||||
dataDiv.appendChild(el);
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
<?xml version="1.0"?>
|
||||
|
||||
<!-- 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/. -->
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>about:compartments</title>
|
||||
<script type="text/javascript;version=1.8" src="chrome://global/content/aboutCompartments.js"></script>
|
||||
<style>
|
||||
td.addon {
|
||||
display: inline-block;
|
||||
width: 400px;
|
||||
}
|
||||
td.time {
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
}
|
||||
td.cpow {
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
}
|
||||
.header {
|
||||
font-weight: bold;
|
||||
}
|
||||
tr.details {
|
||||
font-weight: lighter;
|
||||
color: gray;
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body onload="go()">
|
||||
<table id="data">
|
||||
<tr class="header">
|
||||
<td class="time">time</td>
|
||||
<td class="cpow">time in CPOWs</td>
|
||||
<td class="addon">name</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
7
toolkit/components/aboutcompartments/jar.mn
Normal file
7
toolkit/components/aboutcompartments/jar.mn
Normal file
@ -0,0 +1,7 @@
|
||||
# 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/.
|
||||
|
||||
toolkit.jar:
|
||||
+ content/global/aboutCompartments.xhtml (content/aboutCompartments.xhtml)
|
||||
+ content/global/aboutCompartments.js (content/aboutCompartments.js)
|
23
toolkit/components/aboutcompartments/moz.build
Normal file
23
toolkit/components/aboutcompartments/moz.build
Normal file
@ -0,0 +1,23 @@
|
||||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
JAR_MANIFESTS += ['jar.mn']
|
||||
|
||||
XPIDL_MODULE = 'compartments'
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
'nsICompartmentInfo.idl',
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'nsCompartmentInfo.cpp'
|
||||
]
|
||||
|
||||
EXPORTS += [
|
||||
'nsCompartmentInfo.h'
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
94
toolkit/components/aboutcompartments/nsCompartmentInfo.cpp
Normal file
94
toolkit/components/aboutcompartments/nsCompartmentInfo.cpp
Normal file
@ -0,0 +1,94 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#include "nsCompartmentInfo.h"
|
||||
#include "nsMemory.h"
|
||||
#include "nsLiteralString.h"
|
||||
#include "nsCRTGlue.h"
|
||||
#include "nsIJSRuntimeService.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "nsIMutableArray.h"
|
||||
#include "nsJSUtils.h"
|
||||
#include "xpcpublic.h"
|
||||
|
||||
class nsCompartment : public nsICompartment {
|
||||
public:
|
||||
nsCompartment(nsAString& aCompartmentName, nsAString& aAddonId,
|
||||
uint64_t aTime, uint64_t aCPOWTime)
|
||||
: mCompartmentName(aCompartmentName), mAddonId(aAddonId), mTime(aTime), mCPOWTime(aCPOWTime) {}
|
||||
|
||||
NS_DECL_ISUPPORTS
|
||||
|
||||
/* readonly attribute wstring compartmentName; */
|
||||
NS_IMETHOD GetCompartmentName(nsAString& aCompartmentName){
|
||||
aCompartmentName.Assign(mCompartmentName);
|
||||
return NS_OK;
|
||||
};
|
||||
|
||||
/* readonly attribute unsigned long time; */
|
||||
NS_IMETHOD GetTime(uint64_t* aTime) {
|
||||
*aTime = mTime;
|
||||
return NS_OK;
|
||||
}
|
||||
/* readonly attribute wstring addon id; */
|
||||
NS_IMETHOD GetAddonId(nsAString& aAddonId){
|
||||
aAddonId.Assign(mAddonId);
|
||||
return NS_OK;
|
||||
};
|
||||
|
||||
/* readonly attribute unsigned long CPOW time; */
|
||||
NS_IMETHOD GetCPOWTime(uint64_t* aCPOWTime) {
|
||||
*aCPOWTime = mCPOWTime;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
nsString mCompartmentName;
|
||||
nsString mAddonId;
|
||||
uint64_t mTime;
|
||||
uint64_t mCPOWTime;
|
||||
virtual ~nsCompartment() {}
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsCompartment, nsICompartment)
|
||||
NS_IMPL_ISUPPORTS(nsCompartmentInfo, nsICompartmentInfo)
|
||||
|
||||
nsCompartmentInfo::nsCompartmentInfo()
|
||||
{
|
||||
}
|
||||
|
||||
nsCompartmentInfo::~nsCompartmentInfo()
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsCompartmentInfo::GetCompartments(nsIArray** aCompartments)
|
||||
{
|
||||
JSRuntime* rt;
|
||||
nsCOMPtr<nsIJSRuntimeService> svc(do_GetService("@mozilla.org/js/xpc/RuntimeService;1"));
|
||||
NS_ENSURE_TRUE(svc, NS_ERROR_FAILURE);
|
||||
svc->GetRuntime(&rt);
|
||||
nsCOMPtr<nsIMutableArray> compartments = do_CreateInstance(NS_ARRAY_CONTRACTID);
|
||||
CompartmentStatsVector stats;
|
||||
if (!JS_GetCompartmentStats(rt, stats))
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
|
||||
size_t num = stats.length();
|
||||
for (size_t pos = 0; pos < num; pos++) {
|
||||
nsString addonId;
|
||||
if (stats[pos].addonId) {
|
||||
AssignJSFlatString(addonId, (JSFlatString*)stats[pos].addonId);
|
||||
} else {
|
||||
addonId.AssignLiteral("<non-addon>");
|
||||
}
|
||||
|
||||
uint32_t cpowTime = xpc::GetCompartmentCPOWMicroseconds(stats[pos].compartment);
|
||||
nsCString compartmentName(stats[pos].compartmentName);
|
||||
NS_ConvertUTF8toUTF16 name(compartmentName);
|
||||
compartments->AppendElement(new nsCompartment(name, addonId, stats[pos].time, cpowTime), false);
|
||||
}
|
||||
compartments.forget(aCompartments);
|
||||
return NS_OK;
|
||||
}
|
25
toolkit/components/aboutcompartments/nsCompartmentInfo.h
Normal file
25
toolkit/components/aboutcompartments/nsCompartmentInfo.h
Normal file
@ -0,0 +1,25 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef nsCompartmentInfo_h
|
||||
#define nsCompartmentInfo_h
|
||||
|
||||
#include "nsICompartmentInfo.h"
|
||||
|
||||
class nsCompartmentInfo : public nsICompartmentInfo
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSICOMPARTMENTINFO
|
||||
|
||||
nsCompartmentInfo();
|
||||
|
||||
private:
|
||||
virtual ~nsCompartmentInfo();
|
||||
|
||||
protected:
|
||||
};
|
||||
|
||||
#endif
|
31
toolkit/components/aboutcompartments/nsICompartmentInfo.idl
Normal file
31
toolkit/components/aboutcompartments/nsICompartmentInfo.idl
Normal file
@ -0,0 +1,31 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-*/
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
#include "nsIArray.idl"
|
||||
|
||||
[scriptable, uuid(13dd4c09-ff11-4943-8dc2-d96eb69c963b)]
|
||||
interface nsICompartment : nsISupports {
|
||||
/* name of compartment */
|
||||
readonly attribute AString compartmentName;
|
||||
/* time spent executing code in this compartment in microseconds */
|
||||
readonly attribute unsigned long long time;
|
||||
/* the id of the addon associated with this compartment, or null */
|
||||
readonly attribute AString addonId;
|
||||
/* time spent processing CPOWs in microseconds */
|
||||
readonly attribute unsigned long long CPOWTime;
|
||||
};
|
||||
|
||||
[scriptable, builtinclass, uuid(5795113a-39a1-4087-ba09-98b7d07d025a)]
|
||||
interface nsICompartmentInfo : nsISupports {
|
||||
nsIArray getCompartments();
|
||||
};
|
||||
|
||||
%{C++
|
||||
#define NS_COMPARTMENT_INFO_CID \
|
||||
{ 0x2d3c2f2d, 0x698d, 0x471d, \
|
||||
{ 0xba, 0x3e, 0x14, 0x44, 0xdd, 0x52, 0x1e, 0x29 } }
|
||||
%}
|
@ -3,7 +3,6 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
toolkit.jar:
|
||||
+ content/global/aboutCompartments.xhtml (content/aboutCompartments.xhtml)
|
||||
+ content/global/aboutMemory.js (content/aboutMemory.js)
|
||||
+ content/global/aboutMemory.xhtml (content/aboutMemory.xhtml)
|
||||
+ content/global/aboutMemory.css (content/aboutMemory.css)
|
||||
|
@ -496,11 +496,21 @@ let RemoteAddonsChild = {
|
||||
_ready: false,
|
||||
|
||||
makeReady: function() {
|
||||
Prefetcher.init();
|
||||
NotificationTracker.init();
|
||||
ContentPolicyChild.init();
|
||||
AboutProtocolChild.init();
|
||||
ObserverChild.init();
|
||||
let shims = [
|
||||
Prefetcher,
|
||||
NotificationTracker,
|
||||
ContentPolicyChild,
|
||||
AboutProtocolChild,
|
||||
ObserverChild,
|
||||
];
|
||||
|
||||
for (let shim of shims) {
|
||||
try {
|
||||
shim.init();
|
||||
} catch(e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
init: function(global) {
|
||||
@ -520,7 +530,11 @@ let RemoteAddonsChild = {
|
||||
|
||||
uninit: function(perTabShims) {
|
||||
for (let shim of perTabShims) {
|
||||
shim.uninit();
|
||||
try {
|
||||
shim.uninit();
|
||||
} catch(e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@ -10,6 +10,7 @@ if CONFIG['MOZ_ENABLE_XREMOTE']:
|
||||
|
||||
DIRS += [
|
||||
'aboutcache',
|
||||
'aboutcompartments',
|
||||
'aboutmemory',
|
||||
'addoncompat',
|
||||
'alerts',
|
||||
|
@ -140,7 +140,7 @@ exports.items = [
|
||||
|
||||
let dirName = prefBranch.getComplexValue(PREF_DIR,
|
||||
Ci.nsISupportsString).data.trim();
|
||||
return gcli.lookupFormat("cmdStatus2", [ dirName ]);
|
||||
return gcli.lookupFormat("cmdStatus3", [ dirName ]);
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -45,18 +45,32 @@ const NODE_ROUTING_METHODS = [
|
||||
const NODE_PROPERTIES = {
|
||||
"OscillatorNode": {
|
||||
"type": {},
|
||||
"frequency": {},
|
||||
"detune": {}
|
||||
"frequency": {
|
||||
"param": true
|
||||
},
|
||||
"detune": {
|
||||
"param": true
|
||||
}
|
||||
},
|
||||
"GainNode": {
|
||||
"gain": {}
|
||||
"gain": {
|
||||
"param": true
|
||||
}
|
||||
},
|
||||
"DelayNode": {
|
||||
"delayTime": {}
|
||||
"delayTime": {
|
||||
"param": true
|
||||
}
|
||||
},
|
||||
// TODO deal with figuring out adding `detune` AudioParam
|
||||
// for AudioBufferSourceNode, which is in the spec
|
||||
// but not yet added in implementation
|
||||
// bug 1116852
|
||||
"AudioBufferSourceNode": {
|
||||
"buffer": { "Buffer": true },
|
||||
"playbackRate": {},
|
||||
"playbackRate": {
|
||||
"param": true,
|
||||
},
|
||||
"loop": {},
|
||||
"loopStart": {},
|
||||
"loopEnd": {}
|
||||
@ -79,19 +93,37 @@ const NODE_PROPERTIES = {
|
||||
"normalize": {},
|
||||
},
|
||||
"DynamicsCompressorNode": {
|
||||
"threshold": {},
|
||||
"knee": {},
|
||||
"ratio": {},
|
||||
"threshold": {
|
||||
"param": true
|
||||
},
|
||||
"knee": {
|
||||
"param": true
|
||||
},
|
||||
"ratio": {
|
||||
"param": true
|
||||
},
|
||||
"reduction": {},
|
||||
"attack": {},
|
||||
"release": {}
|
||||
"attack": {
|
||||
"param": true
|
||||
},
|
||||
"release": {
|
||||
"param": true
|
||||
}
|
||||
},
|
||||
"BiquadFilterNode": {
|
||||
"type": {},
|
||||
"frequency": {},
|
||||
"Q": {},
|
||||
"detune": {},
|
||||
"gain": {}
|
||||
"frequency": {
|
||||
"param": true
|
||||
},
|
||||
"Q": {
|
||||
"param": true
|
||||
},
|
||||
"detune": {
|
||||
"param": true
|
||||
},
|
||||
"gain": {
|
||||
"param": true
|
||||
}
|
||||
},
|
||||
"WaveShaperNode": {
|
||||
"curve": { "Float32Array": true },
|
||||
@ -401,14 +433,14 @@ let AudioNodeActor = exports.AudioNodeActor = protocol.ActorClass({
|
||||
|
||||
getAutomationData: method(function (paramName) {
|
||||
let timeline = this.automation[paramName];
|
||||
let events = timeline.events;
|
||||
let values = [];
|
||||
let i = 0;
|
||||
|
||||
if (!timeline) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let events = timeline.events;
|
||||
let values = [];
|
||||
let i = 0;
|
||||
|
||||
if (!timeline.events.length) {
|
||||
return { events, values };
|
||||
}
|
||||
@ -421,9 +453,9 @@ let AudioNodeActor = exports.AudioNodeActor = protocol.ActorClass({
|
||||
let scale = timeDelta / AUTOMATION_GRANULARITY;
|
||||
|
||||
for (; i < AUTOMATION_GRANULARITY; i++) {
|
||||
let t = firstEvent.time + (i * scale);
|
||||
let value = timeline.getValueAtTime(t);
|
||||
values.push({ t, value });
|
||||
let delta = firstEvent.time + (i * scale);
|
||||
let value = timeline.getValueAtTime(delta);
|
||||
values.push({ delta, value });
|
||||
}
|
||||
|
||||
// If the last event is setTargetAtTime, the automation
|
||||
@ -432,9 +464,9 @@ let AudioNodeActor = exports.AudioNodeActor = protocol.ActorClass({
|
||||
// until we're "close enough" to the target.
|
||||
if (lastEvent.type === "setTargetAtTime") {
|
||||
for (; i < AUTOMATION_GRANULARITY_MAX; i++) {
|
||||
let t = firstEvent.time + (++i * scale);
|
||||
let value = timeline.getValueAtTime(t);
|
||||
values.push({ t, value });
|
||||
let delta = firstEvent.time + (++i * scale);
|
||||
let value = timeline.getValueAtTime(delta);
|
||||
values.push({ delta, value });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
let { Cu } = require("chrome");
|
||||
let DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
|
||||
let Services = require("Services");
|
||||
let promise = require("devtools/toolkit/deprecated-sync-thenables");
|
||||
let {Class} = require("sdk/core/heritage");
|
||||
@ -1148,7 +1150,7 @@ let Front = Class({
|
||||
this.actor().then(actorID => {
|
||||
packet.to = actorID;
|
||||
this.conn._transport.send(packet);
|
||||
});
|
||||
}).then(null, e => DevToolsUtils.reportException("Front.prototype.send", e));
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -18,12 +18,18 @@ function test() {
|
||||
let principal = document.getElementById("content").contentDocument.defaultView.document.nodePrincipal;
|
||||
is(DOMApplicationRegistry.getAppLocalIdByManifestURL(app.manifestURL), principal.appId, "Principal app ID correct");
|
||||
|
||||
let alwaysAllowed = ["indexedDB"]
|
||||
|
||||
// Check if all the permissions of the app are unknown.
|
||||
for (let permName of AllPossiblePermissions) {
|
||||
// Get the value for the permission.
|
||||
let permValue = Services.perms.testExactPermissionFromPrincipal(principal, permName);
|
||||
|
||||
is(permValue, Ci.nsIPermissionManager.UNKNOWN_ACTION, "Permission " + permName + " unknown.");
|
||||
if (alwaysAllowed.includes(permName)) {
|
||||
is(permValue, Ci.nsIPermissionManager.ALLOW_ACTION, "Permission " + permName + " allowed.");
|
||||
} else {
|
||||
is(permValue, Ci.nsIPermissionManager.UNKNOWN_ACTION, "Permission " + permName + " unknown.");
|
||||
}
|
||||
}
|
||||
|
||||
finish();
|
||||
|
@ -50,6 +50,8 @@
|
||||
#include "nsThreadManager.h"
|
||||
#include "nsThreadPool.h"
|
||||
|
||||
#include "nsCompartmentInfo.h"
|
||||
|
||||
#include "xptinfo.h"
|
||||
#include "nsIInterfaceInfoManager.h"
|
||||
#include "xptiprivate.h"
|
||||
@ -234,6 +236,8 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsMemoryInfoDumper)
|
||||
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsStatusReporterManager, Init)
|
||||
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(nsCompartmentInfo)
|
||||
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(nsIOUtil)
|
||||
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(nsSecurityConsoleMessage)
|
||||
|
@ -82,3 +82,4 @@
|
||||
COMPONENT(CYCLE_COLLECTOR_LOGGER, nsCycleCollectorLoggerConstructor)
|
||||
COMPONENT(MESSAGE_LOOP, nsMessageLoopConstructor)
|
||||
COMPONENT(STATUS_REPORTER_MANAGER, nsStatusReporterManagerConstructor)
|
||||
COMPONENT(COMPARTMENT_INFO, nsCompartmentInfoConstructor)
|
@ -90,6 +90,8 @@
|
||||
*/
|
||||
#define NS_MESSAGE_LOOP_CONTRACTID "@mozilla.org/message-loop;1"
|
||||
|
||||
#define NS_COMPARTMENT_INFO_CONTRACTID "@mozilla.org/compartment-info;1"
|
||||
|
||||
/**
|
||||
* The following are the CIDs and Contract IDs of the nsISupports wrappers for
|
||||
* primative types.
|
||||
|
Loading…
Reference in New Issue
Block a user