mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1007920 - Handle non-primitive types in the AudioNode actor and in the web audio editor tool. r=vp
This commit is contained in:
parent
72a30cb72f
commit
7fc416d8ed
BIN
browser/devtools/webaudioeditor/test/440hz_sine.ogg
Normal file
BIN
browser/devtools/webaudioeditor/test/440hz_sine.ogg
Normal file
Binary file not shown.
@ -5,11 +5,14 @@ support-files =
|
||||
doc_simple-context.html
|
||||
doc_complex-context.html
|
||||
doc_simple-node-creation.html
|
||||
doc_buffer-and-array.html
|
||||
440hz_sine.ogg
|
||||
head.js
|
||||
|
||||
[browser_audionode-actor-get-set-param.js]
|
||||
[browser_audionode-actor-get-type.js]
|
||||
[browser_audionode-actor-get-params.js]
|
||||
[browser_audionode-actor-get-params-01.js]
|
||||
[browser_audionode-actor-get-params-02.js]
|
||||
[browser_audionode-actor-get-param-flags.js]
|
||||
[browser_audionode-actor-is-source.js]
|
||||
[browser_webaudio-actor-simple.js]
|
||||
@ -21,9 +24,11 @@ support-files =
|
||||
[browser_wa_graph-render-02.js]
|
||||
[browser_wa_graph-markers.js]
|
||||
|
||||
[browser_wa_inspector.js]
|
||||
[browser_wa_inspector-toggle.js]
|
||||
|
||||
[browser_wa_properties-view.js]
|
||||
# [browser_wa_properties-view-edit.js]
|
||||
# Disabled for too many intermittents bug 1010423
|
||||
|
||||
[browser_wa_inspector.js]
|
||||
[browser_wa_inspector-toggle.js]
|
||||
[browser_wa_properties-view-params.js]
|
||||
[browser_wa_properties-view-params-objects.js]
|
||||
|
@ -23,12 +23,7 @@ function spawnTest () {
|
||||
nodeTypes.forEach((type, i) => {
|
||||
let params = allNodeParams[i];
|
||||
params.forEach(({param, value, flags}) => {
|
||||
ok(~NODE_PROPERTIES[type].indexOf(param), "expected parameter for " + type);
|
||||
|
||||
// Skip over some properties that are undefined by default
|
||||
if (!/buffer|loop|smoothing|curve|cone/.test(param)) {
|
||||
ok(value != undefined, param + " is not undefined");
|
||||
}
|
||||
ok(param in NODE_DEFAULT_VALUES[type], "expected parameter for " + type);
|
||||
|
||||
ok(typeof flags === "object", type + " has a flags object");
|
||||
|
@ -0,0 +1,45 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that default properties are returned with the correct type
|
||||
* from the AudioNode actors.
|
||||
*/
|
||||
|
||||
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 allParams = yield Promise.all(nodes.map(node => node.getParams()));
|
||||
let types = [
|
||||
"AudioDestinationNode", "AudioBufferSourceNode", "ScriptProcessorNode",
|
||||
"AnalyserNode", "GainNode", "DelayNode", "BiquadFilterNode", "WaveShaperNode",
|
||||
"PannerNode", "ConvolverNode", "ChannelSplitterNode", "ChannelMergerNode",
|
||||
"DynamicsCompressorNode", "OscillatorNode"
|
||||
];
|
||||
|
||||
allParams.forEach((params, i) => {
|
||||
compare(params, NODE_DEFAULT_VALUES[types[i]], types[i]);
|
||||
});
|
||||
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
}
|
||||
|
||||
function compare (actual, expected, type) {
|
||||
actual.forEach(({ value, param }) => {
|
||||
value = getGripValue(value);
|
||||
if (typeof expected[param] === "function") {
|
||||
ok(expected[param](value), type + " has a passing value for " + param);
|
||||
}
|
||||
else {
|
||||
ise(value, expected[param], type + " has correct default value and type for " + param);
|
||||
}
|
||||
});
|
||||
|
||||
is(actual.length, Object.keys(expected).length,
|
||||
type + " has correct amount of properties.");
|
||||
}
|
@ -20,7 +20,8 @@ function spawnTest () {
|
||||
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");
|
||||
ok(type.type === "undefined",
|
||||
"AudioNode:getParam correctly returns a grip value for `undefined` for an invalid param.");
|
||||
|
||||
let resSuccess = yield oscNode.setParam("frequency", 220);
|
||||
let freq = yield oscNode.getParam("frequency");
|
||||
|
@ -31,7 +31,7 @@ function spawnTest() {
|
||||
let setAndCheck = setAndCheckVariable(panelWin, gVars);
|
||||
|
||||
checkVariableView(gVars, 0, {
|
||||
"type": "\"sine\"",
|
||||
"type": "sine",
|
||||
"frequency": 440,
|
||||
"detune": 0
|
||||
}, "default loaded string");
|
||||
@ -44,7 +44,7 @@ function spawnTest() {
|
||||
|
||||
click(panelWin, findGraphNode(panelWin, nodeIds[1]));
|
||||
yield once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET);
|
||||
yield setAndCheck(0, "type", "square", "\"square\"", "sets string as string");
|
||||
yield setAndCheck(0, "type", "square", "square", "sets string as string");
|
||||
|
||||
click(panelWin, findGraphNode(panelWin, nodeIds[2]));
|
||||
yield once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET);
|
||||
|
@ -0,0 +1,39 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that params view correctly displays non-primitive properties
|
||||
* like AudioBuffer and Float32Array in properties of AudioNodes.
|
||||
*/
|
||||
|
||||
function spawnTest() {
|
||||
let [target, debuggee, panel] = yield initWebAudioEditor(BUFFER_AND_ARRAY_URL);
|
||||
let { panelWin } = panel;
|
||||
let { gFront, $, $$, EVENTS, WebAudioInspectorView } = panelWin;
|
||||
let gVars = WebAudioInspectorView._propsView;
|
||||
|
||||
let started = once(gFront, "start-context");
|
||||
|
||||
reload(target);
|
||||
|
||||
let [actors] = yield Promise.all([
|
||||
getN(gFront, "create-node", 3),
|
||||
waitForGraphRendered(panelWin, 3, 2)
|
||||
]);
|
||||
let nodeIds = actors.map(actor => actor.actorID);
|
||||
|
||||
click(panelWin, findGraphNode(panelWin, nodeIds[2]));
|
||||
yield once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET);
|
||||
checkVariableView(gVars, 0, {
|
||||
"curve": "Float32Array"
|
||||
}, "WaveShaper's `curve` is listed as an `Float32Array`.");
|
||||
|
||||
click(panelWin, findGraphNode(panelWin, nodeIds[1]));
|
||||
yield once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET);
|
||||
checkVariableView(gVars, 0, {
|
||||
"buffer": "AudioBuffer"
|
||||
}, "AudioBufferSourceNode's `buffer` is listed as an `AudioBuffer`.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that params view correctly displays all properties for nodes
|
||||
* correctly, with default values and correct types.
|
||||
*/
|
||||
|
||||
function spawnTest() {
|
||||
let [target, debuggee, panel] = yield initWebAudioEditor(SIMPLE_NODES_URL);
|
||||
let { panelWin } = panel;
|
||||
let { gFront, $, $$, EVENTS, WebAudioInspectorView } = panelWin;
|
||||
let gVars = WebAudioInspectorView._propsView;
|
||||
|
||||
let started = once(gFront, "start-context");
|
||||
|
||||
reload(target);
|
||||
|
||||
let [actors] = yield Promise.all([
|
||||
getN(gFront, "create-node", 14),
|
||||
waitForGraphRendered(panelWin, 14, 0)
|
||||
]);
|
||||
let nodeIds = actors.map(actor => actor.actorID);
|
||||
let types = [
|
||||
"AudioDestinationNode", "AudioBufferSourceNode", "ScriptProcessorNode",
|
||||
"AnalyserNode", "GainNode", "DelayNode", "BiquadFilterNode", "WaveShaperNode",
|
||||
"PannerNode", "ConvolverNode", "ChannelSplitterNode", "ChannelMergerNode",
|
||||
"DynamicsCompressorNode", "OscillatorNode"
|
||||
];
|
||||
|
||||
for (let i = 0; i < types.length; i++) {
|
||||
click(panelWin, findGraphNode(panelWin, nodeIds[i]));
|
||||
yield once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET);
|
||||
checkVariableView(gVars, 0, NODE_DEFAULT_VALUES[types[i]], types[i]);
|
||||
}
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
<!-- 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 audioURL = "http://example.com/browser/browser/devtools/webaudioeditor/test/440hz_sine.ogg";
|
||||
|
||||
let ctx = new AudioContext();
|
||||
let bufferNode = ctx.createBufferSource();
|
||||
let shaperNode = ctx.createWaveShaper();
|
||||
shaperNode.curve = generateWaveShapingCurve();
|
||||
|
||||
let xhr = getBuffer(audioURL, () => {
|
||||
ctx.decodeAudioData(xhr.response, (buffer) => {
|
||||
bufferNode.buffer = buffer;
|
||||
bufferNode.connect(shaperNode);
|
||||
shaperNode.connect(ctx.destination);
|
||||
});
|
||||
});
|
||||
|
||||
function generateWaveShapingCurve() {
|
||||
let frames = 65536;
|
||||
let curve = new Float32Array(frames);
|
||||
let n = frames;
|
||||
let n2 = n / 2;
|
||||
|
||||
for (let i = 0; i < n; ++i) {
|
||||
let x = (i - n2) / n2;
|
||||
let y = Math.atan(5 * x) / (0.5 * Math.PI);
|
||||
}
|
||||
|
||||
return curve;
|
||||
}
|
||||
|
||||
function getBuffer (url, callback) {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", url, true);
|
||||
xhr.responseType = "arraybuffer";
|
||||
xhr.onload = callback;
|
||||
xhr.send();
|
||||
return xhr;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -24,6 +24,7 @@ const EXAMPLE_URL = "http://example.com/browser/browser/devtools/webaudioeditor/
|
||||
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";
|
||||
const BUFFER_AND_ARRAY_URL = EXAMPLE_URL + "doc_buffer-and-array.html";
|
||||
|
||||
// All tests are asynchronous.
|
||||
waitForExplicitFinish();
|
||||
@ -219,8 +220,23 @@ function checkVariableView (view, index, hash, description = "") {
|
||||
let aVar = scope.get(variable);
|
||||
is(aVar.target.querySelector(".name").getAttribute("value"), variable,
|
||||
"Correct property name for " + variable);
|
||||
is(aVar.target.querySelector(".value").getAttribute("value"), hash[variable],
|
||||
"Correct property value of " + hash[variable] + " for " + variable + " " + description);
|
||||
let value = aVar.target.querySelector(".value").getAttribute("value");
|
||||
|
||||
// Cast value with JSON.parse if possible;
|
||||
// will fail when displaying Object types like "ArrayBuffer"
|
||||
// and "Float32Array", but will match the original value.
|
||||
try {
|
||||
value = JSON.parse(value);
|
||||
}
|
||||
catch (e) {}
|
||||
if (typeof hash[variable] === "function") {
|
||||
ok(hash[variable](value),
|
||||
"Passing property value of " + value + " for " + variable + " " + description);
|
||||
}
|
||||
else {
|
||||
ise(value, hash[variable],
|
||||
"Correct property value of " + hash[variable] + " for " + variable + " " + description);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -292,23 +308,93 @@ function wait (n) {
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the primitive value of a grip's value, or the
|
||||
* original form that the string grip.type comes from.
|
||||
*/
|
||||
function getGripValue (value) {
|
||||
if (~["boolean", "string", "number"].indexOf(typeof value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
switch (value.type) {
|
||||
case "undefined": return undefined;
|
||||
case "Infinity": return Infinity;
|
||||
case "-Infinity": return -Infinity;
|
||||
case "NaN": return NaN;
|
||||
case "-0": return -0;
|
||||
case "null": return null;
|
||||
default: return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List of audio node properties to test against expectations of the AudioNode actor
|
||||
*/
|
||||
|
||||
const NODE_PROPERTIES = {
|
||||
"OscillatorNode": ["type", "frequency", "detune"],
|
||||
"GainNode": ["gain"],
|
||||
"DelayNode": ["delayTime"],
|
||||
"AudioBufferSourceNode": ["buffer", "playbackRate", "loop", "loopStart", "loopEnd"],
|
||||
"ScriptProcessorNode": ["bufferSize"],
|
||||
"PannerNode": ["panningModel", "distanceModel", "refDistance", "maxDistance", "rolloffFactor", "coneInnerAngle", "coneOuterAngle", "coneOuterGain"],
|
||||
"ConvolverNode": ["buffer", "normalize"],
|
||||
"DynamicsCompressorNode": ["threshold", "knee", "ratio", "reduction", "attack", "release"],
|
||||
"BiquadFilterNode": ["type", "frequency", "Q", "detune", "gain"],
|
||||
"WaveShaperNode": ["curve", "oversample"],
|
||||
"AnalyserNode": ["fftSize", "minDecibels", "maxDecibels", "smoothingTimeConstraint", "frequencyBinCount"],
|
||||
"AudioDestinationNode": [],
|
||||
"ChannelSplitterNode": [],
|
||||
"ChannelMergerNode": []
|
||||
const NODE_DEFAULT_VALUES = {
|
||||
"AudioDestinationNode": {},
|
||||
"AudioBufferSourceNode": {
|
||||
"playbackRate": 1,
|
||||
"loop": false,
|
||||
"loopStart": 0,
|
||||
"loopEnd": 0,
|
||||
"buffer": null
|
||||
},
|
||||
"ScriptProcessorNode": {
|
||||
"bufferSize": 4096
|
||||
},
|
||||
"AnalyserNode": {
|
||||
"fftSize": 2048,
|
||||
"minDecibels": -100,
|
||||
"maxDecibels": -30,
|
||||
"smoothingTimeConstant": 0.8,
|
||||
"frequencyBinCount": 1024
|
||||
},
|
||||
"GainNode": {
|
||||
"gain": 1
|
||||
},
|
||||
"DelayNode": {
|
||||
"delayTime": 0
|
||||
},
|
||||
"BiquadFilterNode": {
|
||||
"type": "lowpass",
|
||||
"frequency": 350,
|
||||
"Q": 1,
|
||||
"detune": 0,
|
||||
"gain": 0
|
||||
},
|
||||
"WaveShaperNode": {
|
||||
"curve": null,
|
||||
"oversample": "none"
|
||||
},
|
||||
"PannerNode": {
|
||||
"panningModel": "HRTF",
|
||||
"distanceModel": "inverse",
|
||||
"refDistance": 1,
|
||||
"maxDistance": 10000,
|
||||
"rolloffFactor": 1,
|
||||
"coneInnerAngle": 360,
|
||||
"coneOuterAngle": 360,
|
||||
"coneOuterGain": 0
|
||||
},
|
||||
"ConvolverNode": {
|
||||
"buffer": null,
|
||||
"normalize": true
|
||||
},
|
||||
"ChannelSplitterNode": {},
|
||||
"ChannelMergerNode": {},
|
||||
"DynamicsCompressorNode": {
|
||||
"threshold": -24,
|
||||
"knee": 30,
|
||||
"ratio": 12,
|
||||
"reduction": 0,
|
||||
"attack": 0.003000000026077032,
|
||||
"release": 0.25
|
||||
},
|
||||
"OscillatorNode": {
|
||||
"type": "sine",
|
||||
"frequency": 440,
|
||||
"detune": 0
|
||||
}
|
||||
};
|
||||
|
@ -11,6 +11,7 @@ const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {})
|
||||
const events = require("sdk/event/core");
|
||||
const protocol = require("devtools/server/protocol");
|
||||
const { CallWatcherActor, CallWatcherFront } = require("devtools/server/actors/call-watcher");
|
||||
const { ThreadActor } = require("devtools/server/actors/script");
|
||||
|
||||
const { on, once, off, emit } = events;
|
||||
const { method, Arg, Option, RetVal } = protocol;
|
||||
@ -98,7 +99,7 @@ const NODE_PROPERTIES = {
|
||||
"fftSize": {},
|
||||
"minDecibels": {},
|
||||
"maxDecibels": {},
|
||||
"smoothingTimeConstraint": {},
|
||||
"smoothingTimeConstant": {},
|
||||
"frequencyBinCount": { "readonly": true },
|
||||
},
|
||||
"AudioDestinationNode": {},
|
||||
@ -128,7 +129,7 @@ let AudioNodeActor = exports.AudioNodeActor = protocol.ActorClass({
|
||||
protocol.Actor.prototype.initialize.call(this, conn);
|
||||
this.node = unwrap(node);
|
||||
try {
|
||||
this.type = this.node.toString().match(/\[object (.*)\]$/)[1];
|
||||
this.type = getConstructorName(this.node);
|
||||
} catch (e) {
|
||||
this.type = "";
|
||||
}
|
||||
@ -188,11 +189,23 @@ let AudioNodeActor = exports.AudioNodeActor = protocol.ActorClass({
|
||||
* Name of the AudioParam to fetch.
|
||||
*/
|
||||
getParam: method(function (param) {
|
||||
// If property does not exist, just return "undefined"
|
||||
if (!this.node[param])
|
||||
return undefined;
|
||||
// Check to see if it's an AudioParam -- if so,
|
||||
// return the `value` property of the parameter.
|
||||
let value = isAudioParam(this.node, param) ? this.node[param].value : this.node[param];
|
||||
return value;
|
||||
|
||||
// Return the grip form of the value; at this time,
|
||||
// there shouldn't be any non-primitives at the moment, other than
|
||||
// AudioBuffer or Float32Array references and the like,
|
||||
// so this just formats the value to be displayed in the VariablesView,
|
||||
// without using real grips and managing via actor pools.
|
||||
let grip;
|
||||
try {
|
||||
grip = ThreadActor.prototype.createValueGrip(value);
|
||||
}
|
||||
catch (e) {
|
||||
grip = createObjectGrip(value);
|
||||
}
|
||||
return grip;
|
||||
}, {
|
||||
request: {
|
||||
param: Arg(0, "string")
|
||||
@ -499,7 +512,7 @@ WebAudioFront.NODE_ROUTING_METHODS = new Set(NODE_ROUTING_METHODS);
|
||||
* @return Boolean
|
||||
*/
|
||||
function isAudioParam (node, prop) {
|
||||
return /AudioParam/.test(node[prop].toString());
|
||||
return !!(node[prop] && /AudioParam/.test(node[prop].toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -516,6 +529,31 @@ function constructError (err) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an object and converts it's `toString()` form, like
|
||||
* "[object OscillatorNode]" or "[object Float32Array]"
|
||||
* to a string of just the constructor name, like "OscillatorNode",
|
||||
* or "Float32Array".
|
||||
*/
|
||||
function getConstructorName (obj) {
|
||||
return obj.toString().match(/\[object (.*)\]$/)[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a grip-like object to pass in renderable information
|
||||
* to the front-end for things like Float32Arrays, AudioBuffers,
|
||||
* without tracking them in an actor pool.
|
||||
*/
|
||||
function createObjectGrip (value) {
|
||||
return {
|
||||
type: "object",
|
||||
preview: {
|
||||
kind: "ObjectWithText",
|
||||
text: ""
|
||||
},
|
||||
class: getConstructorName(value)
|
||||
};
|
||||
}
|
||||
function unwrap (obj) {
|
||||
return XPCNativeWrapper.unwrap(obj);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user