Merge mozilla-central to mozilla-inbound on a CLOSED TREE

This commit is contained in:
Carsten "Tomcat" Book 2014-03-26 13:59:35 +01:00
commit 8b07ffe912
20 changed files with 211 additions and 1247 deletions

View File

@ -38,7 +38,7 @@ let developerHUD = {
_targets: new Map(),
_frames: new Map(),
_client: null,
_conn: null,
_webappsActor: null,
_watchers: [],
_logging: true,
@ -61,33 +61,32 @@ let developerHUD = {
RemoteDebugger.start();
}
// We instantiate a local debugger connection so that watchers can use our
// DebuggerClient to send requests to tab actors (e.g. the consoleActor).
// Note the special usage of the private _serverConnection, which we need
// to call connectToChild and set up child process actors on a frame we
// intend to track. These actors will use the connection to communicate with
// our DebuggerServer in the parent process.
let transport = DebuggerServer.connectPipe();
this._conn = transport._serverConnection;
this._client = new DebuggerClient(transport);
this._client = new DebuggerClient(DebuggerServer.connectPipe());
this._client.connect((type, traits) => {
for (let w of this._watchers) {
if (w.init) {
w.init(this._client);
}
}
// FIXME(Bug 962577) see below.
this._client.listTabs((res) => {
this._webappsActor = res.webappsActor;
Services.obs.addObserver(this, 'remote-browser-shown', false);
Services.obs.addObserver(this, 'inprocess-browser-shown', false);
Services.obs.addObserver(this, 'message-manager-disconnect', false);
for (let w of this._watchers) {
if (w.init) {
w.init(this._client);
}
}
let systemapp = document.querySelector('#systemapp');
this.trackFrame(systemapp);
Services.obs.addObserver(this, 'remote-browser-shown', false);
Services.obs.addObserver(this, 'inprocess-browser-shown', false);
Services.obs.addObserver(this, 'message-manager-disconnect', false);
let frames = systemapp.contentWindow.document.querySelectorAll('iframe[mozapp]');
for (let frame of frames) {
this.trackFrame(frame);
}
let systemapp = document.querySelector('#systemapp');
this.trackFrame(systemapp);
let frames = systemapp.contentWindow.document.querySelectorAll('iframe[mozapp]');
for (let frame of frames) {
this.trackFrame(frame);
}
});
});
SettingsListener.observe('hud.logging', this._logging, enabled => {
this._logging = enabled;
@ -118,12 +117,17 @@ let developerHUD = {
if (this._targets.has(frame))
return;
let mm = frame.QueryInterface(Ci.nsIFrameLoaderOwner)
.frameLoader
.messageManager;
// FIXME(Bug 962577) Factor getAppActor out of webappsActor.
this._client.request({
to: this._webappsActor,
type: 'getAppActor',
manifestURL: frame.appManifestURL
}, (res) => {
if (res.error) {
return;
}
DebuggerServer.connectToChild(this._conn, mm).then(actor => {
let target = new Target(frame, actor);
let target = new Target(frame, res.actor);
this._targets.set(frame, target);
for (let w of this._watchers) {

View File

@ -1205,9 +1205,6 @@ pref("devtools.styleeditor.autocompletion-enabled", true);
// Enable the Shader Editor.
pref("devtools.shadereditor.enabled", false);
// Enable the Web Audio Editor
pref("devtools.webaudioeditor.enabled", false);
// Enable tools for Chrome development.
pref("devtools.chrome.enabled", false);

View File

@ -23,7 +23,6 @@ DIRS += [
'styleeditor',
'styleinspector',
'tilt',
'webaudioeditor',
'webconsole',
]
@ -32,4 +31,4 @@ EXTRA_COMPONENTS += [
'devtools-clhandler.manifest',
]
JAR_MANIFESTS += ['jar.mn']
JAR_MANIFESTS += ['jar.mn']

View File

@ -1,12 +0,0 @@
# 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/.
TEST_DIRS += ['test']
JS_MODULES_PATH = 'modules/devtools/webaudioeditor'
EXTRA_JS_MODULES += [
]

View File

@ -1,12 +0,0 @@
[DEFAULT]
support-files =
doc_simple-context.html
doc_complex-context.html
doc_simple-node-creation.html
head.js
[browser_webaudio-actor-simple.js]
[browser_audionode-actor-get-set-param.js]
[browser_audionode-actor-is-source.js]
[browser_audionode-actor-get-type.js]

View File

@ -1,51 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test AudioNode#getParam() / AudioNode#setParam()
*/
function spawnTest () {
let [target, debuggee, front] = yield initBackend(SIMPLE_CONTEXT_URL);
let [_, [destNode, oscNode, gainNode]] = yield Promise.all([
front.setup({ reload: true }),
get3(front, "create-node")
]);
let freq = yield oscNode.getParam("frequency");
info(typeof freq);
ise(freq, 440, "AudioNode:getParam correctly fetches AudioParam");
let type = yield oscNode.getParam("type");
ise(type, "sine", "AudioNode:getParam correctly fetches non-AudioParam");
let type = yield oscNode.getParam("not-a-valid-param");
is(type, undefined, "AudioNode:getParam correctly returns false for invalid param");
let resSuccess = yield oscNode.setParam("frequency", "220", "number");
let freq = yield oscNode.getParam("frequency");
ise(freq, 220, "AudioNode:setParam correctly sets a `number` AudioParam");
is(resSuccess, undefined, "AudioNode:setParam returns undefined for correctly set AudioParam");
resSuccess = yield oscNode.setParam("type", "square", "string");
let type = yield oscNode.getParam("type");
ise(type, "square", "AudioNode:setParam correctly sets a `string` non-AudioParam");
is(resSuccess, undefined, "AudioNode:setParam returns undefined for correctly set AudioParam");
resSuccess = yield oscNode.setParam("type", "\"triangle\"", "string");
type = yield oscNode.getParam("type");
ise(type, "triangle", "AudioNode:setParam correctly removes quotes in `string` non-AudioParam");
try {
yield oscNode.setParam("frequency", "hello", "string");
ok(false, "setParam with invalid types should throw");
} catch (e) {
ok(/is not a finite floating-point/.test(e.message), "AudioNode:setParam returns error with correct message when attempting an invalid assignment");
is(e.type, "TypeError", "AudioNode:setParam returns error with correct type when attempting an invalid assignment");
freq = yield oscNode.getParam("frequency");
ise(freq, 220, "AudioNode:setParam does not modify value when an error occurs");
}
yield removeTab(target.tab);
finish();
}

View File

@ -1,29 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test AudioNode#getType()
*/
function spawnTest () {
let [target, debuggee, front] = yield initBackend(SIMPLE_NODES_URL);
let [_, nodes] = yield Promise.all([
front.setup({ reload: true }),
getN(front, "create-node", 14)
]);
let actualTypes = yield Promise.all(nodes.map(node => node.getType()));
let expectedTypes = [
"AudioDestinationNode",
"AudioBufferSourceNode", "ScriptProcessorNode", "AnalyserNode", "GainNode",
"DelayNode", "BiquadFilterNode", "WaveShaperNode", "PannerNode", "ConvolverNode",
"ChannelSplitterNode", "ChannelMergerNode", "DynamicsCompressorNode", "OscillatorNode"
];
expectedTypes.forEach((type, i) => {
is(actualTypes[i], type, type + " successfully created with correct type");
});
yield removeTab(target.tab);
finish();
}

View File

@ -1,28 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test AudioNode#isSource()
*/
function spawnTest () {
let [target, debuggee, front] = yield initBackend(SIMPLE_NODES_URL);
let [_, nodes] = yield Promise.all([
front.setup({ reload: true }),
getN(front, "create-node", 14)
]);
let actualTypes = yield Promise.all(nodes.map(node => node.getType()));
let isSourceResult = yield Promise.all(nodes.map(node => node.isSource()));
actualTypes.forEach((type, i) => {
let shouldBeSource = type === "AudioBufferSourceNode" || type === "OscillatorNode";
if (shouldBeSource)
is(isSourceResult[i], true, type + "'s isSource() yields into `true`");
else
is(isSourceResult[i], false, type + "'s isSource() yields into `false`");
});
yield removeTab(target.tab);
finish();
}

View File

@ -1,35 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test basic communication of Web Audio actor
*/
function spawnTest () {
let [target, debuggee, front] = yield initBackend(SIMPLE_CONTEXT_URL);
let [_, __, [destNode, oscNode, gainNode], [connect1, connect2]] = yield Promise.all([
front.setup({ reload: true }),
once(front, "start-context"),
get3(front, "create-node"),
get2(front, "connect-node")
]);
let destType = yield destNode.getType();
let oscType = yield oscNode.getType();
let gainType = yield gainNode.getType();
is(destType, "AudioDestinationNode", "WebAudioActor:create-node returns AudioNodeActor for AudioDestination");
is(oscType, "OscillatorNode", "WebAudioActor:create-node returns AudioNodeActor");
is(gainType, "GainNode", "WebAudioActor:create-node returns AudioNodeActor");
let { source, dest } = connect1;
is(source.actorID, oscNode.actorID, "WebAudioActor:connect-node returns correct actor with ID on source (osc->gain)");
is(dest.actorID, gainNode.actorID, "WebAudioActor:connect-node returns correct actor with ID on dest (osc->gain)");
let { source, dest } = connect2;
is(source.actorID, gainNode.actorID, "WebAudioActor:connect-node returns correct actor with ID on source (gain->dest)");
is(dest.actorID, destNode.actorID, "WebAudioActor:connect-node returns correct actor with ID on dest (gain->dest)");
yield removeTab(target.tab);
finish();
}

View File

@ -1,44 +0,0 @@
<!-- 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";
/*
↱ proc
osc → gain →
osc → gain → destination
buffer →↳ filter →
*/
let ctx = new AudioContext();
let osc1 = ctx.createOscillator();
let gain1 = ctx.createGain();
let proc = ctx.createScriptProcessor();
osc1.connect(gain1);
osc1.connect(proc);
gain1.connect(ctx.destination);
let osc2 = ctx.createOscillator();
let gain2 = ctx.createGain();
osc2.connect(gain2);
gain2.connect(ctx.destination);
let buf = ctx.createBufferSource();
let filter = ctx.createBiquadFilter();
buf.connect(filter);
osc2.connect(filter);
filter.connect(ctx.destination);
</script>
</body>
</html>

View File

@ -1,26 +0,0 @@
<!-- 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";
let ctx = new AudioContext();
let osc = ctx.createOscillator();
let gain = ctx.createGain();
gain.gain.value = 0;
osc.connect(gain);
gain.connect(ctx.destination);
osc.start(0);
</script>
</body>
</html>

View File

@ -1,28 +0,0 @@
<!-- 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";
let ctx = new AudioContext();
let NODE_CREATION_METHODS = [
"createBufferSource", "createScriptProcessor", "createAnalyser",
"createGain", "createDelay", "createBiquadFilter", "createWaveShaper",
"createPanner", "createConvolver", "createChannelSplitter", "createChannelMerger",
"createDynamicsCompressor", "createOscillator"
];
let nodes = NODE_CREATION_METHODS.map(method => ctx[method]());
</script>
</body>
</html>

View File

@ -1,160 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
let { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
// Enable logging for all the tests. Both the debugger server and frontend will
// be affected by this pref.
let gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
Services.prefs.setBoolPref("devtools.debugger.log", true);
let { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
let { Promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
let { DebuggerClient } = Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
let { WebAudioFront } = devtools.require("devtools/server/actors/webaudio");
let TargetFactory = devtools.TargetFactory;
let Toolbox = devtools.Toolbox;
const EXAMPLE_URL = "http://example.com/browser/browser/devtools/webaudioeditor/test/";
const SIMPLE_CONTEXT_URL = EXAMPLE_URL + "doc_simple-context.html";
const COMPLEX_CONTEXT_URL = EXAMPLE_URL + "doc_complex-context.html";
const SIMPLE_NODES_URL = EXAMPLE_URL + "doc_simple-node-creation.html";
// All tests are asynchronous.
waitForExplicitFinish();
let gToolEnabled = Services.prefs.getBoolPref("devtools.webaudioeditor.enabled");
registerCleanupFunction(() => {
info("finish() was called, cleaning up...");
Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
Services.prefs.setBoolPref("devtools.webaudioeditor.enabled", gToolEnabled);
Cu.forceGC();
});
function addTab(aUrl, aWindow) {
info("Adding tab: " + aUrl);
let deferred = Promise.defer();
let targetWindow = aWindow || window;
let targetBrowser = targetWindow.gBrowser;
targetWindow.focus();
let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl);
let linkedBrowser = tab.linkedBrowser;
linkedBrowser.addEventListener("load", function onLoad() {
linkedBrowser.removeEventListener("load", onLoad, true);
info("Tab added and finished loading: " + aUrl);
deferred.resolve(tab);
}, true);
return deferred.promise;
}
function removeTab(aTab, aWindow) {
info("Removing tab.");
let deferred = Promise.defer();
let targetWindow = aWindow || window;
let targetBrowser = targetWindow.gBrowser;
let tabContainer = targetBrowser.tabContainer;
tabContainer.addEventListener("TabClose", function onClose(aEvent) {
tabContainer.removeEventListener("TabClose", onClose, false);
info("Tab removed and finished closing.");
deferred.resolve();
}, false);
targetBrowser.removeTab(aTab);
return deferred.promise;
}
function handleError(aError) {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
finish();
}
function once(aTarget, aEventName, aUseCapture = false) {
info("Waiting for event: '" + aEventName + "' on " + aTarget + ".");
let deferred = Promise.defer();
for (let [add, remove] of [
["on", "off"], // Use event emitter before DOM events for consistency
["addEventListener", "removeEventListener"],
["addListener", "removeListener"]
]) {
if ((add in aTarget) && (remove in aTarget)) {
aTarget[add](aEventName, function onEvent(...aArgs) {
aTarget[remove](aEventName, onEvent, aUseCapture);
deferred.resolve(...aArgs);
}, aUseCapture);
break;
}
}
return deferred.promise;
}
function test () {
Task.spawn(spawnTest).then(finish, handleError);
}
function initBackend(aUrl) {
info("Initializing a web audio editor front.");
if (!DebuggerServer.initialized) {
DebuggerServer.init(() => true);
DebuggerServer.addBrowserActors();
}
return Task.spawn(function*() {
let tab = yield addTab(aUrl);
let target = TargetFactory.forTab(tab);
let debuggee = target.window.wrappedJSObject;
yield target.makeRemote();
let front = new WebAudioFront(target.client, target.form);
return [target, debuggee, front];
});
}
// Due to web audio will fire most events synchronously back-to-back,
// and we can't yield them in a chain without missing actors, this allows
// us to listen for `n` events and return a promise resolving to them.
//
// Takes a `front` object that is an event emitter, the number of
// programs that should be listened to and waited on, and an optional
// `onAdd` function that calls with the entire actors array on program link
function getN (front, eventName, count, spread) {
let actors = [];
let deferred = Promise.defer();
front.on(eventName, function onEvent (...args) {
let actor = args[0];
if (actors.length !== count) {
actors.push(spread ? args : actor);
}
if (actors.length === count) {
front.off(eventName, onEvent);
deferred.resolve(actors);
}
});
return deferred.promise;
}
function get (front, eventName) { return getN(front, eventName, 1); }
function get2 (front, eventName) { return getN(front, eventName, 2); }
function get3 (front, eventName) { return getN(front, eventName, 3); }
function getSpread (front, eventName) { return getN(front, eventName, 1, true); }
function get2Spread (front, eventName) { return getN(front, eventName, 2, true); }
function get3Spread (front, eventName) { return getN(front, eventName, 3, true); }
function getNSpread (front, eventName, count) { return getN(front, eventName, count, true); }

View File

@ -1,6 +0,0 @@
# 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/.
BROWSER_CHROME_MANIFESTS += ['browser.ini']

View File

@ -15,14 +15,11 @@ Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/WebappOSUtils.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "NetworkUtil",
"@mozilla.org/network/util;1",
"nsINetUtil");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyGetter(this, "NetUtil", function() {
return Cc["@mozilla.org/network/util;1"]
.getService(Ci.nsINetUtil);
});
// Shared code for AppsServiceChild.jsm, Webapps.jsm and Webapps.js
@ -342,7 +339,7 @@ this.AppsUtils = {
checkManifestContentType(aInstallOrigin, aWebappOrigin, aContentType) {
let hadCharset = { };
let charset = { };
let contentType = NetworkUtil.parseContentType(aContentType, charset, hadCharset);
let contentType = NetUtil.parseContentType(aContentType, charset, hadCharset);
if (aInstallOrigin != aWebappOrigin &&
contentType != "application/x-web-app-manifest+json") {
return false;
@ -499,57 +496,30 @@ this.AppsUtils = {
return true;
},
// Asynchronously loads a JSON file. aPath is a string representing the path
// Loads a JSON file using OS.file. aFile is a string representing the path
// of the file to be read.
loadJSONAsync: function(aPath) {
let deferred = Promise.defer();
try {
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
file.initWithPath(aPath);
let channel = NetUtil.newChannel(file);
channel.contentType = "application/json";
NetUtil.asyncFetch(channel, function(aStream, aResult) {
if (!Components.isSuccessCode(aResult)) {
deferred.resolve(null);
if (aResult == Cr.NS_ERROR_FILE_NOT_FOUND) {
// We expect this under certain circumstances, like for webapps.json
// on firstrun, so we return early without reporting an error.
return;
}
Cu.reportError("AppsUtils: Could not read from json file " + aPath);
return;
}
try {
// Obtain a converter to read from a UTF-8 encoded input stream.
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
.createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
// Read json file into a string
let data = JSON.parse(converter.ConvertToUnicode(NetUtil.readInputStreamToString(aStream,
aStream.available()) || ""));
aStream.close();
deferred.resolve(data);
} catch (ex) {
Cu.reportError("AppsUtils: Could not parse JSON: " +
aPath + " " + ex + "\n" + ex.stack);
deferred.resolve(null);
}
});
} catch (ex) {
Cu.reportError("AppsUtils: Could not read from " +
aPath + " : " + ex + "\n" + ex.stack);
deferred.resolve(null);
}
return deferred.promise;
// Returns a Promise resolved with the json payload or rejected with
// OS.File.Error
loadJSONAsync: function(aFile) {
debug("_loadJSONAsync: " + aFile);
return Task.spawn(function() {
let file = yield OS.File.open(aFile, { read: true });
let rawData = yield file.read();
// Read json file into a string
let data;
try {
// Obtain a converter to read from a UTF-8 encoded input stream.
let converter = new TextDecoder();
data = JSON.parse(converter.decode(rawData));
file.close();
} catch (ex) {
debug("Error parsing JSON: " + aFile + ". Error: " + ex);
Cu.reportError("OperatorApps: Could not parse JSON: " +
aFile + " " + ex + "\n" + ex.stack);
throw ex;
}
throw new Task.Result(data);
});
},
// Returns the MD5 hash of a string.

View File

@ -18,8 +18,6 @@ Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/AppsUtils.jsm");
Cu.import("resource://gre/modules/Task.jsm");
let Path = OS.Path;
#ifdef MOZ_B2G_RIL
XPCOMUtils.defineLazyServiceGetter(this, "iccProvider",
"@mozilla.org/ril/content-helper;1",
@ -60,6 +58,9 @@ function isFirstRunWithSIM() {
}
#ifdef MOZ_B2G_RIL
let File = OS.File;
let Path = OS.Path;
let iccListener = {
notifyStkCommand: function() {},
@ -139,37 +140,38 @@ this.OperatorAppsRegistry = {
debug("copying " + aOrg + " to " + aDst);
return aDst && Task.spawn(function() {
try {
let orgDir = Cc["@mozilla.org/file/local;1"]
.createInstance(Ci.nsIFile);
orgDir.initWithPath(aOrg);
if (!orgDir.isDirectory()) {
let orgInfo = yield File.stat(aOrg);
if (!orgInfo.isDir) {
return;
}
let dstDir = Cc["@mozilla.org/file/local;1"]
.createInstance(Ci.nsIFile);
dstDir.initWithPath(aDst);
if (!dstDir.exists()) {
dstDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
let dirDstExists = yield File.exists(aDst);
if (!dirDstExists) {
yield File.makeDir(aDst);
}
let entries = orgDir.directoryEntries;
while (entries.hasMoreElements()) {
let entry = entries.getNext().QueryInterface(Ci.nsIFile);
if (!entry.isDirectory()) {
// Remove the file, because copyTo doesn't overwrite files.
let dstFile = dstDir.clone();
dstFile.append(entry.leafName);
if(dstFile.exists()) {
dstFile.remove(false);
let iterator = new File.DirectoryIterator(aOrg);
if (!iterator) {
debug("No iterator over: " + aOrg);
return;
}
try {
while (true) {
let entry;
try {
entry = yield iterator.next();
} catch (ex if ex == StopIteration) {
break;
}
entry.copyTo(dstDir, entry.leafName);
} else {
yield this._copyDirectory(entry.path,
Path.join(aDst, entry.name));
if (!entry.isDir) {
yield File.copy(entry.path, Path.join(aDst, entry.name));
} else {
yield this._copyDirectory(entry.path,
Path.join(aDst, entry.name));
}
}
} finally {
iterator.close();
}
} catch (e) {
debug("Error copying " + aOrg + " to " + aDst + ". " + e);
@ -193,27 +195,23 @@ this.OperatorAppsRegistry = {
// DIRECTORY_NAME + SINGLE_VARIANT_SOURCE_DIR and move all apps (and
// configuration file) to PREF_SINGLE_VARIANT_DIR and return
// PREF_SINGLE_VARIANT_DIR as sourceDir.
let svFinalDir = Cc["@mozilla.org/file/local;1"]
.createInstance(Ci.nsIFile);
svFinalDir.initWithPath(svFinalDirName);
if (!svFinalDir.exists()) {
svFinalDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
let existsDir = yield File.exists(svFinalDirName);
if (!existsDir) {
yield File.makeDir(svFinalDirName, {ignoreExisting: true});
}
let svIndex = svFinalDir.clone();
svIndex.append(SINGLE_VARIANT_CONF_FILE);
if (!svIndex.exists()) {
let svSourceDir = FileUtils.getFile(DIRECTORY_NAME,
[SINGLE_VARIANT_SOURCE_DIR]);
yield this._copyDirectory(svSourceDir.path, svFinalDirName);
debug("removing directory:" + svSourceDir.path);
try {
svSourceDir.remove(true);
} catch(ex) { }
let existsSvIndex = yield File.exists(Path.join(svFinalDirName,
SINGLE_VARIANT_CONF_FILE));
if (!existsSvIndex) {
let svSourceDirName = FileUtils.getFile(DIRECTORY_NAME,
[SINGLE_VARIANT_SOURCE_DIR]).path;
yield this._copyDirectory(svSourceDirName, svFinalDirName);
debug("removing directory:" + svSourceDirName);
File.removeDir(svSourceDirName, {
ignoreAbsent: true,
ignorePermissions: true
});
}
this.appsDir = svFinalDirName;
}.bind(this));
},
@ -285,18 +283,18 @@ this.OperatorAppsRegistry = {
if (isPackage) {
debug("aId:" + aId + ". Installing as packaged app.");
let installPack = this.appsDir.clone();
installPack.append(aId);
installPack.append(APPLICATION_ZIP);
if (!installPack.exists()) {
debug("SV " + installPack.path + " file do not exists for app " + aId);
return;
}
appData.app.localInstallPath = installPack.path;
appData.app.updateManifest = aManifest;
DOMApplicationRegistry.confirmInstall(appData);
let installPack = OS.Path.join(this.appsDir.path, aId, APPLICATION_ZIP);
OS.File.exists(installPack).then(
function(aExists) {
if (!aExists) {
debug("SV " + installPack.path + " file do not exists for app " +
aId);
return;
}
appData.app.localInstallPath = installPack;
appData.app.updateManifest = aManifest;
DOMApplicationRegistry.confirmInstall(appData);
});
} else {
debug("aId:" + aId + ". Installing as hosted app.");
appData.app.manifest = aManifest;
@ -317,24 +315,18 @@ this.OperatorAppsRegistry = {
for (let i = 0; i < aIdsApp.length; i++) {
let aId = aIdsApp[i];
let aMetadata = yield AppsUtils.loadJSONAsync(
Path.join(this.appsDir.path, aId, METADATA));
if (!aMetadata) {
debug("Error reading metadata file");
return;
}
OS.Path.join(this.appsDir.path, aId, METADATA));
debug("metadata:" + JSON.stringify(aMetadata));
let isPackage = true;
let manifest;
let manifests = [UPDATEMANIFEST, MANIFEST];
for (let j = 0; j < manifests.length; j++) {
manifest = yield AppsUtils.loadJSONAsync(
Path.join(this.appsDir.path, aId, manifests[j]));
if (!manifest) {
isPackage = false;
} else {
try {
manifest = yield AppsUtils.loadJSONAsync(
OS.Path.join(this.appsDir.path, aId, manifests[j]));
break;
} catch (e) {
isPackage = false;
}
}
if (manifest) {
@ -364,7 +356,7 @@ this.OperatorAppsRegistry = {
return Task.spawn(function () {
let key = normalizeCode(aMcc) + "-" + normalizeCode(aMnc);
let file = Path.join(this.appsDir.path, SINGLE_VARIANT_CONF_FILE);
let file = OS.Path.join(this.appsDir.path, SINGLE_VARIANT_CONF_FILE);
let aData = yield AppsUtils.loadJSONAsync(file);
if (!aData || !(key in aData)) {
return;

View File

@ -178,7 +178,7 @@ this.DOMApplicationRegistry = {
// loads the current registry, that could be empty on first run.
loadCurrentRegistry: function() {
return AppsUtils.loadJSONAsync(this.appsFile).then((aData) => {
return this._loadJSONAsync(this.appsFile).then((aData) => {
if (!aData) {
return;
}
@ -513,7 +513,7 @@ this.DOMApplicationRegistry = {
}
// a
let data = yield AppsUtils.loadJSONAsync(file.path);
let data = yield this._loadJSONAsync(file.path);
if (!data) {
return;
}
@ -944,6 +944,56 @@ this.DOMApplicationRegistry = {
}
},
_loadJSONAsync: function(aPath) {
let deferred = Promise.defer();
try {
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
file.initWithPath(aPath);
let channel = NetUtil.newChannel(file);
channel.contentType = "application/json";
NetUtil.asyncFetch(channel, function(aStream, aResult) {
if (!Components.isSuccessCode(aResult)) {
deferred.resolve(null);
if (aResult == Cr.NS_ERROR_FILE_NOT_FOUND) {
// We expect this under certain circumstances, like for webapps.json
// on firstrun, so we return early without reporting an error.
return;
}
Cu.reportError("DOMApplicationRegistry: Could not read from json file "
+ aPath);
return;
}
try {
// Obtain a converter to read from a UTF-8 encoded input stream.
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
.createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
// Read json file into a string
let data = JSON.parse(converter.ConvertToUnicode(NetUtil.readInputStreamToString(aStream,
aStream.available()) || ""));
aStream.close();
deferred.resolve(data);
} catch (ex) {
Cu.reportError("DOMApplicationRegistry: Could not parse JSON: " +
aPath + " " + ex + "\n" + ex.stack);
deferred.resolve(null);
}
});
} catch (ex) {
Cu.reportError("DOMApplicationRegistry: Could not read from " +
aPath + " : " + ex + "\n" + ex.stack);
deferred.resolve(null);
}
return deferred.promise;
},
addMessageListener: function(aMsgNames, aApp, aMm) {
aMsgNames.forEach(function (aMsgName) {
let man = aApp && aApp.manifestURL;
@ -1179,30 +1229,10 @@ this.DOMApplicationRegistry = {
_writeFile: function(aPath, aData) {
debug("Saving " + aPath);
let deferred = Promise.defer();
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
file.initWithPath(aPath);
// Initialize the file output stream
let ostream = FileUtils.openSafeFileOutputStream(file);
// Obtain a converter to convert our data to a UTF-8 encoded input stream.
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
.createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
// Asynchronously copy the data to the file.
let istream = converter.convertToInputStream(aData);
NetUtil.asyncCopy(istream, ostream, function(aResult) {
if (!Components.isSuccessCode(aResult)) {
deferred.reject()
} else {
deferred.resolve();
}
});
return deferred.promise;
return OS.File.writeAtomic(aPath,
new TextEncoder().encode(aData),
{ tmpPath: aPath + ".tmp" })
.then(null, Cu.reportError);
},
doLaunch: function (aData, aMm) {
@ -1371,7 +1401,7 @@ this.DOMApplicationRegistry = {
return;
}
AppsUtils.loadJSONAsync(file.path).then((aJSON) => {
this._loadJSONAsync(file.path).then((aJSON) => {
if (!aJSON) {
debug("startDownload: No update manifest found at " + file.path + " " +
aManifestURL);
@ -1484,7 +1514,7 @@ this.DOMApplicationRegistry = {
// Update the handlers and permissions for this app.
this.updateAppHandlers(aOldManifest, aData, app);
AppsUtils.loadJSONAsync(staged.path).then((aUpdateManifest) => {
this._loadJSONAsync(staged.path).then((aUpdateManifest) => {
let appObject = AppsUtils.cloneAppObject(app);
appObject.updateManifest = aUpdateManifest;
this.notifyUpdateHandlers(appObject, aData, appFile.path);
@ -2575,7 +2605,7 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
let fileNames = ["manifest.webapp", "update.webapp", "manifest.json"];
for (let fileName of fileNames) {
this._manifestCache[id] = yield AppsUtils.loadJSONAsync(OS.Path.join(dir.path, fileName));
this._manifestCache[id] = yield this._loadJSONAsync(OS.Path.join(dir.path, fileName));
if (this._manifestCache[id]) {
break;
}
@ -2866,41 +2896,48 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
* @returns {String} the MD5 hash of the file
*/
_computeFileHash: function(aFilePath) {
let deferred = Promise.defer();
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
file.initWithPath(aFilePath);
NetUtil.asyncFetch(file, function(inputStream, status) {
if (!Components.isSuccessCode(status)) {
debug("Error reading " + aFilePath + ": " + e);
deferred.reject();
return;
}
return Task.spawn(function*() {
let hasher = Cc["@mozilla.org/security/hash;1"]
.createInstance(Ci.nsICryptoHash);
// We want to use the MD5 algorithm.
hasher.init(hasher.MD5);
const PR_UINT32_MAX = 0xffffffff;
hasher.updateFromStream(inputStream, PR_UINT32_MAX);
const CHUNK_SIZE = 16384;
// Return the two-digit hexadecimal code for a byte.
function toHexString(charCode) {
return ("0" + charCode.toString(16)).slice(-2);
}
let file;
try {
file = yield OS.File.open(aFilePath, { read: true });
} catch(e) {
debug("Error opening " + aFilePath + ": " + e);
return null;
}
try {
let array;
do {
array = yield file.read(CHUNK_SIZE);
hasher.update(array, array.length);
} while (array.length == CHUNK_SIZE);
} catch(e) {
debug("Error reading " + aFilePath + ": " + e);
return null;
}
yield file.close();
// We're passing false to get the binary hash and not base64.
let data = hasher.finish(false);
// Convert the binary hash data to a hex string.
let hash = [toHexString(data.charCodeAt(i)) for (i in data)].join("");
debug("File hash computed: " + hash);
deferred.resolve(hash);
return hash;
});
return deferred.promise;
},
/**
@ -2928,15 +2965,12 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
aOldApp.manifestHash = aOldApp.staged.manifestHash;
aOldApp.etag = aOldApp.staged.etag || aOldApp.etag;
aOldApp.staged = {};
// Move the staged update manifest to a non staged one.
try {
let staged = this._getAppDir(aId);
staged.append("staged-update.webapp");
staged.moveTo(staged.parent, "update.webapp");
} catch (ex) {
// We don't really mind much if this fails.
}
let dirPath = this._getAppDir(aId).path;
// We don't really mind much if this fails.
OS.File.move(OS.Path.join(dirPath, "staged-update.webapp"),
OS.Path.join(dirPath, "update.webapp"));
}
// Save the updated registry, and cleanup the tmp directory.

View File

@ -1,173 +0,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/. */
"use strict";
const {Cc, Ci, Cu, Cr} = require("chrome");
const protocol = require("devtools/server/protocol");
const { method, Arg, Option, RetVal } = protocol;
// Add a grip type for our `getParam` method, as the type can be
// unknown.
protocol.types.addDictType("audio-node-param-grip", {
type: "string",
value: "nullable:primitive"
});
/**
* An Audio Node actor allowing communication to a specific audio node in the
* Audio Context graph.
*/
let AudioNodeActor = exports.AudioNodeActor = protocol.ActorClass({
typeName: "audionode",
/**
* Create the Audio Node actor.
*
* @param DebuggerServerConnection conn
* The server connection.
* @param AudioNode node
* The AudioNode that was created.
*/
initialize: function (conn, node) {
protocol.Actor.prototype.initialize.call(this, conn);
this.node = XPCNativeWrapper.unwrap(node);
try {
this.type = this.node.toString().match(/\[object (.*)\]$/)[1];
} catch (e) {
this.type = "";
}
},
/**
* Returns the name of the audio type.
* Examples: "OscillatorNode", "MediaElementAudioSourceNode"
*/
getType: method(function () {
return this.type;
}, {
response: { type: RetVal("string") }
}),
/**
* Returns a boolean indicating if the node is a source node,
* like BufferSourceNode, MediaElementAudioSourceNode, OscillatorNode, etc.
*/
isSource: method(function () {
return !!~this.type.indexOf("Source") || this.type === "OscillatorNode";
}, {
response: { source: RetVal("boolean") }
}),
/**
* Changes a param on the audio node. Responds with a `string` that's either
* an empty string `""` on success, or a description of the error upon
* param set failure.
*
* @param String param
* Name of the AudioParam to change.
* @param String value
* Value to change AudioParam to. Subsequently cast via `type`.
* @param String type
* Datatype that `value` should be cast to.
*/
setParam: method(function (param, value, dataType) {
// Strip quotes because sometimes UIs include that for strings
if (dataType === "string") {
value = value.replace(/[\'\"]*/g, "");
}
try {
if (isAudioParam(this.node, param))
this.node[param].value = cast(value, dataType);
else
this.node[param] = cast(value, dataType);
return undefined;
} catch (e) {
return constructError(e);
}
}, {
request: {
param: Arg(0, "string"),
value: Arg(1, "string"),
dataType: Arg(2, "string")
},
response: { error: RetVal("nullable:json") }
}),
/**
* Gets a param on the audio node.
*
* @param String param
* Name of the AudioParam to fetch.
*/
getParam: method(function (param) {
// If property does not exist, just return "undefined"
if (!this.node[param])
return undefined;
let value = isAudioParam(this.node, param) ? this.node[param].value : this.node[param];
let type = typeof type;
return value;
return { type: type, value: value };
}, {
request: {
param: Arg(0, "string")
},
response: { text: RetVal("nullable:primitive") }
}),
});
/**
* Casts string `value` to specified `type`.
*
* @param String value
* The string to cast.
* @param String type
* The datatype to cast `value` to.
*/
function cast (value, type) {
if (!type || type === "string")
return value;
if (type === "number")
return parseFloat(value);
if (type === "boolean")
return value === "true";
return undefined;
}
/**
* Determines whether or not property is an AudioParam.
*
* @param AudioNode node
* An AudioNode.
* @param String prop
* Property of `node` to evaluate to see if it's an AudioParam.
* @return Boolean
*/
function isAudioParam (node, prop) {
return /AudioParam/.test(node[prop].toString());
}
/**
* Takes an `Error` object and constructs a JSON-able response
*
* @param Error err
* A TypeError, RangeError, etc.
* @return Object
*/
function constructError (err) {
return {
message: err.message,
type: err.constructor.name
};
}
/**
* The corresponding Front object for the AudioNodeActor.
*/
let AudioNodeFront = protocol.FrontClass(AudioNodeActor, {
initialize: function (client, form) {
protocol.Front.prototype.initialize.call(this, client, form);
client.addActorPool(this);
this.manage(this);
}
});

View File

@ -1,427 +0,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/. */
"use strict";
const {Cc, Ci, Cu, Cr} = require("chrome");
const Services = require("Services");
const events = require("sdk/event/core");
const protocol = require("devtools/server/protocol");
const { on, once, off, emit } = events;
const { method, Arg, Option, RetVal } = protocol;
const { AudioNodeActor } = require("devtools/server/actors/audionode");
const console = Cu.import("resource://gre/modules/devtools/Console.jsm").console;
exports.register = function(handle) {
handle.addTabActor(WebAudioActor, "webaudioActor");
};
exports.unregister = function(handle) {
handle.removeTabActor(WebAudioActor);
};
/**
* A WebGL Shader contributing to building a WebGL Program.
/**
* The Web Audio Actor handles simple interaction with an AudioContext
* high-level methods. After instantiating this actor, you'll need to set it
* up by calling setup().
*/
let WebAudioActor = exports.WebAudioActor = protocol.ActorClass({
typeName: "webaudio",
initialize: function(conn, tabActor) {
protocol.Actor.prototype.initialize.call(this, conn);
this.tabActor = tabActor;
this._onGlobalCreated = this._onGlobalCreated.bind(this);
this._onGlobalDestroyed = this._onGlobalDestroyed.bind(this);
this._onStartContext = this._onStartContext.bind(this);
this._onConnectNode = this._onConnectNode.bind(this);
this._onConnectParam = this._onConnectParam.bind(this);
this._onDisconnectNode = this._onDisconnectNode.bind(this);
this._onParamChange = this._onParamChange.bind(this);
this._onCreateNode = this._onCreateNode.bind(this);
},
destroy: function(conn) {
protocol.Actor.prototype.destroy.call(this, conn);
this.finalize();
},
/**
* Starts waiting for the current tab actor's document global to be
* created, in order to instrument the Canvas context and become
* aware of everything the content does with Web Audio.
*
* See ContentObserver and WebAudioInstrumenter for more details.
*/
setup: method(function({ reload }) {
if (this._initialized) {
return;
}
this._initialized = true;
// Weak map mapping audio nodes to their corresponding actors
this._nodeActors = new Map();
this._contentObserver = new ContentObserver(this.tabActor);
this._webaudioObserver = new WebAudioObserver();
on(this._contentObserver, "global-created", this._onGlobalCreated);
on(this._contentObserver, "global-destroyed", this._onGlobalDestroyed);
on(this._webaudioObserver, "start-context", this._onStartContext);
on(this._webaudioObserver, "connect-node", this._onConnectNode);
on(this._webaudioObserver, "connect-param", this._onConnectParam);
on(this._webaudioObserver, "disconnect-node", this._onDisconnectNode);
on(this._webaudioObserver, "param-change", this._onParamChange);
on(this._webaudioObserver, "create-node", this._onCreateNode);
if (reload) {
this.tabActor.window.location.reload();
}
}, {
request: { reload: Option(0, "boolean") },
oneway: true
}),
/**
* Stops listening for document global changes and puts this actor
* to hibernation. This method is called automatically just before the
* actor is destroyed.
*/
finalize: method(function() {
if (!this._initialized) {
return;
}
this._initialized = false;
this._contentObserver.stopListening();
off(this._contentObserver, "global-created", this._onGlobalCreated);
off(this._contentObserver, "global-destroyed", this._onGlobalDestroyed);
off(this._webaudioObserver, "start-context", this._onStartContext);
off(this._webaudioObserver, "connect-node", this._onConnectNode);
off(this._webaudioObserver, "connect-param", this._onConnectParam);
off(this._webaudioObserver, "disconnect-node", this._onDisconnectNode);
off(this._webaudioObserver, "param-change", this._onParamChange);
off(this._webaudioObserver, "create-node", this._onCreateNode);
this._contentObserver = null;
this._webaudioObserver = null;
}, {
oneway: true
}),
/**
* Events emitted by this actor.
*/
events: {
"start-context": {
type: "startContext"
},
"connect-node": {
type: "connectNode",
source: Option(0, "audionode"),
dest: Option(0, "audionode")
},
"disconnect-node": {
type: "disconnectNode",
source: Arg(0, "audionode")
},
"connect-param": {
type: "connectParam",
source: Arg(0, "audionode"),
param: Arg(1, "string")
},
"change-param": {
type: "changeParam",
source: Option(0, "audionode"),
param: Option(0, "string"),
value: Option(0, "string")
},
"create-node": {
type: "createNode",
source: Arg(0, "audionode")
}
},
/**
* Invoked whenever the current tab actor's document global is created.
*/
_onGlobalCreated: function(window) {
WebAudioInstrumenter.handle(window, this._webaudioObserver);
},
/**
* Invoked whenever the current tab actor's inner window is destroyed.
*/
_onGlobalDestroyed: function(id) {
},
/**
* Helper for constructing an AudioNodeActor, assigning to
* internal weak map, and tracking via `manage` so it is assigned
* an `actorID`.
*/
_constructAudioNode: function (node) {
let actor = new AudioNodeActor(this.conn, node);
this.manage(actor);
this._nodeActors.set(node, actor);
return actor;
},
/**
* Takes an AudioNode and returns the stored actor for it.
* In some cases, we won't have an actor stored (for example,
* 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);
}
return actor;
},
/**
* Called on first audio node creation, signifying audio context usage
*/
_onStartContext: function () {
events.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", {
source: sourceActor,
dest: destActor
});
},
/**
* Called when an audio node is connected to an audio param.
* Implement in bug 986705
*/
_onConnectParam: function (source, dest) {
// TODO bug 986705
},
/**
* Called when an audio node is disconnected.
*/
_onDisconnectNode: function (node) {
let actor = this._actorFor(node);
events.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", {
source: actor,
param: param,
value: value
});
},
/**
* Called on node creation.
*/
_onCreateNode: function (node) {
let actor = this._constructAudioNode(node);
events.emit(this, "create-node", actor);
}
});
/**
* The corresponding Front object for the WebAudioActor.
*/
let WebAudioFront = exports.WebAudioFront = protocol.FrontClass(WebAudioActor, {
initialize: function(client, { webaudioActor }) {
protocol.Front.prototype.initialize.call(this, client, { actor: webaudioActor });
client.addActorPool(this);
this.manage(this);
}
});
/**
* Handles adding an observer for the creation of content document globals,
* event sent immediately after a web content document window has been set up,
* but before any script code has been executed. This will allow us to
* instrument the HTMLCanvasElement with the appropriate inspection methods.
* TODO use ContentObserver from bug 917226 once landed, bug 986704
*/
function ContentObserver(tabActor) {
this._contentWindow = tabActor.window;
this._onContentGlobalCreated = this._onContentGlobalCreated.bind(this);
this._onInnerWindowDestroyed = this._onInnerWindowDestroyed.bind(this);
this.startListening();
}
ContentObserver.prototype = {
/**
* Starts listening for the required observer messages.
*/
startListening: function() {
Services.obs.addObserver(
this._onContentGlobalCreated, "content-document-global-created", false);
Services.obs.addObserver(
this._onInnerWindowDestroyed, "inner-window-destroyed", false);
},
/**
* Stops listening for the required observer messages.
*/
stopListening: function() {
Services.obs.removeObserver(
this._onContentGlobalCreated, "content-document-global-created", false);
Services.obs.removeObserver(
this._onInnerWindowDestroyed, "inner-window-destroyed", false);
},
/**
* Fired immediately after a web content document window has been set up.
*/
_onContentGlobalCreated: function(subject, topic, data) {
if (subject == this._contentWindow) {
emit(this, "global-created", subject);
}
},
/**
* Fired when an inner window is removed from the backward/forward cache.
*/
_onInnerWindowDestroyed: function(subject, topic, data) {
let id = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
emit(this, "global-destroyed", id);
}
};
/**
* Instruments an AudioContext with inspector methods.
* TODO refactor with CallWatcherActor, bug 986704
*/
let WebAudioInstrumenter = {
/**
* Overrides all AudioContext methods.
*
* @param nsIDOMWindow window
* The window to perform the instrumentation in.
* @param WebAudioObserver observer
* The observer watching function calls in the context.
*/
handle: function(window, observer) {
let self = this;
let AudioContext = unwrap(window.AudioContext);
let AudioNode = unwrap(window.AudioNode);
let ctxProto = AudioContext.prototype;
let nodeProto = AudioNode.prototype;
// All Web Audio nodes inherit from AudioNode's prototype, so
// hook into the `connect` and `disconnect` methods
// audionode.connect(node|param);
let originalConnect = nodeProto.connect;
nodeProto.connect = function (...args) {
let source = unwrap(this);
let nodeOrParam = unwrap(args[0]);
originalConnect.apply(source, args);
// Alert observer differently if connecting to an AudioNode or AudioParam
if (nodeOrParam instanceof AudioNode)
observer.connectNode(source, nodeOrParam);
else
observer.connectParam(source, nodeOrParam);
};
// audionode.disconnect()
let originalDisconnect = nodeProto.disconnect;
nodeProto.disconnect = function (...args) {
let source = unwrap(this);
originalDisconnect.apply(source, args);
observer.disconnectNode(source);
};
// Keep track of the first node created, so we can alert
// the front end that an audio context is being used since
// we're not hooking into the constructor itself, just its
// instance's methods.
let firstNodeCreated = false;
// Patch all of AudioContext's methods that create an audio node
// and hook into the observer
NODE_CREATION_METHODS.forEach(method => {
let originalMethod = ctxProto[method];
ctxProto[method] = function (...args) {
let node = originalMethod.apply(this, args);
// Fire the start-up event if this is the first node created
// and trigger a `create-node` event for the context destination
if (!firstNodeCreated) {
firstNodeCreated = true;
observer.startContext();
observer.createNode(node.context.destination);
}
observer.createNode(node);
return node;
};
});
}
};
/**
* An observer that captures an Audio Context's actions and emits
* events
*/
function WebAudioObserver () {}
WebAudioObserver.prototype = {
startContext: function () {
emit(this, "start-context");
},
connectNode: function (source, dest) {
emit(this, "connect-node", source, dest);
},
connectParam: function (source, param) {
emit(this, "connect-param", source, param);
},
disconnectNode: function (source) {
emit(this, "disconnect-node", source);
},
createNode: function (source) {
emit(this, "create-node", source);
},
paramChange: function (node, param, val) {
emit(this, "param-change", node, param, val);
}
};
function unwrap (obj) {
return XPCNativeWrapper.unwrap(obj);
}
let NODE_CREATION_METHODS = [
"createBufferSource", "createMediaElementSource", "createMediaStreamSource",
"createMediaStreamDestination", "createScriptProcessor", "createAnalyser",
"createGain", "createDelay", "createBiquadFilter", "createWaveShaper",
"createPanner", "createConvolver", "createChannelSplitter", "createChannelMerger",
"createDynamicsCompressor", "createOscillator"
];

View File

@ -392,7 +392,6 @@ var DebuggerServer = {
this.addActors("resource://gre/modules/devtools/server/actors/webconsole.js");
this.registerModule("devtools/server/actors/inspector");
this.registerModule("devtools/server/actors/webgl");
this.registerModule("devtools/server/actors/webaudio");
this.registerModule("devtools/server/actors/stylesheets");
this.registerModule("devtools/server/actors/styleeditor");
this.registerModule("devtools/server/actors/storage");