зеркало из https://github.com/mozilla/gecko-dev.git
Bug 980506 - Emit destruction events on AudioNodes in the WebAudioActor. r=vp
This commit is contained in:
Родитель
17c242ecef
Коммит
b5bd9291e9
|
@ -7,6 +7,7 @@ support-files =
|
|||
doc_simple-node-creation.html
|
||||
doc_buffer-and-array.html
|
||||
doc_media-node-creation.html
|
||||
doc_destroy-nodes.html
|
||||
440hz_sine.ogg
|
||||
head.js
|
||||
|
||||
|
@ -17,6 +18,7 @@ support-files =
|
|||
[browser_audionode-actor-get-param-flags.js]
|
||||
[browser_audionode-actor-is-source.js]
|
||||
[browser_webaudio-actor-simple.js]
|
||||
[browser_webaudio-actor-destroy-node.js]
|
||||
|
||||
[browser_wa_first-run.js]
|
||||
[browser_wa_reset-01.js]
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test `destroy-node` event on WebAudioActor.
|
||||
*/
|
||||
|
||||
function spawnTest () {
|
||||
let [target, debuggee, front] = yield initBackend(DESTROY_NODES_URL);
|
||||
|
||||
let waitUntilDestroyed = getN(front, "destroy-node", 10);
|
||||
let [_, _, created] = yield Promise.all([
|
||||
front.setup({ reload: true }),
|
||||
once(front, "start-context"),
|
||||
// Should create 1 destination node and 10 disposable buffer nodes
|
||||
getN(front, "create-node", 13)
|
||||
]);
|
||||
|
||||
// Force CC so we can ensure it's run to clear out dead AudioNodes
|
||||
forceCC();
|
||||
|
||||
let destroyed = yield waitUntilDestroyed;
|
||||
|
||||
let destroyedTypes = yield Promise.all(destroyed.map(actor => actor.getType()));
|
||||
destroyedTypes.forEach((type, i) => {
|
||||
ok(type, "AudioBufferSourceNode", "Only buffer nodes are destroyed");
|
||||
ok(actorIsInList(created, destroyed[i]),
|
||||
"`destroy-node` called only on AudioNodes in current document.");
|
||||
});
|
||||
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
}
|
||||
|
||||
function actorIsInList (list, actor) {
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
if (list[i].actorID === actor.actorID)
|
||||
return list[i];
|
||||
}
|
||||
return null;
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<!doctype html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Web Audio Editor test page</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script type="text/javascript;version=1.8">
|
||||
"use strict";
|
||||
(function () {
|
||||
let ctx = new AudioContext();
|
||||
let osc = ctx.createOscillator();
|
||||
let gain = ctx.createGain();
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
ctx.createBufferSource();
|
||||
}
|
||||
|
||||
osc.connect(gain);
|
||||
gain.connect(ctx.destination);
|
||||
gain.gain.value = 0;
|
||||
osc.start();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -26,6 +26,7 @@ const COMPLEX_CONTEXT_URL = EXAMPLE_URL + "doc_complex-context.html";
|
|||
const SIMPLE_NODES_URL = EXAMPLE_URL + "doc_simple-node-creation.html";
|
||||
const MEDIA_NODES_URL = EXAMPLE_URL + "doc_media-node-creation.html";
|
||||
const BUFFER_AND_ARRAY_URL = EXAMPLE_URL + "doc_buffer-and-array.html";
|
||||
const DESTROY_NODES_URL = EXAMPLE_URL + "doc_destroy-nodes.html";
|
||||
|
||||
// All tests are asynchronous.
|
||||
waitForExplicitFinish();
|
||||
|
@ -372,6 +373,15 @@ function countGraphObjects (win) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces cycle collection and GC, used in AudioNode destruction tests.
|
||||
*/
|
||||
function forceCC () {
|
||||
SpecialPowers.DOMWindowUtils.cycleCollect();
|
||||
SpecialPowers.DOMWindowUtils.garbageCollect();
|
||||
SpecialPowers.DOMWindowUtils.garbageCollect();
|
||||
}
|
||||
|
||||
/**
|
||||
* List of audio node properties to test against expectations of the AudioNode actor
|
||||
*/
|
||||
|
|
|
@ -68,8 +68,11 @@ let FunctionCallActor = protocol.ActorClass({
|
|||
* The called function's arguments.
|
||||
* @param any result
|
||||
* The value returned by the function call.
|
||||
* @param boolean holdWeak
|
||||
* Determines whether or not FunctionCallActor stores a weak reference
|
||||
* to the underlying objects.
|
||||
*/
|
||||
initialize: function(conn, [window, global, caller, type, name, stack, args, result]) {
|
||||
initialize: function(conn, [window, global, caller, type, name, stack, args, result], holdWeak) {
|
||||
protocol.Actor.prototype.initialize.call(this, conn);
|
||||
|
||||
this.details = {
|
||||
|
@ -81,7 +84,7 @@ let FunctionCallActor = protocol.ActorClass({
|
|||
// Store a weak reference to all objects so we don't
|
||||
// prevent natural GC if `holdWeak` was passed into
|
||||
// setup as truthy. Used in the Web Audio Editor.
|
||||
if (this._holdWeak) {
|
||||
if (holdWeak) {
|
||||
let weakRefs = {
|
||||
window: Cu.getWeakReference(window),
|
||||
caller: Cu.getWeakReference(caller),
|
||||
|
@ -526,7 +529,7 @@ let CallWatcherActor = exports.CallWatcherActor = protocol.ActorClass({
|
|||
* Invoked whenever an instrumented function is called.
|
||||
*/
|
||||
_onContentFunctionCall: function(...details) {
|
||||
let functionCall = new FunctionCallActor(this.conn, details);
|
||||
let functionCall = new FunctionCallActor(this.conn, details, this._holdWeak);
|
||||
this._functionCalls.push(functionCall);
|
||||
this.onCall(functionCall);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ const Services = require("Services");
|
|||
|
||||
const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
const events = require("sdk/event/core");
|
||||
const { on: systemOn, off: systemOff } = require("sdk/system/events");
|
||||
const protocol = require("devtools/server/protocol");
|
||||
const { CallWatcherActor, CallWatcherFront } = require("devtools/server/actors/call-watcher");
|
||||
const { ThreadActor } = require("devtools/server/actors/script");
|
||||
|
@ -289,6 +290,9 @@ let WebAudioActor = exports.WebAudioActor = protocol.ActorClass({
|
|||
// to the associated actorID, so we don't have to expose `nativeID`
|
||||
// to the client in any way.
|
||||
this._nativeToActorID = new Map();
|
||||
|
||||
this._onDestroyNode = this._onDestroyNode.bind(this);
|
||||
this._onGlobalDestroyed = this._onGlobalDestroyed.bind(this);
|
||||
},
|
||||
|
||||
destroy: function(conn) {
|
||||
|
@ -326,6 +330,11 @@ let WebAudioActor = exports.WebAudioActor = protocol.ActorClass({
|
|||
performReload: reload,
|
||||
holdWeak: true
|
||||
});
|
||||
// Bind to the `global-destroyed` event on the content observer so we can
|
||||
// unbind events between the global destruction and the `finalize` cleanup
|
||||
// method on the actor.
|
||||
// TODO expose these events on CallWatcherActor itself, bug 1021321
|
||||
on(this._callWatcher._contentObserver, "global-destroyed", this._onGlobalDestroyed);
|
||||
}, {
|
||||
request: { reload: Option(0, "boolean") },
|
||||
oneway: true
|
||||
|
@ -395,6 +404,7 @@ let WebAudioActor = exports.WebAudioActor = protocol.ActorClass({
|
|||
}
|
||||
this.tabActor = null;
|
||||
this._initialized = false;
|
||||
off(this._callWatcher._contentObserver, "global-destroyed", this._onGlobalDestroyed);
|
||||
this._nativeToActorID = null;
|
||||
this._callWatcher.eraseRecording();
|
||||
this._callWatcher.finalize();
|
||||
|
@ -433,6 +443,10 @@ let WebAudioActor = exports.WebAudioActor = protocol.ActorClass({
|
|||
"create-node": {
|
||||
type: "createNode",
|
||||
source: Arg(0, "audionode")
|
||||
},
|
||||
"destroy-node": {
|
||||
type: "destroyNode",
|
||||
source: Arg(0, "audionode")
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -471,6 +485,7 @@ let WebAudioActor = exports.WebAudioActor = protocol.ActorClass({
|
|||
* Called on first audio node creation, signifying audio context usage
|
||||
*/
|
||||
_onStartContext: function () {
|
||||
systemOn("webaudio-node-demise", this._onDestroyNode);
|
||||
emit(this, "start-context");
|
||||
},
|
||||
|
||||
|
@ -520,6 +535,40 @@ let WebAudioActor = exports.WebAudioActor = protocol.ActorClass({
|
|||
_onCreateNode: function (node) {
|
||||
let actor = this._constructAudioNode(node);
|
||||
emit(this, "create-node", actor);
|
||||
},
|
||||
|
||||
/** Called when `webaudio-node-demise` is triggered,
|
||||
* and emits the associated actor to the front if found.
|
||||
*/
|
||||
_onDestroyNode: function ({data}) {
|
||||
// Cast to integer.
|
||||
let nativeID = ~~data;
|
||||
|
||||
let actor = this._getActorByNativeID(nativeID);
|
||||
|
||||
// If actorID exists, emit; in the case where we get demise
|
||||
// notifications for a document that no longer exists,
|
||||
// the mapping should not be found, so we do not emit an event.
|
||||
if (actor) {
|
||||
this._nativeToActorID.delete(nativeID);
|
||||
emit(this, "destroy-node", actor);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the underlying ContentObserver fires `global-destroyed`
|
||||
* so we can cleanup some things between the global being destroyed and
|
||||
* when the actor's `finalize` method gets called.
|
||||
*/
|
||||
_onGlobalDestroyed: function (id) {
|
||||
if (this._callWatcher._tracedWindowId !== id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._nativeToActorID) {
|
||||
this._nativeToActorID.clear();
|
||||
}
|
||||
systemOff("webaudio-node-demise", this._onDestroyNode);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче