зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to b2g-inbound on a CLOSED TREE
This commit is contained in:
Коммит
93952458ba
|
@ -38,7 +38,7 @@ let developerHUD = {
|
|||
_targets: new Map(),
|
||||
_frames: new Map(),
|
||||
_client: null,
|
||||
_conn: null,
|
||||
_webappsActor: null,
|
||||
_watchers: [],
|
||||
_logging: true,
|
||||
|
||||
|
@ -61,15 +61,12 @@ 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) => {
|
||||
|
||||
// FIXME(Bug 962577) see below.
|
||||
this._client.listTabs((res) => {
|
||||
this._webappsActor = res.webappsActor;
|
||||
|
||||
for (let w of this._watchers) {
|
||||
if (w.init) {
|
||||
|
@ -88,6 +85,8 @@ let developerHUD = {
|
|||
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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -23,7 +23,6 @@ DIRS += [
|
|||
'styleeditor',
|
||||
'styleinspector',
|
||||
'tilt',
|
||||
'webaudioeditor',
|
||||
'webconsole',
|
||||
]
|
||||
|
||||
|
|
|
@ -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 += [
|
||||
]
|
||||
|
|
@ -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]
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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); }
|
|
@ -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']
|
|
@ -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;
|
||||
}
|
||||
|
||||
// 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 = 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);
|
||||
let converter = new TextDecoder();
|
||||
data = JSON.parse(converter.decode(rawData));
|
||||
file.close();
|
||||
} catch (ex) {
|
||||
Cu.reportError("AppsUtils: Could not parse JSON: " +
|
||||
aPath + " " + ex + "\n" + ex.stack);
|
||||
deferred.resolve(null);
|
||||
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);
|
||||
});
|
||||
} catch (ex) {
|
||||
Cu.reportError("AppsUtils: Could not read from " +
|
||||
aPath + " : " + ex + "\n" + ex.stack);
|
||||
deferred.resolve(null);
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
// Returns the MD5 hash of a string.
|
||||
|
|
|
@ -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,38 +140,39 @@ 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 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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
entry.copyTo(dstDir, entry.leafName);
|
||||
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);
|
||||
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.path;
|
||||
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++) {
|
||||
try {
|
||||
manifest = yield AppsUtils.loadJSONAsync(
|
||||
Path.join(this.appsDir.path, aId, manifests[j]));
|
||||
|
||||
if (!manifest) {
|
||||
isPackage = false;
|
||||
} else {
|
||||
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;
|
||||
|
|
|
@ -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) {
|
||||
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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
|
@ -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"
|
||||
];
|
|
@ -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");
|
||||
|
|
Загрузка…
Ссылка в новой задаче