Bug 1022917 - Do not store any strong references in the web audio editor. r=vp

---
 toolkit/devtools/server/actors/webaudio.js | 114 +++++++++++++++++++----------
 1 file changed, 77 insertions(+), 37 deletions(-)
This commit is contained in:
Jordan Santell 2014-06-11 10:06:54 -07:00
parent 89ab9e4eab
commit 2eae9dd675

View File

@ -107,9 +107,6 @@ const NODE_PROPERTIES = {
"ChannelMergerNode": {}
};
/**
* Track an array of audio nodes
/**
* An Audio Node actor allowing communication to a specific audio node in the
* Audio Context graph.
@ -127,9 +124,15 @@ let AudioNodeActor = exports.AudioNodeActor = protocol.ActorClass({
*/
initialize: function (conn, node) {
protocol.Actor.prototype.initialize.call(this, conn);
this.node = unwrap(node);
// Store ChromeOnly property `id` to identify AudioNode,
// rather than storing a strong reference, and store a weak
// ref to underlying node for controlling.
this.nativeID = node.id;
this.node = Cu.getWeakReference(node);
try {
this.type = getConstructorName(this.node);
this.type = getConstructorName(node);
} catch (e) {
this.type = "";
}
@ -165,11 +168,17 @@ let AudioNodeActor = exports.AudioNodeActor = protocol.ActorClass({
* Value to change AudioParam to.
*/
setParam: method(function (param, value) {
let node = this.node.get();
if (node === null) {
return CollectedAudioNodeError();
}
try {
if (isAudioParam(this.node, param))
this.node[param].value = value;
if (isAudioParam(node, param))
node[param].value = value;
else
this.node[param] = value;
node[param] = value;
return undefined;
} catch (e) {
return constructError(e);
@ -189,9 +198,15 @@ let AudioNodeActor = exports.AudioNodeActor = protocol.ActorClass({
* Name of the AudioParam to fetch.
*/
getParam: method(function (param) {
let node = this.node.get();
if (node === null) {
return CollectedAudioNodeError();
}
// Check to see if it's an AudioParam -- if so,
// return the `value` property of the parameter.
let value = isAudioParam(this.node, param) ? this.node[param].value : this.node[param];
let value = isAudioParam(node, param) ? node[param].value : node[param];
// Return the grip form of the value; at this time,
// there shouldn't be any non-primitives at the moment, other than
@ -262,7 +277,13 @@ let WebAudioActor = exports.WebAudioActor = protocol.ActorClass({
initialize: function(conn, tabActor) {
protocol.Actor.prototype.initialize.call(this, conn);
this.tabActor = tabActor;
this._onContentFunctionCall = this._onContentFunctionCall.bind(this);
// Store ChromeOnly ID (`nativeID` property on AudioNodeActor) mapped
// to the associated actorID, so we don't have to expose `nativeID`
// to the client in any way.
this._nativeToActorID = new Map();
},
destroy: function(conn) {
@ -282,13 +303,15 @@ let WebAudioActor = exports.WebAudioActor = protocol.ActorClass({
// the first time, to ultimately fire `start-context` event
this._firstNodeCreated = false;
// Clear out stored nativeIDs on reload as we do not want to track
// AudioNodes that are no longer on this document.
this._nativeToActorID.clear();
if (this._initialized) {
return;
}
this._initialized = true;
// Weak map mapping audio nodes to their corresponding actors
this._nodeActors = new Map();
this._initialized = true;
this._callWatcher = new CallWatcherActor(this.conn, this.tabActor);
this._callWatcher.onCall = this._onContentFunctionCall;
@ -321,9 +344,9 @@ let WebAudioActor = exports.WebAudioActor = protocol.ActorClass({
_handleRoutingCall: function(functionCall) {
let { caller, args, window, name } = functionCall.details;
let source = unwrap(caller);
let dest = unwrap(args[0]);
let isAudioParam = dest instanceof unwrap(window.AudioParam);
let source = caller;
let dest = args[0];
let isAudioParam = dest instanceof window.AudioParam;
// audionode.connect(param)
if (name === "connect" && isAudioParam) {
@ -349,7 +372,7 @@ let WebAudioActor = exports.WebAudioActor = protocol.ActorClass({
// Fire the start-up event if this is the first node created
// and trigger a `create-node` event for the context destination
this._onStartContext();
this._onCreateNode(unwrap(caller.destination));
this._onCreateNode(caller.destination);
this._firstNodeCreated = true;
}
this._onCreateNode(result);
@ -364,9 +387,10 @@ let WebAudioActor = exports.WebAudioActor = protocol.ActorClass({
if (!this._initialized) {
return;
}
this.tabActor = null;
this._initialized = false;
this._nativeToActorID = null;
this._callWatcher.eraseRecording();
this._callWatcher.finalize();
this._callWatcher = null;
}, {
@ -412,9 +436,12 @@ let WebAudioActor = exports.WebAudioActor = protocol.ActorClass({
* an `actorID`.
*/
_constructAudioNode: function (node) {
// Ensure AudioNode is wrapped.
node = new XPCNativeWrapper(node);
let actor = new AudioNodeActor(this.conn, node);
this.manage(actor);
this._nodeActors.set(node, actor);
this._nativeToActorID.set(node.id, actor.actorID);
return actor;
},
@ -424,11 +451,13 @@ let WebAudioActor = exports.WebAudioActor = protocol.ActorClass({
* connecting to an AudioDestinationNode, since it's implicitly
* created), so make a new actor and store that.
*/
_actorFor: function (node) {
let actor = this._nodeActors.get(node);
if (!actor) {
actor = this._constructAudioNode(node);
}
_getActorByNativeID: function (nativeID) {
// Ensure we have a Number, rather than a string
// return via notification.
nativeID = ~~nativeID;
let actorID = this._nativeToActorID.get(nativeID);
let actor = actorID != null ? this.conn.getActor(actorID) : null;
return actor;
},
@ -436,16 +465,16 @@ let WebAudioActor = exports.WebAudioActor = protocol.ActorClass({
* Called on first audio node creation, signifying audio context usage
*/
_onStartContext: function () {
events.emit(this, "start-context");
emit(this, "start-context");
},
/**
* Called when one audio node is connected to another.
*/
_onConnectNode: function (source, dest) {
let sourceActor = this._actorFor(source);
let destActor = this._actorFor(dest);
events.emit(this, "connect-node", {
let sourceActor = this._getActorByNativeID(source.id);
let destActor = this._getActorByNativeID(dest.id);
emit(this, "connect-node", {
source: sourceActor,
dest: destActor
});
@ -463,16 +492,16 @@ let WebAudioActor = exports.WebAudioActor = protocol.ActorClass({
* Called when an audio node is disconnected.
*/
_onDisconnectNode: function (node) {
let actor = this._actorFor(node);
events.emit(this, "disconnect-node", actor);
let actor = this._getActorByNativeID(node.id);
emit(this, "disconnect-node", actor);
},
/**
* Called when a parameter changes on an audio node
*/
_onParamChange: function (node, param, value) {
let actor = this._actorFor(node);
events.emit(this, "param-change", {
let actor = this._getActorByNativeID(node.id);
emit(this, "param-change", {
source: actor,
param: param,
value: value
@ -484,7 +513,7 @@ let WebAudioActor = exports.WebAudioActor = protocol.ActorClass({
*/
_onCreateNode: function (node) {
let actor = this._constructAudioNode(node);
events.emit(this, "create-node", actor);
emit(this, "create-node", actor);
}
});
@ -529,14 +558,28 @@ function constructError (err) {
};
}
/**
* Creates and returns a JSON-able response used to indicate
* attempt to access an AudioNode that has been GC'd.
*
* @return Object
*/
function CollectedAudioNodeError () {
return {
message: "AudioNode has been garbage collected and can no longer be reached.",
type: "UnreachableAudioNode"
};
}
/**
* Takes an object and converts it's `toString()` form, like
* "[object OscillatorNode]" or "[object Float32Array]"
* "[object OscillatorNode]" or "[object Float32Array]",
* or XrayWrapper objects like "[object XrayWrapper [object Array]]"
* to a string of just the constructor name, like "OscillatorNode",
* or "Float32Array".
*/
function getConstructorName (obj) {
return obj.toString().match(/\[object (.*)\]$/)[1];
return obj.toString().match(/\[object ([^\[\]]*)\]\]?$/)[1];
}
/**
@ -554,6 +597,3 @@ function createObjectGrip (value) {
class: getConstructorName(value)
};
}
function unwrap (obj) {
return XPCNativeWrapper.unwrap(obj);
}