diff options
Diffstat (limited to 'devtools/client/webaudioeditor/test')
68 files changed, 3358 insertions, 0 deletions
diff --git a/devtools/client/webaudioeditor/test/.eslintrc.js b/devtools/client/webaudioeditor/test/.eslintrc.js new file mode 100644 index 000000000..8d15a76d9 --- /dev/null +++ b/devtools/client/webaudioeditor/test/.eslintrc.js @@ -0,0 +1,6 @@ +"use strict"; + +module.exports = { + // Extend from the shared list of defined globals for mochitests. + "extends": "../../../.eslintrc.mochitests.js" +}; diff --git a/devtools/client/webaudioeditor/test/440hz_sine.ogg b/devtools/client/webaudioeditor/test/440hz_sine.ogg Binary files differnew file mode 100644 index 000000000..bd84564e2 --- /dev/null +++ b/devtools/client/webaudioeditor/test/440hz_sine.ogg diff --git a/devtools/client/webaudioeditor/test/browser.ini b/devtools/client/webaudioeditor/test/browser.ini new file mode 100644 index 000000000..cad17a530 --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser.ini @@ -0,0 +1,77 @@ +[DEFAULT] +tags = devtools +subsuite = devtools +support-files = + doc_simple-context.html + doc_complex-context.html + doc_simple-node-creation.html + doc_buffer-and-array.html + doc_media-node-creation.html + doc_destroy-nodes.html + doc_connect-param.html + doc_connect-multi-param.html + doc_iframe-context.html + doc_automation.html + doc_bug_1112378.html + doc_bug_1125817.html + doc_bug_1130901.html + doc_bug_1141261.html + 440hz_sine.ogg + head.js + +[browser_audionode-actor-get-param-flags.js] +[browser_audionode-actor-get-params-01.js] +[browser_audionode-actor-get-params-02.js] +[browser_audionode-actor-get-set-param.js] +[browser_audionode-actor-type.js] +[browser_audionode-actor-source.js] +[browser_audionode-actor-bypass.js] +[browser_audionode-actor-bypassable.js] +[browser_audionode-actor-connectnode-disconnect.js] +[browser_audionode-actor-connectparam.js] +skip-if = true # bug 1092571 +# [browser_audionode-actor-add-automation-event.js] bug 1134036 +# [browser_audionode-actor-get-automation-data-01.js] bug 1134036 +# [browser_audionode-actor-get-automation-data-02.js] bug 1134036 +# [browser_audionode-actor-get-automation-data-03.js] bug 1134036 +[browser_callwatcher-01.js] +[browser_callwatcher-02.js] +[browser_webaudio-actor-simple.js] +[browser_webaudio-actor-destroy-node.js] +[browser_webaudio-actor-connect-param.js] +# [browser_webaudio-actor-automation-event.js] bug 1134036 + +# [browser_wa_automation-view-01.js] bug 1134036 +# [browser_wa_automation-view-02.js] bug 1134036 +[browser_wa_controller-01.js] +[browser_wa_destroy-node-01.js] +[browser_wa_first-run.js] +[browser_wa_graph-click.js] +[browser_wa_graph-markers.js] +[browser_wa_graph-render-01.js] +[browser_wa_graph-render-02.js] +[browser_wa_graph-render-03.js] +[browser_wa_graph-render-04.js] +[browser_wa_graph-render-05.js] +skip-if = true # bug 1092571 +[browser_wa_graph-render-06.js] +[browser_wa_graph-selected.js] +[browser_wa_graph-zoom.js] +[browser_wa_inspector.js] +[browser_wa_inspector-toggle.js] +[browser_wa_inspector-width.js] +[browser_wa_inspector-bypass-01.js] +[browser_wa_navigate.js] +[browser_wa_properties-view.js] +[browser_wa_properties-view-edit-01.js] +skip-if = true # bug 1010423 +[browser_wa_properties-view-edit-02.js] +skip-if = true # bug 1010423 +[browser_wa_properties-view-media-nodes.js] +skip-if = os == 'mac' # bug 1216542 +[browser_wa_properties-view-params.js] +[browser_wa_properties-view-params-objects.js] +[browser_wa_reset-01.js] +[browser_wa_reset-02.js] +[browser_wa_reset-03.js] +[browser_wa_reset-04.js] diff --git a/devtools/client/webaudioeditor/test/browser_audionode-actor-add-automation-event.js b/devtools/client/webaudioeditor/test/browser_audionode-actor-add-automation-event.js new file mode 100644 index 000000000..4b451c826 --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_audionode-actor-add-automation-event.js @@ -0,0 +1,52 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test AudioNode#addAutomationEvent(); + */ + +add_task(function* () { + let { target, front } = yield initBackend(SIMPLE_CONTEXT_URL); + let [_, [destNode, oscNode, gainNode]] = yield Promise.all([ + front.setup({ reload: true }), + get3(front, "create-node") + ]); + let count = 0; + let counter = () => count++; + front.on("automation-event", counter); + + let t0 = 0, t1 = 0.1, t2 = 0.2, t3 = 0.3, t4 = 0.4, t5 = 0.6, t6 = 0.7, t7 = 1; + let curve = [-1, 0, 1]; + yield oscNode.addAutomationEvent("frequency", "setValueAtTime", [0.2, t0]); + yield oscNode.addAutomationEvent("frequency", "setValueAtTime", [0.3, t1]); + yield oscNode.addAutomationEvent("frequency", "setValueAtTime", [0.4, t2]); + yield oscNode.addAutomationEvent("frequency", "linearRampToValueAtTime", [1, t3]); + yield oscNode.addAutomationEvent("frequency", "linearRampToValueAtTime", [0.15, t4]); + yield oscNode.addAutomationEvent("frequency", "exponentialRampToValueAtTime", [0.75, t5]); + yield oscNode.addAutomationEvent("frequency", "exponentialRampToValueAtTime", [0.5, t6]); + yield oscNode.addAutomationEvent("frequency", "setValueCurveAtTime", [curve, t7, t7 - t6]); + yield oscNode.addAutomationEvent("frequency", "setTargetAtTime", [20, 2, 5]); + + ok(true, "successfully set automation events for valid automation events"); + + try { + yield oscNode.addAutomationEvent("frequency", "notAMethod", 20, 2, 5); + ok(false, "non-automation methods should not be successful"); + } catch (e) { + ok(/invalid/.test(e.message), "AudioNode:addAutomationEvent fails for invalid automation methods"); + } + + try { + yield oscNode.addAutomationEvent("invalidparam", "setValueAtTime", 0.2, t0); + ok(false, "automating non-AudioParams should not be successful"); + } catch (e) { + ok(/invalid/.test(e.message), "AudioNode:addAutomationEvent fails for a non AudioParam"); + } + + front.off("automation-event", counter); + + is(count, 9, + "when calling `addAutomationEvent`, the WebAudioActor should still fire `automation-event`."); + + yield removeTab(target.tab); +}); diff --git a/devtools/client/webaudioeditor/test/browser_audionode-actor-bypass.js b/devtools/client/webaudioeditor/test/browser_audionode-actor-bypass.js new file mode 100644 index 000000000..e9fc7f321 --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_audionode-actor-bypass.js @@ -0,0 +1,36 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test AudioNode#bypass(), AudioNode#isBypassed() + */ + +add_task(function* () { + let { target, front } = yield initBackend(SIMPLE_CONTEXT_URL); + let [_, [destNode, oscNode, gainNode]] = yield Promise.all([ + front.setup({ reload: true }), + get3(front, "create-node") + ]); + + is((yield gainNode.isBypassed()), false, "Nodes start off unbypassed."); + + info("Calling node#bypass(true)"); + let isBypassed = yield gainNode.bypass(true); + + is(isBypassed, true, "node.bypass(true) resolves to true"); + is((yield gainNode.isBypassed()), true, "Node is now bypassed."); + + info("Calling node#bypass(false)"); + isBypassed = yield gainNode.bypass(false); + + is(isBypassed, false, "node.bypass(false) resolves to false"); + is((yield gainNode.isBypassed()), false, "Node back to being unbypassed."); + + info("Calling node#bypass(true) on unbypassable node"); + isBypassed = yield destNode.bypass(true); + + is(isBypassed, false, "node.bypass(true) resolves to false for unbypassable node"); + is((yield gainNode.isBypassed()), false, "Unbypassable node is unaffect"); + + yield removeTab(target.tab); +}); diff --git a/devtools/client/webaudioeditor/test/browser_audionode-actor-bypassable.js b/devtools/client/webaudioeditor/test/browser_audionode-actor-bypassable.js new file mode 100644 index 000000000..2f093f2e4 --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_audionode-actor-bypassable.js @@ -0,0 +1,38 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test AudioNode#bypassable + */ + +add_task(function* () { + let { target, front } = yield initBackend(SIMPLE_NODES_URL); + let [_, nodes] = yield Promise.all([ + front.setup({ reload: true }), + getN(front, "create-node", 14) + ]); + + let actualBypassability = nodes.map(node => node.bypassable); + let expectedBypassability = [ + false, // AudioDestinationNode + true, // AudioBufferSourceNode + true, // ScriptProcessorNode + true, // AnalyserNode + true, // GainNode + true, // DelayNode + true, // BiquadFilterNode + true, // WaveShaperNode + true, // PannerNode + true, // ConvolverNode + false, // ChannelSplitterNode + false, // ChannelMergerNode + true, // DynamicsCompressNode + true, // OscillatorNode + ]; + + expectedBypassability.forEach((bypassable, i) => { + is(actualBypassability[i], bypassable, `${nodes[i].type} has correct ".bypassable" status`); + }); + + yield removeTab(target.tab); +}); diff --git a/devtools/client/webaudioeditor/test/browser_audionode-actor-connectnode-disconnect.js b/devtools/client/webaudioeditor/test/browser_audionode-actor-connectnode-disconnect.js new file mode 100644 index 000000000..dcd1689f5 --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_audionode-actor-connectnode-disconnect.js @@ -0,0 +1,39 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that AudioNodeActor#connectNode() and AudioNodeActor#disconnect() work. + * Uses the editor front as the actors do not retain connect state. + */ + +add_task(function* () { + let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); + let { panelWin } = panel; + let { gFront, $, $$, EVENTS, gAudioNodes } = panelWin; + + let events = Promise.all([ + get3(gFront, "create-node"), + waitForGraphRendered(panelWin, 3, 2) + ]); + reload(target); + let [actors] = yield events; + let [dest, osc, gain] = actors; + + info("Disconnecting oscillator..."); + osc.disconnect(); + yield Promise.all([ + waitForGraphRendered(panelWin, 3, 1), + once(gAudioNodes, "disconnect") + ]); + ok(true, "Oscillator disconnected, event emitted."); + + info("Reconnecting oscillator..."); + osc.connectNode(gain); + yield Promise.all([ + waitForGraphRendered(panelWin, 3, 2), + once(gAudioNodes, "connect") + ]); + ok(true, "Oscillator reconnected."); + + yield teardown(target); +}); diff --git a/devtools/client/webaudioeditor/test/browser_audionode-actor-connectparam.js b/devtools/client/webaudioeditor/test/browser_audionode-actor-connectparam.js new file mode 100644 index 000000000..454f0d563 --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_audionode-actor-connectparam.js @@ -0,0 +1,32 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that AudioNodeActor#connectParam() work. + * Uses the editor front as the actors do not retain connect state. + */ + +add_task(function* () { + let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); + let { panelWin } = panel; + let { gFront, $, $$, EVENTS, gAudioNodes } = panelWin; + + let events = Promise.all([ + get3(gFront, "create-node"), + waitForGraphRendered(panelWin, 3, 2) + ]); + reload(target); + let [actors] = yield events; + let [dest, osc, gain] = actors; + + yield osc.disconnect(); + + osc.connectParam(gain, "gain"); + yield Promise.all([ + waitForGraphRendered(panelWin, 3, 1, 1), + once(gAudioNodes, "connect") + ]); + ok(true, "Oscillator connect to Gain's Gain AudioParam, event emitted."); + + yield teardown(target); +}); diff --git a/devtools/client/webaudioeditor/test/browser_audionode-actor-get-automation-data-01.js b/devtools/client/webaudioeditor/test/browser_audionode-actor-get-automation-data-01.js new file mode 100644 index 000000000..640b3e351 --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_audionode-actor-get-automation-data-01.js @@ -0,0 +1,53 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test AudioNode#addAutomationEvent() checking automation values, also using + * a curve as the last event to check duration spread. + */ + +add_task(function* () { + let { target, front } = yield initBackend(SIMPLE_CONTEXT_URL); + let [_, [destNode, oscNode, gainNode]] = yield Promise.all([ + front.setup({ reload: true }), + get3(front, "create-node") + ]); + + let t0 = 0, t1 = 0.1, t2 = 0.2, t3 = 0.3, t4 = 0.4, t5 = 0.6, t6 = 0.7, t7 = 1; + let curve = [-1, 0, 1]; + yield oscNode.addAutomationEvent("frequency", "setValueAtTime", [0.2, t0]); + yield oscNode.addAutomationEvent("frequency", "setValueAtTime", [0.3, t1]); + yield oscNode.addAutomationEvent("frequency", "setValueAtTime", [0.4, t2]); + yield oscNode.addAutomationEvent("frequency", "linearRampToValueAtTime", [1, t3]); + yield oscNode.addAutomationEvent("frequency", "linearRampToValueAtTime", [0.15, t4]); + yield oscNode.addAutomationEvent("frequency", "exponentialRampToValueAtTime", [0.75, t5]); + yield oscNode.addAutomationEvent("frequency", "exponentialRampToValueAtTime", [0.05, t6]); + // End with a curve here so we can get proper results on the last event (which takes into account + // duration) + yield oscNode.addAutomationEvent("frequency", "setValueCurveAtTime", [curve, t6, t7 - t6]); + + let { events, values } = yield oscNode.getAutomationData("frequency"); + + is(events.length, 8, "8 recorded events returned."); + is(values.length, 2000, "2000 value points returned."); + + checkAutomationValue(values, 0.05, 0.2); + checkAutomationValue(values, 0.1, 0.3); + checkAutomationValue(values, 0.15, 0.3); + checkAutomationValue(values, 0.2, 0.4); + checkAutomationValue(values, 0.25, 0.7); + checkAutomationValue(values, 0.3, 1); + checkAutomationValue(values, 0.35, 0.575); + checkAutomationValue(values, 0.4, 0.15); + checkAutomationValue(values, 0.45, 0.15 * Math.pow(0.75 / 0.15, 0.05 / 0.2)); + checkAutomationValue(values, 0.5, 0.15 * Math.pow(0.75 / 0.15, 0.5)); + checkAutomationValue(values, 0.55, 0.15 * Math.pow(0.75 / 0.15, 0.15 / 0.2)); + checkAutomationValue(values, 0.6, 0.75); + checkAutomationValue(values, 0.65, 0.75 * Math.pow(0.05 / 0.75, 0.5)); + checkAutomationValue(values, 0.705, -1); // Increase this time a bit to prevent off by the previous exponential amount + checkAutomationValue(values, 0.8, 0); + checkAutomationValue(values, 0.9, 1); + checkAutomationValue(values, 1, 1); + + yield removeTab(target.tab); +}); diff --git a/devtools/client/webaudioeditor/test/browser_audionode-actor-get-automation-data-02.js b/devtools/client/webaudioeditor/test/browser_audionode-actor-get-automation-data-02.js new file mode 100644 index 000000000..f24fb1905 --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_audionode-actor-get-automation-data-02.js @@ -0,0 +1,42 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test AudioNode#addAutomationEvent() when automation series ends with + * `setTargetAtTime`, which approaches its target to infinity. + */ + +add_task(function* () { + let { target, front } = yield initBackend(SIMPLE_CONTEXT_URL); + let [_, [destNode, oscNode, gainNode]] = yield Promise.all([ + front.setup({ reload: true }), + get3(front, "create-node") + ]); + + yield oscNode.addAutomationEvent("frequency", "setValueAtTime", [300, 0.1]); + yield oscNode.addAutomationEvent("frequency", "linearRampToValueAtTime", [500, 0.4]); + yield oscNode.addAutomationEvent("frequency", "exponentialRampToValueAtTime", [200, 0.6]); + // End with a setTargetAtTime event, as the target approaches infinity, which will + // give us more points to render than the default 2000 + yield oscNode.addAutomationEvent("frequency", "setTargetAtTime", [1000, 2, 0.5]); + + var { events, values } = yield oscNode.getAutomationData("frequency"); + + is(events.length, 4, "4 recorded events returned."); + is(values.length, 4000, "4000 value points returned when ending with exponentiall approaching automator."); + + checkAutomationValue(values, 2.01, 215.055); + checkAutomationValue(values, 2.1, 345.930); + checkAutomationValue(values, 3, 891.601); + checkAutomationValue(values, 5, 998.01); + + // Refetch the automation data to ensure it recalculates correctly (bug 1118071) + var { events, values } = yield oscNode.getAutomationData("frequency"); + + checkAutomationValue(values, 2.01, 215.055); + checkAutomationValue(values, 2.1, 345.930); + checkAutomationValue(values, 3, 891.601); + checkAutomationValue(values, 5, 998.01); + + yield removeTab(target.tab); +}); diff --git a/devtools/client/webaudioeditor/test/browser_audionode-actor-get-automation-data-03.js b/devtools/client/webaudioeditor/test/browser_audionode-actor-get-automation-data-03.js new file mode 100644 index 000000000..7de509ccd --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_audionode-actor-get-automation-data-03.js @@ -0,0 +1,34 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that `cancelScheduledEvents` clears out events on and after + * its argument. + */ + +add_task(function* () { + let { target, front } = yield initBackend(SIMPLE_CONTEXT_URL); + let [_, [destNode, oscNode, gainNode]] = yield Promise.all([ + front.setup({ reload: true }), + get3(front, "create-node") + ]); + + yield oscNode.addAutomationEvent("frequency", "setValueAtTime", [300, 0]); + yield oscNode.addAutomationEvent("frequency", "linearRampToValueAtTime", [500, 0.9]); + yield oscNode.addAutomationEvent("frequency", "setValueAtTime", [700, 1]); + yield oscNode.addAutomationEvent("frequency", "exponentialRampToValueAtTime", [1000, 2]); + yield oscNode.addAutomationEvent("frequency", "cancelScheduledValues", [1]); + + var { events, values } = yield oscNode.getAutomationData("frequency"); + + is(events.length, 2, "2 recorded events returned."); + is(values.length, 2000, "2000 value points returned"); + + checkAutomationValue(values, 0, 300); + checkAutomationValue(values, 0.5, 411.15); + checkAutomationValue(values, 0.9, 499.9); + checkAutomationValue(values, 1, 499.9); + checkAutomationValue(values, 2, 499.9); + + yield removeTab(target.tab); +}); diff --git a/devtools/client/webaudioeditor/test/browser_audionode-actor-get-param-flags.js b/devtools/client/webaudioeditor/test/browser_audionode-actor-get-param-flags.js new file mode 100644 index 000000000..17f7bf846 --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_audionode-actor-get-param-flags.js @@ -0,0 +1,47 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test AudioNode#getParamFlags() + */ + +add_task(function* () { + let { target, front } = yield initBackend(SIMPLE_NODES_URL); + let [_, nodes] = yield Promise.all([ + front.setup({ reload: true }), + getN(front, "create-node", 15) + ]); + + let allNodeParams = yield Promise.all(nodes.map(node => node.getParams())); + let nodeTypes = [ + "AudioDestinationNode", + "AudioBufferSourceNode", "ScriptProcessorNode", "AnalyserNode", "GainNode", + "DelayNode", "BiquadFilterNode", "WaveShaperNode", "PannerNode", "ConvolverNode", + "ChannelSplitterNode", "ChannelMergerNode", "DynamicsCompressorNode", "OscillatorNode", + "StereoPannerNode" + ]; + + // For some reason nodeTypes.forEach and params.forEach fail here so we use + // simple for loops. + for (let i = 0; i < nodeTypes.length; i++) { + let type = nodeTypes[i]; + let params = allNodeParams[i]; + + for (let {param, value, flags} of params) { + let testFlags = yield nodes[i].getParamFlags(param); + ok(typeof testFlags === "object", type + " has flags from #getParamFlags(" + param + ")"); + + if (param === "buffer") { + is(flags.Buffer, true, "`buffer` params have Buffer flag"); + } + else if (param === "bufferSize" || param === "frequencyBinCount") { + is(flags.readonly, true, param + " is readonly"); + } + else if (param === "curve") { + is(flags["Float32Array"], true, "`curve` param has Float32Array flag"); + } + } + } + + yield removeTab(target.tab); +}); diff --git a/devtools/client/webaudioeditor/test/browser_audionode-actor-get-params-01.js b/devtools/client/webaudioeditor/test/browser_audionode-actor-get-params-01.js new file mode 100644 index 000000000..6cfabbe85 --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_audionode-actor-get-params-01.js @@ -0,0 +1,49 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test AudioNode#getParams() + */ + +add_task(function* () { + let { target, front } = yield initBackend(SIMPLE_NODES_URL); + let [_, nodes] = yield Promise.all([ + front.setup({ reload: true }), + getN(front, "create-node", 15) + ]); + + yield loadFrameScripts(); + + let allNodeParams = yield Promise.all(nodes.map(node => node.getParams())); + let nodeTypes = [ + "AudioDestinationNode", + "AudioBufferSourceNode", "ScriptProcessorNode", "AnalyserNode", "GainNode", + "DelayNode", "BiquadFilterNode", "WaveShaperNode", "PannerNode", "ConvolverNode", + "ChannelSplitterNode", "ChannelMergerNode", "DynamicsCompressorNode", "OscillatorNode", + "StereoPannerNode" + ]; + + let defaults = yield Promise.all(nodeTypes.map(type => nodeDefaultValues(type))); + + nodeTypes.map((type, i) => { + let params = allNodeParams[i]; + + params.forEach(({param, value, flags}) => { + ok(param in defaults[i], "expected parameter for " + type); + + ok(typeof flags === "object", type + " has a flags object"); + + if (param === "buffer") { + is(flags.Buffer, true, "`buffer` params have Buffer flag"); + } + else if (param === "bufferSize" || param === "frequencyBinCount") { + is(flags.readonly, true, param + " is readonly"); + } + else if (param === "curve") { + is(flags["Float32Array"], true, "`curve` param has Float32Array flag"); + } + }); + }); + + yield removeTab(target.tab); +}); diff --git a/devtools/client/webaudioeditor/test/browser_audionode-actor-get-params-02.js b/devtools/client/webaudioeditor/test/browser_audionode-actor-get-params-02.js new file mode 100644 index 000000000..8d60a5e4d --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_audionode-actor-get-params-02.js @@ -0,0 +1,52 @@ +/* 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. + */ + +add_task(function* () { + let { target, front } = yield initBackend(SIMPLE_NODES_URL); + let [_, nodes] = yield Promise.all([ + front.setup({ reload: true }), + getN(front, "create-node", 15) + ]); + + yield loadFrameScripts(); + + 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", "StereoPannerNode" + ]; + + let defaults = yield Promise.all(types.map(type => nodeDefaultValues(type))); + + info(JSON.stringify(defaults)); + + allParams.forEach((params, i) => { + compare(params, defaults[i], types[i]); + }); + + yield removeTab(target.tab); +}); + +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 { + is(value, expected[param], type + " has correct default value and type for " + param); + } + }); + + info(Object.keys(expected).join(",") + " - " + JSON.stringify(expected)); + + is(actual.length, Object.keys(expected).length, + type + " has correct amount of properties."); +} diff --git a/devtools/client/webaudioeditor/test/browser_audionode-actor-get-set-param.js b/devtools/client/webaudioeditor/test/browser_audionode-actor-get-set-param.js new file mode 100644 index 000000000..0d4c7c5c7 --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_audionode-actor-get-set-param.js @@ -0,0 +1,47 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test AudioNode#getParam() / AudioNode#setParam() + */ + +add_task(function* () { + let { target, 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); + is(freq, 440, "AudioNode:getParam correctly fetches AudioParam"); + + let type = yield oscNode.getParam("type"); + is(type, "sine", "AudioNode:getParam correctly fetches non-AudioParam"); + + type = yield oscNode.getParam("not-a-valid-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); + freq = yield oscNode.getParam("frequency"); + is(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"); + type = yield oscNode.getParam("type"); + is(type, "square", "AudioNode:setParam correctly sets a `string` non-AudioParam"); + is(resSuccess, undefined, "AudioNode:setParam returns undefined for correctly set AudioParam"); + + try { + yield oscNode.setParam("frequency", "hello"); + 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"); + is(freq, 220, "AudioNode:setParam does not modify value when an error occurs"); + } + + yield removeTab(target.tab); +}); diff --git a/devtools/client/webaudioeditor/test/browser_audionode-actor-source.js b/devtools/client/webaudioeditor/test/browser_audionode-actor-source.js new file mode 100644 index 000000000..203e88012 --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_audionode-actor-source.js @@ -0,0 +1,27 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test AudioNode#source + */ + +add_task(function* () { + let { target, front } = yield initBackend(SIMPLE_NODES_URL); + let [_, nodes] = yield Promise.all([ + front.setup({ reload: true }), + getN(front, "create-node", 14) + ]); + + let actualTypes = nodes.map(node => node.type); + let isSourceResult = nodes.map(node => node.source); + + actualTypes.forEach((type, i) => { + let shouldBeSource = type === "AudioBufferSourceNode" || type === "OscillatorNode"; + if (shouldBeSource) + is(isSourceResult[i], true, type + "'s `source` is `true`"); + else + is(isSourceResult[i], false, type + "'s `source` is `false`"); + }); + + yield removeTab(target.tab); +}); diff --git a/devtools/client/webaudioeditor/test/browser_audionode-actor-type.js b/devtools/client/webaudioeditor/test/browser_audionode-actor-type.js new file mode 100644 index 000000000..58712067e --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_audionode-actor-type.js @@ -0,0 +1,28 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test AudioNode#type + */ + +add_task(function* () { + let { target, front } = yield initBackend(SIMPLE_NODES_URL); + let [_, nodes] = yield Promise.all([ + front.setup({ reload: true }), + getN(front, "create-node", 14) + ]); + + let actualTypes = nodes.map(node => node.type); + 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); +}); diff --git a/devtools/client/webaudioeditor/test/browser_callwatcher-01.js b/devtools/client/webaudioeditor/test/browser_callwatcher-01.js new file mode 100644 index 000000000..11c3ad11c --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_callwatcher-01.js @@ -0,0 +1,26 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Bug 1130901 + * Tests to ensure that calling call/apply on methods wrapped + * via CallWatcher do not throw a security permissions error: + * "Error: Permission denied to access property 'call'" + */ + +const BUG_1130901_URL = EXAMPLE_URL + "doc_bug_1130901.html"; + +add_task(function* () { + let { target, panel } = yield initWebAudioEditor(BUG_1130901_URL); + let { panelWin } = panel; + let { gFront, $, $$, EVENTS, gAudioNodes } = panelWin; + + let rendered = waitForGraphRendered(panelWin, 3, 0); + reload(target); + yield rendered; + + ok(true, "Successfully created a node from AudioContext via `call`."); + ok(true, "Successfully created a node from AudioContext via `apply`."); + + yield teardown(target); +}); diff --git a/devtools/client/webaudioeditor/test/browser_callwatcher-02.js b/devtools/client/webaudioeditor/test/browser_callwatcher-02.js new file mode 100644 index 000000000..f901efdcb --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_callwatcher-02.js @@ -0,0 +1,44 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Bug 1112378 + * Tests to ensure that errors called on wrapped functions via call-watcher + * correctly looks like the error comes from the content, not from within the devtools. + */ + +const BUG_1112378_URL = EXAMPLE_URL + "doc_bug_1112378.html"; + +add_task(function* () { + let { target, panel } = yield initWebAudioEditor(BUG_1112378_URL); + let { panelWin } = panel; + let { gFront, $, $$, EVENTS, gAudioNodes } = panelWin; + + loadFrameScripts(); + + let rendered = waitForGraphRendered(panelWin, 2, 0); + reload(target); + yield rendered; + + let error = yield evalInDebuggee("throwError()"); + is(error.lineNumber, 21, "error has correct lineNumber"); + is(error.columnNumber, 11, "error has correct columnNumber"); + is(error.name, "TypeError", "error has correct name"); + is(error.message, "Argument 1 is not valid for any of the 2-argument overloads of AudioNode.connect.", "error has correct message"); + is(error.stringified, "TypeError: Argument 1 is not valid for any of the 2-argument overloads of AudioNode.connect.", "error is stringified correctly"); + is(error.instanceof, true, "error is correctly an instanceof TypeError"); + is(error.fileName, "http://example.com/browser/devtools/client/webaudioeditor/test/doc_bug_1112378.html", "error has correct fileName"); + + error = yield evalInDebuggee("throwDOMException()"); + is(error.lineNumber, 37, "exception has correct lineNumber"); + is(error.columnNumber, 0, "exception has correct columnNumber"); + is(error.code, 9, "exception has correct code"); + is(error.result, 2152923145, "exception has correct result"); + is(error.name, "NotSupportedError", "exception has correct name"); + is(error.message, "Operation is not supported", "exception has correct message"); + is(error.stringified, "NotSupportedError: Operation is not supported", "exception is stringified correctly"); + is(error.instanceof, true, "exception is correctly an instance of DOMException"); + is(error.filename, "http://example.com/browser/devtools/client/webaudioeditor/test/doc_bug_1112378.html", "exception has correct filename"); + + yield teardown(target); +}); diff --git a/devtools/client/webaudioeditor/test/browser_wa_automation-view-01.js b/devtools/client/webaudioeditor/test/browser_wa_automation-view-01.js new file mode 100644 index 000000000..1e0034b5b --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_wa_automation-view-01.js @@ -0,0 +1,57 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that automation view shows the correct view depending on if events + * or params exist. + */ + +add_task(function* () { + let { target, panel } = yield initWebAudioEditor(AUTOMATION_URL); + let { panelWin } = panel; + let { gFront, $, $$, EVENTS } = panelWin; + + let started = once(gFront, "start-context"); + + let events = Promise.all([ + get3(gFront, "create-node"), + waitForGraphRendered(panelWin, 3, 2) + ]); + reload(target); + let [actors] = yield events; + let nodeIds = actors.map(actor => actor.actorID); + + let $tabbox = $("#web-audio-editor-tabs"); + + // Oscillator node + click(panelWin, findGraphNode(panelWin, nodeIds[1])); + yield waitForInspectorRender(panelWin, EVENTS); + $tabbox.selectedIndex = 1; + + ok(isVisible($("#automation-graph-container")), "graph container should be visible"); + ok(isVisible($("#automation-content")), "automation content should be visible"); + ok(!isVisible($("#automation-no-events")), "no-events panel should not be visible"); + ok(!isVisible($("#automation-empty")), "empty panel should not be visible"); + + // Gain node + click(panelWin, findGraphNode(panelWin, nodeIds[2])); + yield waitForInspectorRender(panelWin, EVENTS); + $tabbox.selectedIndex = 1; + + ok(!isVisible($("#automation-graph-container")), "graph container should not be visible"); + ok(isVisible($("#automation-content")), "automation content should be visible"); + ok(isVisible($("#automation-no-events")), "no-events panel should be visible"); + ok(!isVisible($("#automation-empty")), "empty panel should not be visible"); + + // destination node + click(panelWin, findGraphNode(panelWin, nodeIds[0])); + yield waitForInspectorRender(panelWin, EVENTS); + $tabbox.selectedIndex = 1; + + ok(!isVisible($("#automation-graph-container")), "graph container should not be visible"); + ok(!isVisible($("#automation-content")), "automation content should not be visible"); + ok(!isVisible($("#automation-no-events")), "no-events panel should not be visible"); + ok(isVisible($("#automation-empty")), "empty panel should be visible"); + + yield teardown(target); +}); diff --git a/devtools/client/webaudioeditor/test/browser_wa_automation-view-02.js b/devtools/client/webaudioeditor/test/browser_wa_automation-view-02.js new file mode 100644 index 000000000..a0f5f5a04 --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_wa_automation-view-02.js @@ -0,0 +1,55 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that automation view selects the first parameter by default and + * switching between AudioParam rerenders the graph. + */ + +add_task(function* () { + let { target, panel } = yield initWebAudioEditor(AUTOMATION_URL); + let { panelWin } = panel; + let { gFront, $, $$, EVENTS, AutomationView } = panelWin; + + let started = once(gFront, "start-context"); + + let events = Promise.all([ + get3(gFront, "create-node"), + waitForGraphRendered(panelWin, 3, 2) + ]); + reload(target); + let [actors] = yield events; + let nodeIds = actors.map(actor => actor.actorID); + + // Oscillator node + click(panelWin, findGraphNode(panelWin, nodeIds[1])); + yield waitForInspectorRender(panelWin, EVENTS); + click(panelWin, $("#automation-tab")); + + ok(AutomationView._selectedParamName, "frequency", + "AutomatioView is set on 'frequency'"); + ok($(".automation-param-button[data-param='frequency']").getAttribute("selected"), + "frequency param should be selected on load"); + ok(!$(".automation-param-button[data-param='detune']").getAttribute("selected"), + "detune param should not be selected on load"); + ok(isVisible($("#automation-content")), "automation content should be visible"); + ok(isVisible($("#automation-graph-container")), "graph container should be visible"); + ok(!isVisible($("#automation-no-events")), "no-events panel should not be visible"); + + click(panelWin, $(".automation-param-button[data-param='detune']")); + yield once(panelWin, EVENTS.UI_AUTOMATION_TAB_RENDERED); + + ok(true, "automation tab rerendered"); + + ok(AutomationView._selectedParamName, "detune", + "AutomatioView is set on 'detune'"); + ok(!$(".automation-param-button[data-param='frequency']").getAttribute("selected"), + "frequency param should not be selected after clicking detune"); + ok($(".automation-param-button[data-param='detune']").getAttribute("selected"), + "detune param should be selected after clicking detune"); + ok(isVisible($("#automation-content")), "automation content should be visible"); + ok(!isVisible($("#automation-graph-container")), "graph container should not be visible"); + ok(isVisible($("#automation-no-events")), "no-events panel should be visible"); + + yield teardown(target); +}); diff --git a/devtools/client/webaudioeditor/test/browser_wa_controller-01.js b/devtools/client/webaudioeditor/test/browser_wa_controller-01.js new file mode 100644 index 000000000..a7064df1f --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_wa_controller-01.js @@ -0,0 +1,28 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Bug 1125817 + * Tests to ensure that disconnecting a node immediately + * after creating it does not fail. + */ + +const BUG_1125817_URL = EXAMPLE_URL + "doc_bug_1125817.html"; + +add_task(function* () { + let { target, panel } = yield initWebAudioEditor(BUG_1125817_URL); + let { panelWin } = panel; + let { gFront, $, $$, EVENTS, gAudioNodes } = panelWin; + + let events = Promise.all([ + once(gAudioNodes, "add", 2), + once(gAudioNodes, "disconnect"), + waitForGraphRendered(panelWin, 2, 0) + ]); + reload(target); + yield events; + + ok(true, "Successfully disconnected a just-created node."); + + yield teardown(target); +}); diff --git a/devtools/client/webaudioeditor/test/browser_wa_destroy-node-01.js b/devtools/client/webaudioeditor/test/browser_wa_destroy-node-01.js new file mode 100644 index 000000000..d7dde4d97 --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_wa_destroy-node-01.js @@ -0,0 +1,59 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that the destruction node event is fired and that the nodes are no + * longer stored internally in the tool, that the graph is updated properly, and + * that selecting a soon-to-be dead node clears the inspector. + * + * All done in one test since this test takes a few seconds to clear GC. + */ + +add_task(function* () { + let { target, panel } = yield initWebAudioEditor(DESTROY_NODES_URL); + let { panelWin } = panel; + let { gFront, $, $$, gAudioNodes } = panelWin; + + let started = once(gFront, "start-context"); + + let events = Promise.all([ + getNSpread(gAudioNodes, "add", 13), + waitForGraphRendered(panelWin, 13, 2) + ]); + reload(target); + let [created] = yield events; + + // Flatten arrays of event arguments and take the first (AudioNodeModel) + // and get its ID. + let actorIDs = created.map(ev => ev[0].id); + + // Click a soon-to-be dead buffer node + yield clickGraphNode(panelWin, actorIDs[5]); + + let destroyed = getN(gAudioNodes, "remove", 10); + + // Force a CC in the child process to collect the orphaned nodes. + forceNodeCollection(); + + // Wait for destruction and graph to re-render + yield Promise.all([destroyed, waitForGraphRendered(panelWin, 3, 2)]); + + // Test internal storage + is(panelWin.gAudioNodes.length, 3, "All nodes should be GC'd except one gain, osc and dest node."); + + // Test graph rendering + ok(findGraphNode(panelWin, actorIDs[0]), "dest should be in graph"); + ok(findGraphNode(panelWin, actorIDs[1]), "osc should be in graph"); + ok(findGraphNode(panelWin, actorIDs[2]), "gain should be in graph"); + + let { nodes, edges } = countGraphObjects(panelWin); + + is(nodes, 3, "Only 3 nodes rendered in graph."); + is(edges, 2, "Only 2 edges rendered in graph."); + + // Test that the inspector reset to no node selected + ok(isVisible($("#web-audio-editor-details-pane-empty")), + "InspectorView empty message should show if the currently selected node gets collected."); + + yield teardown(target); +}); diff --git a/devtools/client/webaudioeditor/test/browser_wa_first-run.js b/devtools/client/webaudioeditor/test/browser_wa_first-run.js new file mode 100644 index 000000000..d2d96932e --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_wa_first-run.js @@ -0,0 +1,49 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// +// Whitelisting this test. +// As part of bug 1077403, the leaking uncaught rejection should be fixed. +// +thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Connection closed"); + +/** + * Tests that the reloading/onContentLoaded hooks work. + */ + +add_task(function* () { + let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); + let { gFront, $ } = panel.panelWin; + + is($("#reload-notice").hidden, false, + "The 'reload this page' notice should initially be visible."); + is($("#waiting-notice").hidden, true, + "The 'waiting for an audio context' notice should initially be hidden."); + is($("#content").hidden, true, + "The tool's content should initially be hidden."); + + let navigating = once(target, "will-navigate"); + let started = once(gFront, "start-context"); + + reload(target); + + yield navigating; + + is($("#reload-notice").hidden, true, + "The 'reload this page' notice should be hidden when navigating."); + is($("#waiting-notice").hidden, false, + "The 'waiting for an audio context' notice should be visible when navigating."); + is($("#content").hidden, true, + "The tool's content should still be hidden."); + + yield started; + + is($("#reload-notice").hidden, true, + "The 'reload this page' notice should be hidden after context found."); + is($("#waiting-notice").hidden, true, + "The 'waiting for an audio context' notice should be hidden after context found."); + is($("#content").hidden, false, + "The tool's content should not be hidden anymore."); + + yield teardown(target); +}); diff --git a/devtools/client/webaudioeditor/test/browser_wa_graph-click.js b/devtools/client/webaudioeditor/test/browser_wa_graph-click.js new file mode 100644 index 000000000..b075d30db --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_wa_graph-click.js @@ -0,0 +1,49 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that the clicking on a node in the GraphView opens and sets + * the correct node in the InspectorView + */ + +add_task(function* () { + let { target, panel } = yield initWebAudioEditor(COMPLEX_CONTEXT_URL); + let panelWin = panel.panelWin; + let { gFront, $, $$, InspectorView } = panelWin; + + let started = once(gFront, "start-context"); + + let events = Promise.all([ + getN(gFront, "create-node", 8), + waitForGraphRendered(panel.panelWin, 8, 8) + ]); + reload(target); + let [actors, _] = yield events; + let nodeIds = actors.map(actor => actor.actorID); + + ok(!InspectorView.isVisible(), "InspectorView hidden on start."); + + yield clickGraphNode(panelWin, nodeIds[1], true); + + ok(InspectorView.isVisible(), "InspectorView visible after selecting a node."); + is(InspectorView.getCurrentAudioNode().id, nodeIds[1], "InspectorView has correct node set."); + + yield clickGraphNode(panelWin, nodeIds[2]); + + ok(InspectorView.isVisible(), "InspectorView still visible after selecting another node."); + is(InspectorView.getCurrentAudioNode().id, nodeIds[2], "InspectorView has correct node set on second node."); + + yield clickGraphNode(panelWin, nodeIds[2]); + is(InspectorView.getCurrentAudioNode().id, nodeIds[2], "Clicking the same node again works (idempotent)."); + + yield clickGraphNode(panelWin, $("rect", findGraphNode(panelWin, nodeIds[3]))); + is(InspectorView.getCurrentAudioNode().id, nodeIds[3], "Clicking on a <rect> works as expected."); + + yield clickGraphNode(panelWin, $("tspan", findGraphNode(panelWin, nodeIds[4]))); + is(InspectorView.getCurrentAudioNode().id, nodeIds[4], "Clicking on a <tspan> works as expected."); + + ok(InspectorView.isVisible(), + "InspectorView still visible after several nodes have been clicked."); + + yield teardown(target); +}); diff --git a/devtools/client/webaudioeditor/test/browser_wa_graph-markers.js b/devtools/client/webaudioeditor/test/browser_wa_graph-markers.js new file mode 100644 index 000000000..adc15d0c3 --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_wa_graph-markers.js @@ -0,0 +1,61 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that the SVG marker styling is updated when devtools theme changes. + */ + +const { setTheme } = require("devtools/client/shared/theme"); + +add_task(function* () { + let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); + let { panelWin } = panel; + let { gFront, $, $$, MARKER_STYLING } = panelWin; + + let currentTheme = Services.prefs.getCharPref("devtools.theme"); + + ok(MARKER_STYLING.light, "Marker styling exists for light theme."); + ok(MARKER_STYLING.dark, "Marker styling exists for dark theme."); + + let started = once(gFront, "start-context"); + + let events = Promise.all([ + get3(gFront, "create-node"), + waitForGraphRendered(panelWin, 3, 2) + ]); + reload(target); + let [actors] = yield events; + + is(getFill($("#arrowhead")), MARKER_STYLING[currentTheme], + "marker initially matches theme."); + + // Switch to light + setTheme("light"); + is(getFill($("#arrowhead")), MARKER_STYLING.light, + "marker styling matches light theme on change."); + + // Switch to dark + setTheme("dark"); + is(getFill($("#arrowhead")), MARKER_STYLING.dark, + "marker styling matches dark theme on change."); + + // Switch to dark again + setTheme("dark"); + is(getFill($("#arrowhead")), MARKER_STYLING.dark, + "marker styling remains dark."); + + // Switch to back to light again + setTheme("light"); + is(getFill($("#arrowhead")), MARKER_STYLING.light, + "marker styling switches back to light once again."); + + yield teardown(target); +}); + +/** + * Returns a hex value found in styling for an element. So parses + * <marker style="fill: #abcdef"> and returns "#abcdef" + */ +function getFill(el) { + return el.getAttribute("style").match(/(#.*)$/)[1]; +} diff --git a/devtools/client/webaudioeditor/test/browser_wa_graph-render-01.js b/devtools/client/webaudioeditor/test/browser_wa_graph-render-01.js new file mode 100644 index 000000000..cee1987b9 --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_wa_graph-render-01.js @@ -0,0 +1,44 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that SVG nodes and edges were created for the Graph View. + */ + +var connectCount = 0; + +add_task(function* () { + let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); + let { panelWin } = panel; + let { gFront, $, $$, EVENTS, gAudioNodes } = panelWin; + + let started = once(gFront, "start-context"); + + gAudioNodes.on("connect", onConnectNode); + + let events = Promise.all([ + get3(gFront, "create-node"), + waitForGraphRendered(panelWin, 3, 2) + ]); + reload(target); + let [actors] = yield events; + let [destId, oscId, gainId] = actors.map(actor => actor.actorID); + + ok(findGraphNode(panelWin, oscId).classList.contains("type-OscillatorNode"), "found OscillatorNode with class"); + ok(findGraphNode(panelWin, gainId).classList.contains("type-GainNode"), "found GainNode with class"); + ok(findGraphNode(panelWin, destId).classList.contains("type-AudioDestinationNode"), "found AudioDestinationNode with class"); + is(findGraphEdge(panelWin, oscId, gainId).toString(), "[object SVGGElement]", "found edge for osc -> gain"); + is(findGraphEdge(panelWin, gainId, destId).toString(), "[object SVGGElement]", "found edge for gain -> dest"); + + yield wait(1000); + + is(connectCount, 2, "Only two node connect events should be fired."); + + gAudioNodes.off("connect", onConnectNode); + + yield teardown(target); +}); + +function onConnectNode() { + ++connectCount; +} diff --git a/devtools/client/webaudioeditor/test/browser_wa_graph-render-02.js b/devtools/client/webaudioeditor/test/browser_wa_graph-render-02.js new file mode 100644 index 000000000..00a63c6b2 --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_wa_graph-render-02.js @@ -0,0 +1,48 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests more edge rendering for complex graphs. + */ + +add_task(function* () { + let { target, panel } = yield initWebAudioEditor(COMPLEX_CONTEXT_URL); + let { panelWin } = panel; + let { gFront, $, $$ } = panelWin; + + let started = once(gFront, "start-context"); + + let events = Promise.all([ + getN(gFront, "create-node", 8), + waitForGraphRendered(panelWin, 8, 8) + ]); + reload(target); + let [actors] = yield events; + let nodeIDs = actors.map(actor => actor.actorID); + + let types = ["AudioDestinationNode", "OscillatorNode", "GainNode", "ScriptProcessorNode", + "OscillatorNode", "GainNode", "AudioBufferSourceNode", "BiquadFilterNode"]; + + + types.forEach((type, i) => { + ok(findGraphNode(panelWin, nodeIDs[i]).classList.contains("type-" + type), "found " + type + " with class"); + }); + + let edges = [ + [1, 2, "osc1 -> gain1"], + [1, 3, "osc1 -> proc"], + [2, 0, "gain1 -> dest"], + [4, 5, "osc2 -> gain2"], + [5, 0, "gain2 -> dest"], + [6, 7, "buf -> filter"], + [4, 7, "osc2 -> filter"], + [7, 0, "filter -> dest"], + ]; + + edges.forEach(([source, target, msg], i) => { + is(findGraphEdge(panelWin, nodeIDs[source], nodeIDs[target]).toString(), "[object SVGGElement]", + "found edge for " + msg); + }); + + yield teardown(target); +}); diff --git a/devtools/client/webaudioeditor/test/browser_wa_graph-render-03.js b/devtools/client/webaudioeditor/test/browser_wa_graph-render-03.js new file mode 100644 index 000000000..ffd9b9881 --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_wa_graph-render-03.js @@ -0,0 +1,34 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests to ensure that selected nodes stay selected on graph redraw. + */ + +add_task(function* () { + let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); + let { panelWin } = panel; + let { gFront, $, $$, EVENTS } = panelWin; + + let events = Promise.all([ + getN(gFront, "create-node", 3), + waitForGraphRendered(panelWin, 3, 2) + ]); + reload(target); + let [actors] = yield events; + let [dest, osc, gain] = actors; + + yield clickGraphNode(panelWin, gain.actorID); + ok(findGraphNode(panelWin, gain.actorID).classList.contains("selected"), + "Node selected once."); + + // Disconnect a node to trigger a rerender + osc.disconnect(); + + yield once(panelWin, EVENTS.UI_GRAPH_RENDERED); + + ok(findGraphNode(panelWin, gain.actorID).classList.contains("selected"), + "Node still selected after rerender."); + + yield teardown(target); +}); diff --git a/devtools/client/webaudioeditor/test/browser_wa_graph-render-04.js b/devtools/client/webaudioeditor/test/browser_wa_graph-render-04.js new file mode 100644 index 000000000..9ed3ceffd --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_wa_graph-render-04.js @@ -0,0 +1,37 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests audio param connection rendering. + */ + +add_task(function* () { + let { target, panel } = yield initWebAudioEditor(CONNECT_MULTI_PARAM_URL); + let { panelWin } = panel; + let { gFront, $, $$, EVENTS } = panelWin; + + let started = once(gFront, "start-context"); + + let events = Promise.all([ + getN(gFront, "create-node", 5), + waitForGraphRendered(panelWin, 5, 2, 3) + ]); + reload(target); + let [actors] = yield events; + let nodeIDs = actors.map(actor => actor.actorID); + + let [, carrier, gain, mod1, mod2] = nodeIDs; + + let edges = [ + [mod1, gain, "gain", "mod1 -> gain[gain]"], + [mod2, carrier, "frequency", "mod2 -> carrier[frequency]"], + [mod2, carrier, "detune", "mod2 -> carrier[detune]"] + ]; + + edges.forEach(([source, target, param, msg], i) => { + let edge = findGraphEdge(panelWin, source, target, param); + ok(edge.classList.contains("param-connection"), "edge is classified as a param-connection"); + }); + + yield teardown(target); +}); diff --git a/devtools/client/webaudioeditor/test/browser_wa_graph-render-05.js b/devtools/client/webaudioeditor/test/browser_wa_graph-render-05.js new file mode 100644 index 000000000..2748984d0 --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_wa_graph-render-05.js @@ -0,0 +1,28 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests to ensure that param connections trigger graph redraws + */ + +add_task(function* () { + let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); + let { panelWin } = panel; + let { gFront, $, $$, EVENTS } = panelWin; + + let events = Promise.all([ + getN(gFront, "create-node", 3), + waitForGraphRendered(panelWin, 3, 2, 0) + ]); + reload(target); + let [actors] = yield events; + let [dest, osc, gain] = actors; + + yield osc.disconnect(); + + osc.connectParam(gain, "gain"); + yield waitForGraphRendered(panelWin, 3, 1, 1); + ok(true, "Graph re-rendered upon param connection"); + + yield teardown(target); +}); diff --git a/devtools/client/webaudioeditor/test/browser_wa_graph-render-06.js b/devtools/client/webaudioeditor/test/browser_wa_graph-render-06.js new file mode 100644 index 000000000..c47d60b7c --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_wa_graph-render-06.js @@ -0,0 +1,25 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests to ensure that param connections trigger graph redraws + */ + +const BUG_1141261_URL = EXAMPLE_URL + "doc_bug_1141261.html"; + +add_task(function* () { + let { target, panel } = yield initWebAudioEditor(BUG_1141261_URL); + let { panelWin } = panel; + let { gFront, $, $$, EVENTS } = panelWin; + + let events = Promise.all([ + getN(gFront, "create-node", 3), + waitForGraphRendered(panelWin, 3, 1, 0) + ]); + reload(target); + yield events; + + ok(true, "Graph correctly shows gain node as disconnected"); + + yield teardown(target); +}); diff --git a/devtools/client/webaudioeditor/test/browser_wa_graph-selected.js b/devtools/client/webaudioeditor/test/browser_wa_graph-selected.js new file mode 100644 index 000000000..72044b7bd --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_wa_graph-selected.js @@ -0,0 +1,49 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that SVG nodes and edges were created for the Graph View. + */ + +add_task(function* () { + let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); + let { panelWin } = panel; + let { gFront, $, $$, EVENTS } = panelWin; + + let started = once(gFront, "start-context"); + + let events = Promise.all([ + get3(gFront, "create-node"), + waitForGraphRendered(panelWin, 3, 2) + ]); + reload(target); + let [actors] = yield events; + let [destId, oscId, gainId] = actors.map(actor => actor.actorID); + + ok(!findGraphNode(panelWin, destId).classList.contains("selected"), + "No nodes selected on start. (destination)"); + ok(!findGraphNode(panelWin, oscId).classList.contains("selected"), + "No nodes selected on start. (oscillator)"); + ok(!findGraphNode(panelWin, gainId).classList.contains("selected"), + "No nodes selected on start. (gain)"); + + yield clickGraphNode(panelWin, oscId); + + ok(findGraphNode(panelWin, oscId).classList.contains("selected"), + "Selected node has class 'selected'."); + ok(!findGraphNode(panelWin, destId).classList.contains("selected"), + "Non-selected nodes do not have class 'selected'."); + ok(!findGraphNode(panelWin, gainId).classList.contains("selected"), + "Non-selected nodes do not have class 'selected'."); + + yield clickGraphNode(panelWin, gainId); + + ok(!findGraphNode(panelWin, oscId).classList.contains("selected"), + "Previously selected node no longer has class 'selected'."); + ok(!findGraphNode(panelWin, destId).classList.contains("selected"), + "Non-selected nodes do not have class 'selected'."); + ok(findGraphNode(panelWin, gainId).classList.contains("selected"), + "Newly selected node now has class 'selected'."); + + yield teardown(target); +}); diff --git a/devtools/client/webaudioeditor/test/browser_wa_graph-zoom.js b/devtools/client/webaudioeditor/test/browser_wa_graph-zoom.js new file mode 100644 index 000000000..240b6d5a1 --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_wa_graph-zoom.js @@ -0,0 +1,43 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that the graph's scale and position is reset on a page reload. + */ + +add_task(function* () { + let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); + let { panelWin } = panel; + let { gFront, $, $$, EVENTS, ContextView } = panelWin; + + let started = once(gFront, "start-context"); + + yield Promise.all([ + waitForGraphRendered(panelWin, 3, 2), + reload(target), + ]); + + is(ContextView.getCurrentScale(), 1, "Default graph scale is 1."); + is(ContextView.getCurrentTranslation()[0], 20, "Default x-translation is 20."); + is(ContextView.getCurrentTranslation()[1], 20, "Default y-translation is 20."); + + // Change both attribute and D3's internal store + panelWin.d3.select("#graph-target").attr("transform", "translate([100, 400]) scale(10)"); + ContextView._zoomBinding.scale(10); + ContextView._zoomBinding.translate([100, 400]); + + is(ContextView.getCurrentScale(), 10, "After zoom, scale is 10."); + is(ContextView.getCurrentTranslation()[0], 100, "After zoom, x-translation is 100."); + is(ContextView.getCurrentTranslation()[1], 400, "After zoom, y-translation is 400."); + + yield Promise.all([ + waitForGraphRendered(panelWin, 3, 2), + reload(target), + ]); + + is(ContextView.getCurrentScale(), 1, "After refresh, graph scale is 1."); + is(ContextView.getCurrentTranslation()[0], 20, "After refresh, x-translation is 20."); + is(ContextView.getCurrentTranslation()[1], 20, "After refresh, y-translation is 20."); + + yield teardown(target); +}); diff --git a/devtools/client/webaudioeditor/test/browser_wa_inspector-bypass-01.js b/devtools/client/webaudioeditor/test/browser_wa_inspector-bypass-01.js new file mode 100644 index 000000000..c9d3450e3 --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_wa_inspector-bypass-01.js @@ -0,0 +1,61 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that nodes are correctly bypassed when bypassing. + */ + +add_task(function* () { + let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); + let { panelWin } = panel; + let { gFront, $, $$, EVENTS, gAudioNodes } = panelWin; + + let events = Promise.all([ + get3(gFront, "create-node"), + waitForGraphRendered(panelWin, 3, 2) + ]); + reload(target); + let [actors] = yield events; + let nodeIds = actors.map(actor => actor.actorID); + + // Wait for the node to be set as well as the inspector to come fully into the view + yield clickGraphNode(panelWin, findGraphNode(panelWin, nodeIds[1]), true); + + let $bypass = $("toolbarbutton.bypass"); + + is((yield actors[1].isBypassed()), false, "AudioNodeActor is not bypassed by default."); + is($bypass.checked, true, "Button is 'on' for normal nodes"); + is($bypass.disabled, false, "Bypass button is not disabled for normal nodes"); + + command($bypass); + yield once(gAudioNodes, "bypass"); + + is((yield actors[1].isBypassed()), true, "AudioNodeActor is bypassed."); + is($bypass.checked, false, "Button is 'off' when clicked"); + is($bypass.disabled, false, "Bypass button is not disabled after click"); + ok(findGraphNode(panelWin, nodeIds[1]).classList.contains("bypassed"), + "AudioNode has 'bypassed' class."); + + command($bypass); + yield once(gAudioNodes, "bypass"); + + is((yield actors[1].isBypassed()), false, "AudioNodeActor is no longer bypassed."); + is($bypass.checked, true, "Button is back on when clicked"); + is($bypass.disabled, false, "Bypass button is not disabled after click"); + ok(!findGraphNode(panelWin, nodeIds[1]).classList.contains("bypassed"), + "AudioNode no longer has 'bypassed' class."); + + yield clickGraphNode(panelWin, findGraphNode(panelWin, nodeIds[0])); + + is((yield actors[0].isBypassed()), false, "Unbypassable AudioNodeActor is not bypassed."); + is($bypass.checked, false, "Button is 'off' for unbypassable nodes"); + is($bypass.disabled, true, "Bypass button is disabled for unbypassable nodes"); + + command($bypass); + is((yield actors[0].isBypassed()), false, + "Clicking button on unbypassable node does not change bypass state on actor."); + is($bypass.checked, false, "Button is still 'off' for unbypassable nodes"); + is($bypass.disabled, true, "Bypass button is still disabled for unbypassable nodes"); + + yield teardown(target); +}); diff --git a/devtools/client/webaudioeditor/test/browser_wa_inspector-toggle.js b/devtools/client/webaudioeditor/test/browser_wa_inspector-toggle.js new file mode 100644 index 000000000..251f92471 --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_wa_inspector-toggle.js @@ -0,0 +1,60 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that the inspector toggle button shows and hides + * the inspector panel as intended. + */ + +add_task(function* () { + let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); + let { panelWin } = panel; + let { gFront, $, $$, EVENTS, InspectorView } = panelWin; + let gVars = InspectorView._propsView; + + let started = once(gFront, "start-context"); + + let events = Promise.all([ + get3(gFront, "create-node"), + waitForGraphRendered(panelWin, 3, 2) + ]); + reload(target); + let [actors] = yield events; + let nodeIds = actors.map(actor => actor.actorID); + + ok(!InspectorView.isVisible(), "InspectorView hidden on start."); + + // Open inspector pane + $("#inspector-pane-toggle").click(); + yield once(panelWin, EVENTS.UI_INSPECTOR_TOGGLED); + + ok(InspectorView.isVisible(), "InspectorView shown after toggling."); + + ok(isVisible($("#web-audio-editor-details-pane-empty")), + "InspectorView empty message should still be visible."); + ok(!isVisible($("#web-audio-editor-tabs")), + "InspectorView tabs view should still be hidden."); + + // Close inspector pane + $("#inspector-pane-toggle").click(); + yield once(panelWin, EVENTS.UI_INSPECTOR_TOGGLED); + + ok(!InspectorView.isVisible(), "InspectorView back to being hidden."); + + // Open again to test node loading while open + $("#inspector-pane-toggle").click(); + yield once(panelWin, EVENTS.UI_INSPECTOR_TOGGLED); + + ok(InspectorView.isVisible(), "InspectorView being shown."); + ok(!isVisible($("#web-audio-editor-tabs")), + "InspectorView tabs are still hidden."); + + yield clickGraphNode(panelWin, findGraphNode(panelWin, nodeIds[1])); + + ok(!isVisible($("#web-audio-editor-details-pane-empty")), + "Empty message hides even when loading node while open."); + ok(isVisible($("#web-audio-editor-tabs")), + "Switches to tab view when loading node while open."); + + yield teardown(target); +}); diff --git a/devtools/client/webaudioeditor/test/browser_wa_inspector-width.js b/devtools/client/webaudioeditor/test/browser_wa_inspector-width.js new file mode 100644 index 000000000..d37774013 --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_wa_inspector-width.js @@ -0,0 +1,57 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that the WebAudioInspector's Width is saved as + * a preference + */ + +add_task(function* () { + let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); + let { panelWin } = panel; + let { gFront, $, $$, EVENTS, InspectorView } = panelWin; + let gVars = InspectorView._propsView; + + let started = once(gFront, "start-context"); + + let events = Promise.all([ + get3(gFront, "create-node"), + waitForGraphRendered(panelWin, 3, 2) + ]); + reload(target); + let [actors] = yield events; + let nodeIds = actors.map(actor => actor.actorID); + + ok(!InspectorView.isVisible(), "InspectorView hidden on start."); + + // Open inspector pane + $("#inspector-pane-toggle").click(); + yield once(panelWin, EVENTS.UI_INSPECTOR_TOGGLED); + + let newInspectorWidth = 500; + + // Setting width to new_inspector_width + $("#web-audio-inspector").setAttribute("width", newInspectorWidth); + + // Width should be 500 after reloading + events = Promise.all([ + get3(gFront, "create-node"), + waitForGraphRendered(panelWin, 3, 2) + ]); + reload(target); + [actors] = yield events; + nodeIds = actors.map(actor => actor.actorID); + + // Open inspector pane + $("#inspector-pane-toggle").click(); + yield once(panelWin, EVENTS.UI_INSPECTOR_TOGGLED); + + yield clickGraphNode(panelWin, findGraphNode(panelWin, nodeIds[1])); + + // Getting the width of the audio inspector + let width = $("#web-audio-inspector").getAttribute("width"); + + is(width, newInspectorWidth, "WebAudioEditor's Inspector width should be saved as a preference"); + + yield teardown(target); +}); diff --git a/devtools/client/webaudioeditor/test/browser_wa_inspector.js b/devtools/client/webaudioeditor/test/browser_wa_inspector.js new file mode 100644 index 000000000..5599ad36f --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_wa_inspector.js @@ -0,0 +1,46 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that inspector view opens on graph node click, and + * loads the correct node inside the inspector. + */ + +add_task(function* () { + let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); + let { panelWin } = panel; + let { gFront, $, $$, EVENTS, InspectorView } = panelWin; + let gVars = InspectorView._propsView; + + let started = once(gFront, "start-context"); + + let events = Promise.all([ + get3(gFront, "create-node"), + waitForGraphRendered(panelWin, 3, 2) + ]); + reload(target); + let [actors] = yield events; + let nodeIds = actors.map(actor => actor.actorID); + + ok(!InspectorView.isVisible(), "InspectorView hidden on start."); + ok(isVisible($("#web-audio-editor-details-pane-empty")), + "InspectorView empty message should show when no node's selected."); + ok(!isVisible($("#web-audio-editor-tabs")), + "InspectorView tabs view should be hidden when no node's selected."); + + // Wait for the node to be set as well as the inspector to come fully into the view + yield clickGraphNode(panelWin, findGraphNode(panelWin, nodeIds[1]), true); + + ok(InspectorView.isVisible(), "InspectorView shown once node selected."); + ok(!isVisible($("#web-audio-editor-details-pane-empty")), + "InspectorView empty message hidden when node selected."); + ok(isVisible($("#web-audio-editor-tabs")), + "InspectorView tabs view visible when node selected."); + + is($("#web-audio-editor-tabs").selectedIndex, 0, + "default tab selected should be the parameters tab."); + + yield clickGraphNode(panelWin, findGraphNode(panelWin, nodeIds[2])); + + yield teardown(target); +}); diff --git a/devtools/client/webaudioeditor/test/browser_wa_navigate.js b/devtools/client/webaudioeditor/test/browser_wa_navigate.js new file mode 100644 index 000000000..e1f094384 --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_wa_navigate.js @@ -0,0 +1,44 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests naviating from a page to another will repopulate + * the audio graph if both pages have an AudioContext. + */ + +add_task(function* () { + let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); + let { panelWin } = panel; + let { gFront, $ } = panelWin; + + let events = Promise.all([ + get3(gFront, "create-node"), + waitForGraphRendered(panelWin, 3, 2) + ]); + reload(target); + yield events; + + var { nodes, edges } = countGraphObjects(panelWin); + is(nodes, 3, "should only be 3 nodes."); + is(edges, 2, "should only be 2 edges."); + + events = Promise.all([ + getN(gFront, "create-node", 15), + waitForGraphRendered(panelWin, 15, 0) + ]); + navigate(target, SIMPLE_NODES_URL); + yield events; + + is($("#reload-notice").hidden, true, + "The 'reload this page' notice should be hidden after context found after navigation."); + is($("#waiting-notice").hidden, true, + "The 'waiting for an audio context' notice should be hidden after context found after navigation."); + is($("#content").hidden, false, + "The tool's content should reappear without closing and reopening the toolbox."); + + var { nodes, edges } = countGraphObjects(panelWin); + is(nodes, 15, "after navigation, should have 15 nodes"); + is(edges, 0, "after navigation, should have 0 edges."); + + yield teardown(target); +}); diff --git a/devtools/client/webaudioeditor/test/browser_wa_properties-view-edit-01.js b/devtools/client/webaudioeditor/test/browser_wa_properties-view-edit-01.js new file mode 100644 index 000000000..ac7deca26 --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_wa_properties-view-edit-01.js @@ -0,0 +1,65 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that properties are updated when modifying the VariablesView. + */ + +add_task(function* () { + let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); + let { panelWin } = panel; + let { gFront, $, $$, EVENTS, PropertiesView } = panelWin; + let gVars = PropertiesView._propsView; + + let started = once(gFront, "start-context"); + + let events = Promise.all([ + get3(gFront, "create-node"), + waitForGraphRendered(panelWin, 3, 2) + ]); + reload(target); + let [actors] = yield events; + let nodeIds = actors.map(actor => actor.actorID); + + click(panelWin, findGraphNode(panelWin, nodeIds[1])); + // Wait for the node to be set as well as the inspector to come fully into the view + yield Promise.all([ + waitForInspectorRender(panelWin, EVENTS), + once(panelWin, EVENTS.UI_INSPECTOR_TOGGLED) + ]); + + let setAndCheck = setAndCheckVariable(panelWin, gVars); + + checkVariableView(gVars, 0, { + "type": "sine", + "frequency": 440, + "detune": 0 + }, "default loaded string"); + + click(panelWin, findGraphNode(panelWin, nodeIds[2])); + yield waitForInspectorRender(panelWin, EVENTS), + checkVariableView(gVars, 0, { + "gain": 0 + }, "default loaded number"); + + click(panelWin, findGraphNode(panelWin, nodeIds[1])); + yield waitForInspectorRender(panelWin, EVENTS), + yield setAndCheck(0, "type", "square", "square", "sets string as string"); + + click(panelWin, findGraphNode(panelWin, nodeIds[2])); + yield waitForInspectorRender(panelWin, EVENTS), + yield setAndCheck(0, "gain", "0.005", 0.005, "sets number as number"); + yield setAndCheck(0, "gain", "0.1", 0.1, "sets float as float"); + yield setAndCheck(0, "gain", ".2", 0.2, "sets float without leading zero as float"); + + yield teardown(target); +}); + +function setAndCheckVariable(panelWin, gVars) { + return Task.async(function* (varNum, prop, value, expected, desc) { + yield modifyVariableView(panelWin, gVars, varNum, prop, value); + var props = {}; + props[prop] = expected; + checkVariableView(gVars, varNum, props, desc); + }); +} diff --git a/devtools/client/webaudioeditor/test/browser_wa_properties-view-edit-02.js b/devtools/client/webaudioeditor/test/browser_wa_properties-view-edit-02.js new file mode 100644 index 000000000..d7c54822d --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_wa_properties-view-edit-02.js @@ -0,0 +1,44 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that properties are not updated when modifying the VariablesView. + */ + +add_task(function* () { + let { target, panel } = yield initWebAudioEditor(COMPLEX_CONTEXT_URL); + let { panelWin } = panel; + let { gFront, $, $$, EVENTS, PropertiesView } = panelWin; + let gVars = PropertiesView._propsView; + + let started = once(gFront, "start-context"); + + let events = Promise.all([ + getN(gFront, "create-node", 8), + waitForGraphRendered(panelWin, 8, 8) + ]); + reload(target); + let [actors] = yield events; + let nodeIds = actors.map(actor => actor.actorID); + + click(panelWin, findGraphNode(panelWin, nodeIds[3])); + // Wait for the node to be set as well as the inspector to come fully into the view + yield Promise.all([ + waitForInspectorRender(panelWin, EVENTS), + once(panelWin, EVENTS.UI_INSPECTOR_TOGGLED), + ]); + + let errorEvent = once(panelWin, EVENTS.UI_SET_PARAM_ERROR); + + try { + yield modifyVariableView(panelWin, gVars, 0, "bufferSize", 2048); + } catch (e) { + // we except modifyVariableView to fail here, because bufferSize is not writable + } + + yield errorEvent; + + checkVariableView(gVars, 0, {bufferSize: 4096}, "check that unwritable variable is not updated"); + + yield teardown(target); +}); diff --git a/devtools/client/webaudioeditor/test/browser_wa_properties-view-media-nodes.js b/devtools/client/webaudioeditor/test/browser_wa_properties-view-media-nodes.js new file mode 100644 index 000000000..c1a916a1f --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_wa_properties-view-media-nodes.js @@ -0,0 +1,76 @@ +/* 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. + */ + +var MEDIA_PERMISSION = "media.navigator.permission.disabled"; + +function waitForDeviceClosed() { + info("Checking that getUserMedia streams are no longer in use."); + + let temp = {}; + Cu.import("resource:///modules/webrtcUI.jsm", temp); + let webrtcUI = temp.webrtcUI; + + if (!webrtcUI.showGlobalIndicator) + return Promise.resolve(); + + let deferred = Promise.defer(); + + const message = "webrtc:UpdateGlobalIndicators"; + let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"] + .getService(Ci.nsIMessageBroadcaster); + ppmm.addMessageListener(message, function listener(aMessage) { + info("Received " + message + " message"); + if (!aMessage.data.showGlobalIndicator) { + ppmm.removeMessageListener(message, listener); + deferred.resolve(); + } + }); + + return deferred.promise; +} + +add_task(function* () { + let { target, panel } = yield initWebAudioEditor(MEDIA_NODES_URL); + let { panelWin } = panel; + let { gFront, $, $$, EVENTS, PropertiesView } = panelWin; + let gVars = PropertiesView._propsView; + + // Auto enable getUserMedia + let mediaPermissionPref = Services.prefs.getBoolPref(MEDIA_PERMISSION); + Services.prefs.setBoolPref(MEDIA_PERMISSION, true); + + yield loadFrameScripts(); + + let events = Promise.all([ + getN(gFront, "create-node", 4), + waitForGraphRendered(panelWin, 4, 0) + ]); + reload(target); + let [actors] = yield events; + let nodeIds = actors.map(actor => actor.actorID); + + let types = [ + "AudioDestinationNode", "MediaElementAudioSourceNode", + "MediaStreamAudioSourceNode", "MediaStreamAudioDestinationNode" + ]; + + let defaults = yield Promise.all(types.map(type => nodeDefaultValues(type))); + + for (let i = 0; i < types.length; i++) { + click(panelWin, findGraphNode(panelWin, nodeIds[i])); + yield waitForInspectorRender(panelWin, EVENTS); + checkVariableView(gVars, 0, defaults[i], types[i]); + } + + // Reset permissions on getUserMedia + Services.prefs.setBoolPref(MEDIA_PERMISSION, mediaPermissionPref); + + yield teardown(target); + + yield waitForDeviceClosed(); +}); diff --git a/devtools/client/webaudioeditor/test/browser_wa_properties-view-params-objects.js b/devtools/client/webaudioeditor/test/browser_wa_properties-view-params-objects.js new file mode 100644 index 000000000..e0a555201 --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_wa_properties-view-params-objects.js @@ -0,0 +1,46 @@ +/* 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. + */ + +add_task(function* () { + let { target, panel } = yield initWebAudioEditor(BUFFER_AND_ARRAY_URL); + let { panelWin } = panel; + let { gFront, $, $$, EVENTS, PropertiesView } = panelWin; + let gVars = PropertiesView._propsView; + + let started = once(gFront, "start-context"); + + let events = Promise.all([ + getN(gFront, "create-node", 3), + waitForGraphRendered(panelWin, 3, 2) + ]); + reload(target); + let [actors] = yield events; + let nodeIds = actors.map(actor => actor.actorID); + + click(panelWin, findGraphNode(panelWin, nodeIds[2])); + yield waitForInspectorRender(panelWin, EVENTS); + checkVariableView(gVars, 0, { + "curve": "Float32Array" + }, "WaveShaper's `curve` is listed as an `Float32Array`."); + + let aVar = gVars.getScopeAtIndex(0).get("curve"); + let state = aVar.target.querySelector(".theme-twisty").hasAttribute("invisible"); + ok(state, "Float32Array property should not have a dropdown."); + + click(panelWin, findGraphNode(panelWin, nodeIds[1])); + yield waitForInspectorRender(panelWin, EVENTS); + checkVariableView(gVars, 0, { + "buffer": "AudioBuffer" + }, "AudioBufferSourceNode's `buffer` is listed as an `AudioBuffer`."); + + aVar = gVars.getScopeAtIndex(0).get("buffer"); + state = aVar.target.querySelector(".theme-twisty").hasAttribute("invisible"); + ok(state, "AudioBuffer property should not have a dropdown."); + + yield teardown(target); +}); diff --git a/devtools/client/webaudioeditor/test/browser_wa_properties-view-params.js b/devtools/client/webaudioeditor/test/browser_wa_properties-view-params.js new file mode 100644 index 000000000..31319e8c5 --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_wa_properties-view-params.js @@ -0,0 +1,43 @@ +/* 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. + */ + +add_task(function* () { + let { target, panel } = yield initWebAudioEditor(SIMPLE_NODES_URL); + let { panelWin } = panel; + let { gFront, $, $$, EVENTS, PropertiesView } = panelWin; + let gVars = PropertiesView._propsView; + + let started = once(gFront, "start-context"); + + yield loadFrameScripts(); + + let events = Promise.all([ + getN(gFront, "create-node", 15), + waitForGraphRendered(panelWin, 15, 0) + ]); + reload(target); + let [actors] = yield events; + let nodeIds = actors.map(actor => actor.actorID); + + let types = [ + "AudioDestinationNode", "AudioBufferSourceNode", "ScriptProcessorNode", + "AnalyserNode", "GainNode", "DelayNode", "BiquadFilterNode", "WaveShaperNode", + "PannerNode", "ConvolverNode", "ChannelSplitterNode", "ChannelMergerNode", + "DynamicsCompressorNode", "OscillatorNode" + ]; + + let defaults = yield Promise.all(types.map(type => nodeDefaultValues(type))); + + for (let i = 0; i < types.length; i++) { + click(panelWin, findGraphNode(panelWin, nodeIds[i])); + yield waitForInspectorRender(panelWin, EVENTS); + checkVariableView(gVars, 0, defaults[i], types[i]); + } + + yield teardown(target); +}); diff --git a/devtools/client/webaudioeditor/test/browser_wa_properties-view.js b/devtools/client/webaudioeditor/test/browser_wa_properties-view.js new file mode 100644 index 000000000..bda51e4ac --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_wa_properties-view.js @@ -0,0 +1,42 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that params view shows params when they exist, and are hidden otherwise. + */ + +add_task(function* () { + let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); + let { panelWin } = panel; + let { gFront, $, $$, EVENTS, PropertiesView } = panelWin; + let gVars = PropertiesView._propsView; + + let started = once(gFront, "start-context"); + + let events = Promise.all([ + get3(gFront, "create-node"), + waitForGraphRendered(panelWin, 3, 2) + ]); + reload(target); + let [actors] = yield events; + let nodeIds = actors.map(actor => actor.actorID); + + // Gain node + click(panelWin, findGraphNode(panelWin, nodeIds[2])); + yield waitForInspectorRender(panelWin, EVENTS); + + ok(isVisible($("#properties-content")), "Parameters shown when they exist."); + ok(!isVisible($("#properties-empty")), + "Empty message hidden when AudioParams exist."); + + // Destination node + click(panelWin, findGraphNode(panelWin, nodeIds[0])); + yield waitForInspectorRender(panelWin, EVENTS); + + ok(!isVisible($("#properties-content")), + "Parameters hidden when they don't exist."); + ok(isVisible($("#properties-empty")), + "Empty message shown when no AudioParams exist."); + + yield teardown(target); +}); diff --git a/devtools/client/webaudioeditor/test/browser_wa_reset-01.js b/devtools/client/webaudioeditor/test/browser_wa_reset-01.js new file mode 100644 index 000000000..67a0c33ff --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_wa_reset-01.js @@ -0,0 +1,67 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// +// Whitelisting this test. +// As part of bug 1077403, the leaking uncaught rejection should be fixed. +// +thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Connection closed"); + +/** + * Tests that reloading a tab will properly listen for the `start-context` + * event and reshow the tools after reloading. + */ + +add_task(function* () { + let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); + let { gFront, $ } = panel.panelWin; + + is($("#reload-notice").hidden, false, + "The 'reload this page' notice should initially be visible."); + is($("#waiting-notice").hidden, true, + "The 'waiting for an audio context' notice should initially be hidden."); + is($("#content").hidden, true, + "The tool's content should initially be hidden."); + + let navigating = once(target, "will-navigate"); + let started = once(gFront, "start-context"); + + reload(target); + + yield navigating; + + is($("#reload-notice").hidden, true, + "The 'reload this page' notice should be hidden when navigating."); + is($("#waiting-notice").hidden, false, + "The 'waiting for an audio context' notice should be visible when navigating."); + is($("#content").hidden, true, + "The tool's content should still be hidden."); + + yield started; + + is($("#reload-notice").hidden, true, + "The 'reload this page' notice should be hidden after context found."); + is($("#waiting-notice").hidden, true, + "The 'waiting for an audio context' notice should be hidden after context found."); + is($("#content").hidden, false, + "The tool's content should not be hidden anymore."); + + navigating = once(target, "will-navigate"); + started = once(gFront, "start-context"); + + reload(target); + + yield Promise.all([navigating, started]); + let rendered = waitForGraphRendered(panel.panelWin, 3, 2); + + is($("#reload-notice").hidden, true, + "The 'reload this page' notice should be hidden after context found after reload."); + is($("#waiting-notice").hidden, true, + "The 'waiting for an audio context' notice should be hidden after context found after reload."); + is($("#content").hidden, false, + "The tool's content should reappear without closing and reopening the toolbox."); + + yield rendered; + + yield teardown(target); +}); diff --git a/devtools/client/webaudioeditor/test/browser_wa_reset-02.js b/devtools/client/webaudioeditor/test/browser_wa_reset-02.js new file mode 100644 index 000000000..c9516b364 --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_wa_reset-02.js @@ -0,0 +1,37 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests reloading a tab with the tools open properly cleans up + * the graph. + */ + +add_task(function* () { + let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); + let { panelWin } = panel; + let { gFront, $ } = panelWin; + + let events = Promise.all([ + get3(gFront, "create-node"), + waitForGraphRendered(panelWin, 3, 2) + ]); + reload(target); + yield events; + + let { nodes, edges } = countGraphObjects(panelWin); + is(nodes, 3, "should only be 3 nodes."); + is(edges, 2, "should only be 2 edges."); + + events = Promise.all([ + get3(gFront, "create-node"), + waitForGraphRendered(panelWin, 3, 2) + ]); + reload(target); + yield events; + + ({ nodes, edges } = countGraphObjects(panelWin)); + is(nodes, 3, "after reload, should only be 3 nodes."); + is(edges, 2, "after reload, should only be 2 edges."); + + yield teardown(target); +}); diff --git a/devtools/client/webaudioeditor/test/browser_wa_reset-03.js b/devtools/client/webaudioeditor/test/browser_wa_reset-03.js new file mode 100644 index 000000000..1207793f5 --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_wa_reset-03.js @@ -0,0 +1,48 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests reloading a tab with the tools open properly cleans up + * the inspector and selected node. + */ + +add_task(function* () { + let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL); + let { panelWin } = panel; + let { gFront, $, InspectorView } = panelWin; + + let events = Promise.all([ + get3(gFront, "create-node"), + waitForGraphRendered(panelWin, 3, 2) + ]); + reload(target); + let [actors] = yield events; + let nodeIds = actors.map(actor => actor.actorID); + + yield clickGraphNode(panelWin, nodeIds[1], true); + ok(InspectorView.isVisible(), "InspectorView visible after selecting a node."); + is(InspectorView.getCurrentAudioNode().id, nodeIds[1], "InspectorView has correct node set."); + + /** + * Reload + */ + + events = Promise.all([ + get3(gFront, "create-node"), + waitForGraphRendered(panelWin, 3, 2) + ]); + reload(target); + [actors] = yield events; + nodeIds = actors.map(actor => actor.actorID); + + ok(!InspectorView.isVisible(), "InspectorView hidden on start."); + is(InspectorView.getCurrentAudioNode(), null, + "InspectorView has no current node set on reset."); + + yield clickGraphNode(panelWin, nodeIds[2], true); + ok(InspectorView.isVisible(), + "InspectorView visible after selecting a node after a reset."); + is(InspectorView.getCurrentAudioNode().id, nodeIds[2], "InspectorView has correct node set upon clicking graph node after a reset."); + + yield teardown(target); +}); diff --git a/devtools/client/webaudioeditor/test/browser_wa_reset-04.js b/devtools/client/webaudioeditor/test/browser_wa_reset-04.js new file mode 100644 index 000000000..7ad009020 --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_wa_reset-04.js @@ -0,0 +1,66 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// +// Whitelisting this test. +// As part of bug 1077403, the leaking uncaught rejection should be fixed. +// +thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Connection closed"); + +/** + * Tests that switching to an iframe works fine. + */ + +add_task(function* () { + Services.prefs.setBoolPref("devtools.command-button-frames.enabled", true); + + let { target, panel, toolbox } = yield initWebAudioEditor(IFRAME_CONTEXT_URL); + let { gFront, $ } = panel.panelWin; + + is($("#reload-notice").hidden, false, + "The 'reload this page' notice should initially be visible."); + is($("#waiting-notice").hidden, true, + "The 'waiting for an audio context' notice should initially be hidden."); + is($("#content").hidden, true, + "The tool's content should initially be hidden."); + + let btn = toolbox.doc.getElementById("command-button-frames"); + ok(!btn.firstChild, "The frame list button has no children"); + + // Open frame menu and wait till it's available on the screen. + let menu = toolbox.showFramesMenu({target: btn}); + yield once(menu, "open"); + + let frames = menu.items; + is(frames.length, 2, "We have both frames in the list"); + + // Select the iframe + frames[1].click(); + + let navigating = once(target, "will-navigate"); + + yield navigating; + + is($("#reload-notice").hidden, false, + "The 'reload this page' notice should still be visible when switching to a frame."); + is($("#waiting-notice").hidden, true, + "The 'waiting for an audio context' notice should be kept hidden when switching to a frame."); + is($("#content").hidden, true, + "The tool's content should still be hidden."); + + navigating = once(target, "will-navigate"); + let started = once(gFront, "start-context"); + + reload(target); + + yield Promise.all([navigating, started]); + + is($("#reload-notice").hidden, true, + "The 'reload this page' notice should be hidden after reloading the frame."); + is($("#waiting-notice").hidden, true, + "The 'waiting for an audio context' notice should be hidden after reloading the frame."); + is($("#content").hidden, false, + "The tool's content should appear after reload."); + + yield teardown(target); +}); diff --git a/devtools/client/webaudioeditor/test/browser_webaudio-actor-automation-event.js b/devtools/client/webaudioeditor/test/browser_webaudio-actor-automation-event.js new file mode 100644 index 000000000..9d542d5f0 --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_webaudio-actor-automation-event.js @@ -0,0 +1,52 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that the WebAudioActor receives and emits the `automation-event` events + * with correct arguments from the content. + */ + +add_task(function* () { + let { target, front } = yield initBackend(AUTOMATION_URL); + let events = []; + + let expected = [ + ["setValueAtTime", 0.2, 0], + ["linearRampToValueAtTime", 1, 0.3], + ["exponentialRampToValueAtTime", 0.75, 0.6], + ["setValueCurveAtTime", [-1, 0, 1], 0.7, 0.3], + ]; + + front.on("automation-event", onAutomationEvent); + + 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") + ]); + + is(events.length, 4, "correct number of events fired"); + + function onAutomationEvent(e) { + let { eventName, paramName, args } = e; + let exp = expected[events.length]; + + is(eventName, exp[0], "correct eventName in event"); + is(paramName, "frequency", "correct paramName in event"); + is(args.length, exp.length - 1, "correct length in args"); + + args.forEach((a, i) => { + // In the case of an array + if (typeof a === "object") { + a.forEach((f, j) => is(f, exp[i + 1][j], `correct argument in Float32Array: ${f}`)); + } else { + is(a, exp[i + 1], `correct ${i + 1}th argument in args: ${a}`); + } + }); + events.push([eventName].concat(args)); + } + + front.off("automation-event", onAutomationEvent); + yield removeTab(target.tab); +}); diff --git a/devtools/client/webaudioeditor/test/browser_webaudio-actor-connect-param.js b/devtools/client/webaudioeditor/test/browser_webaudio-actor-connect-param.js new file mode 100644 index 000000000..2910d5bd1 --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_webaudio-actor-connect-param.js @@ -0,0 +1,25 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test the `connect-param` event on the web audio actor. + */ + +add_task(function* () { + let { target, front } = yield initBackend(CONNECT_PARAM_URL); + let [, , [destNode, carrierNode, modNode, gainNode], , connectParam] = yield Promise.all([ + front.setup({ reload: true }), + once(front, "start-context"), + getN(front, "create-node", 4), + get2(front, "connect-node"), + once(front, "connect-param") + ]); + + info(connectParam); + + is(connectParam.source.actorID, modNode.actorID, "`connect-param` has correct actor for `source`"); + is(connectParam.dest.actorID, gainNode.actorID, "`connect-param` has correct actor for `dest`"); + is(connectParam.param, "gain", "`connect-param` has correct parameter name for `param`"); + + yield removeTab(target.tab); +}); diff --git a/devtools/client/webaudioeditor/test/browser_webaudio-actor-destroy-node.js b/devtools/client/webaudioeditor/test/browser_webaudio-actor-destroy-node.js new file mode 100644 index 000000000..e48836c3f --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_webaudio-actor-destroy-node.js @@ -0,0 +1,41 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test `destroy-node` event on WebAudioActor. + */ + +add_task(function* () { + let { target, front } = yield initBackend(DESTROY_NODES_URL); + + let [, , created] = yield Promise.all([ + front.setup({ reload: true }), + once(front, "start-context"), + // Should create dest, gain, and oscillator node and 10 + // disposable buffer nodes + getN(front, "create-node", 13) + ]); + + let waitUntilDestroyed = getN(front, "destroy-node", 10); + + // Force CC so we can ensure it's run to clear out dead AudioNodes + forceNodeCollection(); + + let destroyed = yield waitUntilDestroyed; + + destroyed.forEach((node, i) => { + ok(node.type, "AudioBufferSourceNode", "Only buffer nodes are destroyed"); + ok(actorIsInList(created, destroyed[i]), + "`destroy-node` called only on AudioNodes in current document."); + }); + + yield removeTab(target.tab); +}); + +function actorIsInList(list, actor) { + for (let i = 0; i < list.length; i++) { + if (list[i].actorID === actor.actorID) + return list[i]; + } + return null; +} diff --git a/devtools/client/webaudioeditor/test/browser_webaudio-actor-simple.js b/devtools/client/webaudioeditor/test/browser_webaudio-actor-simple.js new file mode 100644 index 000000000..28ff75651 --- /dev/null +++ b/devtools/client/webaudioeditor/test/browser_webaudio-actor-simple.js @@ -0,0 +1,30 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test basic communication of Web Audio actor + */ + +add_task(function* () { + let { target, 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") + ]); + + is(destNode.type, "AudioDestinationNode", "WebAudioActor:create-node returns AudioNodeActor for AudioDestination"); + is(oscNode.type, "OscillatorNode", "WebAudioActor:create-node returns AudioNodeActor"); + is(gainNode.type, "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)"); + + ({ 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); +}); diff --git a/devtools/client/webaudioeditor/test/doc_automation.html b/devtools/client/webaudioeditor/test/doc_automation.html new file mode 100644 index 000000000..6f074208c --- /dev/null +++ b/devtools/client/webaudioeditor/test/doc_automation.html @@ -0,0 +1,30 @@ +<!-- 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.frequency.setValueAtTime(0.2, 0); + osc.frequency.linearRampToValueAtTime(1, 0.3); + osc.frequency.exponentialRampToValueAtTime(0.75, 0.6); + osc.frequency.setValueCurveAtTime(new Float32Array([-1, 0, 1]), 0.7, 0.3); + osc.connect(gain); + gain.connect(ctx.destination); + osc.start(0); + </script> + </body> + +</html> diff --git a/devtools/client/webaudioeditor/test/doc_buffer-and-array.html b/devtools/client/webaudioeditor/test/doc_buffer-and-array.html new file mode 100644 index 000000000..ef4cec8b6 --- /dev/null +++ b/devtools/client/webaudioeditor/test/doc_buffer-and-array.html @@ -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/devtools/client/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> diff --git a/devtools/client/webaudioeditor/test/doc_bug_1112378.html b/devtools/client/webaudioeditor/test/doc_bug_1112378.html new file mode 100644 index 000000000..ecdfd7d63 --- /dev/null +++ b/devtools/client/webaudioeditor/test/doc_bug_1112378.html @@ -0,0 +1,57 @@ +<!-- 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(); + + function throwError () { + try { + osc.connect({}); + } catch (e) { + return { + lineNumber: e.lineNumber, + fileName: e.fileName, + columnNumber: e.columnNumber, + message: e.message, + instanceof: e instanceof TypeError, + stringified: e.toString(), + name: e.name + } + } + } + + function throwDOMException () { + try { + osc.frequency.setValueAtTime(0, -1); + } catch (e) { + return { + lineNumber: e.lineNumber, + columnNumber: e.columnNumber, + filename: e.filename, + message: e.message, + code: e.code, + result: e.result, + instanceof: e instanceof DOMException, + stringified: e.toString(), + name: e.name + } + } + } + + + </script> + </body> + +</html> diff --git a/devtools/client/webaudioeditor/test/doc_bug_1125817.html b/devtools/client/webaudioeditor/test/doc_bug_1125817.html new file mode 100644 index 000000000..49a2be11a --- /dev/null +++ b/devtools/client/webaudioeditor/test/doc_bug_1125817.html @@ -0,0 +1,23 @@ +<!-- 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(); + osc.frequency.value = 200; + osc.disconnect(); + </script> + </body> + +</html> diff --git a/devtools/client/webaudioeditor/test/doc_bug_1130901.html b/devtools/client/webaudioeditor/test/doc_bug_1130901.html new file mode 100644 index 000000000..1ce1ebf55 --- /dev/null +++ b/devtools/client/webaudioeditor/test/doc_bug_1130901.html @@ -0,0 +1,22 @@ +<!-- 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(); + ctx.createOscillator.call(ctx); + ctx.createGain.apply(ctx, []); + </script> + </body> + +</html> diff --git a/devtools/client/webaudioeditor/test/doc_bug_1141261.html b/devtools/client/webaudioeditor/test/doc_bug_1141261.html new file mode 100644 index 000000000..87c1210a4 --- /dev/null +++ b/devtools/client/webaudioeditor/test/doc_bug_1141261.html @@ -0,0 +1,25 @@ +<!-- 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(); + osc.connect(gain); + gain.connect(ctx.destination); + gain.disconnect(); + </script> + </body> + +</html> diff --git a/devtools/client/webaudioeditor/test/doc_complex-context.html b/devtools/client/webaudioeditor/test/doc_complex-context.html new file mode 100644 index 000000000..396bbce3f --- /dev/null +++ b/devtools/client/webaudioeditor/test/doc_complex-context.html @@ -0,0 +1,44 @@ +<!-- 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> diff --git a/devtools/client/webaudioeditor/test/doc_connect-multi-param.html b/devtools/client/webaudioeditor/test/doc_connect-multi-param.html new file mode 100644 index 000000000..ed4bd84e8 --- /dev/null +++ b/devtools/client/webaudioeditor/test/doc_connect-multi-param.html @@ -0,0 +1,32 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Web Audio Editor test page</title> + </head> + + <body> + + <script type="text/javascript;version=1.8"> + "use strict"; + + let ctx = new AudioContext(); + let carrier = ctx.createOscillator(); + let gain = ctx.createGain(); + let modulator = ctx.createOscillator(); + let modulator2 = ctx.createOscillator(); + carrier.connect(gain); + gain.connect(ctx.destination); + modulator.connect(gain.gain); + modulator2.connect(carrier.frequency); + modulator2.connect(carrier.detune); + modulator.start(0); + modulator2.start(0); + carrier.start(0); + </script> + </body> + +</html> diff --git a/devtools/client/webaudioeditor/test/doc_connect-param.html b/devtools/client/webaudioeditor/test/doc_connect-param.html new file mode 100644 index 000000000..9185c0b05 --- /dev/null +++ b/devtools/client/webaudioeditor/test/doc_connect-param.html @@ -0,0 +1,28 @@ +<!-- 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 carrier = ctx.createOscillator(); + let modulator = ctx.createOscillator(); + let gain = ctx.createGain(); + carrier.connect(gain); + gain.connect(ctx.destination); + modulator.connect(gain.gain); + modulator.start(0); + carrier.start(0); + </script> + </body> + +</html> diff --git a/devtools/client/webaudioeditor/test/doc_destroy-nodes.html b/devtools/client/webaudioeditor/test/doc_destroy-nodes.html new file mode 100644 index 000000000..98dfc9ad2 --- /dev/null +++ b/devtools/client/webaudioeditor/test/doc_destroy-nodes.html @@ -0,0 +1,36 @@ +<!-- 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"; + // Keep the nodes we want to GC alive until we are ready for them to + // be collected. We will zero this reference by force from the devtools + // side. + var keepAlive = []; + (function () { + let ctx = new AudioContext(); + let osc = ctx.createOscillator(); + let gain = ctx.createGain(); + + for (let i = 0; i < 10; i++) { + keepAlive.push(ctx.createBufferSource()); + } + + osc.connect(gain); + gain.connect(ctx.destination); + gain.gain.value = 0; + osc.start(); + })(); + </script> + </body> + +</html> diff --git a/devtools/client/webaudioeditor/test/doc_iframe-context.html b/devtools/client/webaudioeditor/test/doc_iframe-context.html new file mode 100644 index 000000000..a0a411a47 --- /dev/null +++ b/devtools/client/webaudioeditor/test/doc_iframe-context.html @@ -0,0 +1,14 @@ +<!-- 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 with an iframe</title> + </head> + + <body> + <iframe id="frame" src="doc_simple-context.html" /> + </body> +</html> diff --git a/devtools/client/webaudioeditor/test/doc_media-node-creation.html b/devtools/client/webaudioeditor/test/doc_media-node-creation.html new file mode 100644 index 000000000..d88233034 --- /dev/null +++ b/devtools/client/webaudioeditor/test/doc_media-node-creation.html @@ -0,0 +1,29 @@ +<!-- 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 audio = new Audio(); + let meNode, msNode, mdNode; + navigator.getUserMedia = navigator.getUserMedia || navigator.mozGetUserMedia; + + navigator.getUserMedia({ audio: true, fake: true }, stream => { + meNode = ctx.createMediaElementSource(audio); + msNode = ctx.createMediaStreamSource(stream); + mdNode = ctx.createMediaStreamDestination(); + }, () => {}); + </script> + </body> + +</html> diff --git a/devtools/client/webaudioeditor/test/doc_simple-context.html b/devtools/client/webaudioeditor/test/doc_simple-context.html new file mode 100644 index 000000000..89a84b882 --- /dev/null +++ b/devtools/client/webaudioeditor/test/doc_simple-context.html @@ -0,0 +1,33 @@ +<!-- 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; + + // Connect multiple times to test that it's disregarded. + osc.connect(gain); + gain.connect(ctx.destination); + gain.connect(ctx.destination); + gain.connect(ctx.destination); + gain.connect(ctx.destination); + gain.connect(ctx.destination); + gain.connect(ctx.destination); + osc.start(0); + </script> + </body> + +</html> diff --git a/devtools/client/webaudioeditor/test/doc_simple-node-creation.html b/devtools/client/webaudioeditor/test/doc_simple-node-creation.html new file mode 100644 index 000000000..e6dcf7b32 --- /dev/null +++ b/devtools/client/webaudioeditor/test/doc_simple-node-creation.html @@ -0,0 +1,28 @@ +<!-- 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", "createStereoPanner" + ]; + let nodes = NODE_CREATION_METHODS.map(method => ctx[method]()); + + </script> + </body> + +</html> diff --git a/devtools/client/webaudioeditor/test/head.js b/devtools/client/webaudioeditor/test/head.js new file mode 100644 index 000000000..7b0b0f01a --- /dev/null +++ b/devtools/client/webaudioeditor/test/head.js @@ -0,0 +1,556 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; + +var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {}); +var { Task } = require("devtools/shared/task"); +var Services = require("Services"); +var { gDevTools } = require("devtools/client/framework/devtools"); +var { TargetFactory } = require("devtools/client/framework/target"); +var { DebuggerServer } = require("devtools/server/main"); +var { generateUUID } = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator); + +var Promise = require("promise"); +var Services = require("Services"); +var { WebAudioFront } = require("devtools/shared/fronts/webaudio"); +var DevToolsUtils = require("devtools/shared/DevToolsUtils"); +var flags = require("devtools/shared/flags"); +var audioNodes = require("devtools/server/actors/utils/audionodes.json"); +var mm = null; + +const FRAME_SCRIPT_UTILS_URL = "chrome://devtools/content/shared/frame-script-utils.js"; +const EXAMPLE_URL = "http://example.com/browser/devtools/client/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"; +const MEDIA_NODES_URL = EXAMPLE_URL + "doc_media-node-creation.html"; +const BUFFER_AND_ARRAY_URL = EXAMPLE_URL + "doc_buffer-and-array.html"; +const DESTROY_NODES_URL = EXAMPLE_URL + "doc_destroy-nodes.html"; +const CONNECT_PARAM_URL = EXAMPLE_URL + "doc_connect-param.html"; +const CONNECT_MULTI_PARAM_URL = EXAMPLE_URL + "doc_connect-multi-param.html"; +const IFRAME_CONTEXT_URL = EXAMPLE_URL + "doc_iframe-context.html"; +const AUTOMATION_URL = EXAMPLE_URL + "doc_automation.html"; + +// Enable logging for all the tests. Both the debugger server and frontend will +// be affected by this pref. +var gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log"); +Services.prefs.setBoolPref("devtools.debugger.log", false); + +// All tests are asynchronous. +waitForExplicitFinish(); + +var gToolEnabled = Services.prefs.getBoolPref("devtools.webaudioeditor.enabled"); + +flags.testing = true; + +registerCleanupFunction(() => { + flags.testing = false; + info("finish() was called, cleaning up..."); + Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging); + Services.prefs.setBoolPref("devtools.webaudioeditor.enabled", gToolEnabled); + Cu.forceGC(); +}); + +/** + * Call manually in tests that use frame script utils after initializing + * the web audio editor. Call after init but before navigating to a different page. + */ +function loadFrameScripts() { + mm = gBrowser.selectedBrowser.messageManager; + mm.loadFrameScript(FRAME_SCRIPT_UTILS_URL, false); +} + +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; + + BrowserTestUtils.browserLoaded(linkedBrowser).then(function () { + info("Tab added and finished loading: " + aUrl); + deferred.resolve(tab); + }); + + 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 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); + info("Got event: '" + aEventName + "' on " + aTarget + "."); + deferred.resolve(...aArgs); + }, aUseCapture); + break; + } + } + + return deferred.promise; +} + +function reload(aTarget, aWaitForTargetEvent = "navigate") { + aTarget.activeTab.reload(); + return once(aTarget, aWaitForTargetEvent); +} + +function navigate(aTarget, aUrl, aWaitForTargetEvent = "navigate") { + executeSoon(() => aTarget.activeTab.navigateTo(aUrl)); + return once(aTarget, aWaitForTargetEvent); +} + +/** + * Call manually in tests that use frame script utils after initializing + * the shader editor. Call after init but before navigating to different pages. + */ +function loadFrameScripts() { + mm = gBrowser.selectedBrowser.messageManager; + mm.loadFrameScript(FRAME_SCRIPT_UTILS_URL, false); +} + +/** + * Adds a new tab, and instantiate a WebAudiFront object. + * This requires calling removeTab before the test ends. + */ +function initBackend(aUrl) { + info("Initializing a web audio editor front."); + + if (!DebuggerServer.initialized) { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + } + + return Task.spawn(function* () { + let tab = yield addTab(aUrl); + let target = TargetFactory.forTab(tab); + + yield target.makeRemote(); + + let front = new WebAudioFront(target.client, target.form); + return { target, front }; + }); +} + +/** + * Adds a new tab, and open the toolbox for that tab, selecting the audio editor + * panel. + * This requires calling teardown before the test ends. + */ +function initWebAudioEditor(aUrl) { + info("Initializing a web audio editor pane."); + + return Task.spawn(function* () { + let tab = yield addTab(aUrl); + let target = TargetFactory.forTab(tab); + + yield target.makeRemote(); + + Services.prefs.setBoolPref("devtools.webaudioeditor.enabled", true); + let toolbox = yield gDevTools.showToolbox(target, "webaudioeditor"); + let panel = toolbox.getCurrentPanel(); + return { target, panel, toolbox }; + }); +} + +/** + * Close the toolbox, destroying all panels, and remove the added test tabs. + */ +function teardown(aTarget) { + info("Destroying the web audio editor."); + + return gDevTools.closeToolbox(aTarget).then(() => { + while (gBrowser.tabs.length > 1) { + gBrowser.removeCurrentTab(); + } + }); +} + +// 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(); + info(`Waiting for ${count} ${eventName} events`); + front.on(eventName, function onEvent(...args) { + let actor = args[0]; + if (actors.length !== count) { + actors.push(spread ? args : actor); + } + info(`Got ${actors.length} / ${count} ${eventName} events`); + 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); } + +/** + * Waits for the UI_GRAPH_RENDERED event to fire, but only + * resolves when the graph was rendered with the correct count of + * nodes and edges. + */ +function waitForGraphRendered(front, nodeCount, edgeCount, paramEdgeCount) { + let deferred = Promise.defer(); + let eventName = front.EVENTS.UI_GRAPH_RENDERED; + info(`Wait for graph rendered with ${nodeCount} nodes, ${edgeCount} edges`); + front.on(eventName, function onGraphRendered(_, nodes, edges, pEdges) { + let paramEdgesDone = paramEdgeCount != null ? paramEdgeCount === pEdges : true; + info(`Got graph rendered with ${nodes} / ${nodeCount} nodes, ` + + `${edges} / ${edgeCount} edges`); + if (nodes === nodeCount && edges === edgeCount && paramEdgesDone) { + front.off(eventName, onGraphRendered); + deferred.resolve(); + } + }); + return deferred.promise; +} + +function checkVariableView(view, index, hash, description = "") { + info("Checking Variable View"); + let scope = view.getScopeAtIndex(index); + let variables = Object.keys(hash); + + // If node shouldn't display any properties, ensure that the 'empty' message is + // visible + if (!variables.length) { + ok(isVisible(scope.window.$("#properties-empty")), + description + " should show the empty properties tab."); + return; + } + + // Otherwise, iterate over expected properties + variables.forEach(variable => { + let aVar = scope.get(variable); + is(aVar.target.querySelector(".name").getAttribute("value"), variable, + "Correct property name for " + variable); + 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 { + is(value, hash[variable], + "Correct property value of " + hash[variable] + " for " + variable + " " + description); + } + }); +} + +function modifyVariableView(win, view, index, prop, value) { + let deferred = Promise.defer(); + let scope = view.getScopeAtIndex(index); + let aVar = scope.get(prop); + scope.expand(); + + win.on(win.EVENTS.UI_SET_PARAM, handleSetting); + win.on(win.EVENTS.UI_SET_PARAM_ERROR, handleSetting); + + // Focus and select the variable to begin editing + win.focus(); + aVar.focus(); + EventUtils.sendKey("RETURN", win); + + // Must wait for the scope DOM to be available to receive + // events + executeSoon(() => { + info("Setting " + value + " for " + prop + "...."); + for (let c of (value + "")) { + EventUtils.synthesizeKey(c, {}, win); + } + EventUtils.sendKey("RETURN", win); + }); + + function handleSetting(eventName) { + win.off(win.EVENTS.UI_SET_PARAM, handleSetting); + win.off(win.EVENTS.UI_SET_PARAM_ERROR, handleSetting); + if (eventName === win.EVENTS.UI_SET_PARAM) + deferred.resolve(); + if (eventName === win.EVENTS.UI_SET_PARAM_ERROR) + deferred.reject(); + } + + return deferred.promise; +} + +function findGraphEdge(win, source, target, param) { + let selector = ".edgePaths .edgePath[data-source='" + source + "'][data-target='" + target + "']"; + if (param) { + selector += "[data-param='" + param + "']"; + } + return win.document.querySelector(selector); +} + +function findGraphNode(win, node) { + let selector = ".nodes > g[data-id='" + node + "']"; + return win.document.querySelector(selector); +} + +function click(win, element) { + EventUtils.sendMouseEvent({ type: "click" }, element, win); +} + +function mouseOver(win, element) { + EventUtils.sendMouseEvent({ type: "mouseover" }, element, win); +} + +function command(button) { + let ev = button.ownerDocument.createEvent("XULCommandEvent"); + ev.initCommandEvent("command", true, true, button.ownerDocument.defaultView, 0, false, false, false, false, null); + button.dispatchEvent(ev); +} + +function isVisible(element) { + return !element.getAttribute("hidden"); +} + +/** + * Used in debugging, returns a promise that resolves in `n` milliseconds. + */ +function wait(n) { + let { promise, resolve } = Promise.defer(); + setTimeout(resolve, n); + info("Waiting " + n / 1000 + " seconds."); + return promise; +} + +/** + * Clicks a graph node based on actorID or passing in an element. + * Returns a promise that resolves once UI_INSPECTOR_NODE_SET is fired and + * the tabs have rendered, completing all RDP requests for the node. + */ +function clickGraphNode(panelWin, el, waitForToggle = false) { + let { promise, resolve } = Promise.defer(); + let promises = [ + once(panelWin, panelWin.EVENTS.UI_INSPECTOR_NODE_SET), + once(panelWin, panelWin.EVENTS.UI_PROPERTIES_TAB_RENDERED), + once(panelWin, panelWin.EVENTS.UI_AUTOMATION_TAB_RENDERED) + ]; + + if (waitForToggle) { + promises.push(once(panelWin, panelWin.EVENTS.UI_INSPECTOR_TOGGLED)); + } + + // Use `el` as the element if it is one, otherwise + // assume it's an ID and find the related graph node + let element = el.tagName ? el : findGraphNode(panelWin, el); + click(panelWin, element); + + return Promise.all(promises); +} + +/** + * 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; + } +} + +/** + * Counts how many nodes and edges are currently in the graph. + */ +function countGraphObjects(win) { + return { + nodes: win.document.querySelectorAll(".nodes > .audionode").length, + edges: win.document.querySelectorAll(".edgePaths > .edgePath").length + }; +} + +/** +* Forces cycle collection and GC, used in AudioNode destruction tests. +*/ +function forceNodeCollection() { + ContentTask.spawn(gBrowser.selectedBrowser, {}, function*() { + // Kill the reference keeping stuff alive. + content.wrappedJSObject.keepAlive = null; + + // Collect the now-deceased nodes. + Cu.forceGC(); + Cu.forceCC(); + Cu.forceGC(); + Cu.forceCC(); + }); +} + +/** + * Takes a `values` array of automation value entries, + * looking for the value at `time` seconds, checking + * to see if the value is close to `expected`. + */ +function checkAutomationValue(values, time, expected) { + // Remain flexible on values as we can approximate points + let EPSILON = 0.01; + + let value = getValueAt(values, time); + ok(Math.abs(value - expected) < EPSILON, "Timeline value at " + time + " with value " + value + " should have value very close to " + expected); + + /** + * Entries are ordered in `values` according to time, so if we can't find an exact point + * on a time of interest, return the point in between the threshold. This should + * get us a very close value. + */ + function getValueAt(values, time) { + for (let i = 0; i < values.length; i++) { + if (values[i].delta === time) { + return values[i].value; + } + if (values[i].delta > time) { + return (values[i - 1].value + values[i].value) / 2; + } + } + return values[values.length - 1].value; + } +} + +/** + * Wait for all inspector tabs to complete rendering. + */ +function waitForInspectorRender(panelWin, EVENTS) { + return Promise.all([ + once(panelWin, EVENTS.UI_PROPERTIES_TAB_RENDERED), + once(panelWin, EVENTS.UI_AUTOMATION_TAB_RENDERED) + ]); +} + +/** + * Takes a string `script` and evaluates it directly in the content + * in potentially a different process. + */ +function evalInDebuggee(script) { + let deferred = Promise.defer(); + + if (!mm) { + throw new Error("`loadFrameScripts()` must be called when using MessageManager."); + } + + let id = generateUUID().toString(); + mm.sendAsyncMessage("devtools:test:eval", { script: script, id: id }); + mm.addMessageListener("devtools:test:eval:response", handler); + + function handler({ data }) { + if (id !== data.id) { + return; + } + + mm.removeMessageListener("devtools:test:eval:response", handler); + deferred.resolve(data.value); + } + + return deferred.promise; +} + +/** + * Takes an AudioNode type and returns it's properties (from audionode.json) + * as keys and their default values as keys + */ +function nodeDefaultValues(nodeName) { + let fn = NODE_CONSTRUCTORS[nodeName]; + + if (typeof fn === "undefined") return {}; + + let init = nodeName === "AudioDestinationNode" ? "destination" : `create${fn}()`; + + let definition = JSON.stringify(audioNodes[nodeName].properties); + + let evalNode = evalInDebuggee(` + let ins = (new AudioContext()).${init}; + let props = ${definition}; + let answer = {}; + + for(let k in props) { + if (props[k].param) { + answer[k] = ins[k].defaultValue; + } else if (typeof ins[k] === "object" && ins[k] !== null) { + answer[k] = ins[k].toString().slice(8, -1); + } else { + answer[k] = ins[k]; + } + } + answer;`); + + return evalNode; +} + +const NODE_CONSTRUCTORS = { + "MediaStreamAudioDestinationNode": "MediaStreamDestination", + "AudioBufferSourceNode": "BufferSource", + "ScriptProcessorNode": "ScriptProcessor", + "AnalyserNode": "Analyser", + "GainNode": "Gain", + "DelayNode": "Delay", + "BiquadFilterNode": "BiquadFilter", + "WaveShaperNode": "WaveShaper", + "PannerNode": "Panner", + "ConvolverNode": "Convolver", + "ChannelSplitterNode": "ChannelSplitter", + "ChannelMergerNode": "ChannelMerger", + "DynamicsCompressorNode": "DynamicsCompressor", + "OscillatorNode": "Oscillator", + "StereoPannerNode": "StereoPanner" +}; |