1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/devtools/webaudioeditor/test/head.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,308 @@ 1.4 +/* Any copyright is dedicated to the Public Domain. 1.5 + http://creativecommons.org/publicdomain/zero/1.0/ */ 1.6 +"use strict"; 1.7 + 1.8 +const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; 1.9 + 1.10 +let { Services } = Cu.import("resource://gre/modules/Services.jsm", {}); 1.11 + 1.12 +// Enable logging for all the tests. Both the debugger server and frontend will 1.13 +// be affected by this pref. 1.14 +let gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log"); 1.15 +Services.prefs.setBoolPref("devtools.debugger.log", true); 1.16 + 1.17 +let { Task } = Cu.import("resource://gre/modules/Task.jsm", {}); 1.18 +let { Promise } = Cu.import("resource://gre/modules/Promise.jsm", {}); 1.19 +let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {}); 1.20 +let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); 1.21 +let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {}); 1.22 + 1.23 +let { WebAudioFront } = devtools.require("devtools/server/actors/webaudio"); 1.24 +let TargetFactory = devtools.TargetFactory; 1.25 + 1.26 +const EXAMPLE_URL = "http://example.com/browser/browser/devtools/webaudioeditor/test/"; 1.27 +const SIMPLE_CONTEXT_URL = EXAMPLE_URL + "doc_simple-context.html"; 1.28 +const COMPLEX_CONTEXT_URL = EXAMPLE_URL + "doc_complex-context.html"; 1.29 +const SIMPLE_NODES_URL = EXAMPLE_URL + "doc_simple-node-creation.html"; 1.30 + 1.31 +// All tests are asynchronous. 1.32 +waitForExplicitFinish(); 1.33 + 1.34 +let gToolEnabled = Services.prefs.getBoolPref("devtools.webaudioeditor.enabled"); 1.35 + 1.36 +registerCleanupFunction(() => { 1.37 + info("finish() was called, cleaning up..."); 1.38 + Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging); 1.39 + Services.prefs.setBoolPref("devtools.webaudioeditor.enabled", gToolEnabled); 1.40 + Cu.forceGC(); 1.41 +}); 1.42 + 1.43 +function addTab(aUrl, aWindow) { 1.44 + info("Adding tab: " + aUrl); 1.45 + 1.46 + let deferred = Promise.defer(); 1.47 + let targetWindow = aWindow || window; 1.48 + let targetBrowser = targetWindow.gBrowser; 1.49 + 1.50 + targetWindow.focus(); 1.51 + let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl); 1.52 + let linkedBrowser = tab.linkedBrowser; 1.53 + 1.54 + linkedBrowser.addEventListener("load", function onLoad() { 1.55 + linkedBrowser.removeEventListener("load", onLoad, true); 1.56 + info("Tab added and finished loading: " + aUrl); 1.57 + deferred.resolve(tab); 1.58 + }, true); 1.59 + 1.60 + return deferred.promise; 1.61 +} 1.62 + 1.63 +function removeTab(aTab, aWindow) { 1.64 + info("Removing tab."); 1.65 + 1.66 + let deferred = Promise.defer(); 1.67 + let targetWindow = aWindow || window; 1.68 + let targetBrowser = targetWindow.gBrowser; 1.69 + let tabContainer = targetBrowser.tabContainer; 1.70 + 1.71 + tabContainer.addEventListener("TabClose", function onClose(aEvent) { 1.72 + tabContainer.removeEventListener("TabClose", onClose, false); 1.73 + info("Tab removed and finished closing."); 1.74 + deferred.resolve(); 1.75 + }, false); 1.76 + 1.77 + targetBrowser.removeTab(aTab); 1.78 + return deferred.promise; 1.79 +} 1.80 + 1.81 +function handleError(aError) { 1.82 + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); 1.83 + finish(); 1.84 +} 1.85 + 1.86 +function once(aTarget, aEventName, aUseCapture = false) { 1.87 + info("Waiting for event: '" + aEventName + "' on " + aTarget + "."); 1.88 + 1.89 + let deferred = Promise.defer(); 1.90 + 1.91 + for (let [add, remove] of [ 1.92 + ["on", "off"], // Use event emitter before DOM events for consistency 1.93 + ["addEventListener", "removeEventListener"], 1.94 + ["addListener", "removeListener"] 1.95 + ]) { 1.96 + if ((add in aTarget) && (remove in aTarget)) { 1.97 + aTarget[add](aEventName, function onEvent(...aArgs) { 1.98 + aTarget[remove](aEventName, onEvent, aUseCapture); 1.99 + deferred.resolve(...aArgs); 1.100 + }, aUseCapture); 1.101 + break; 1.102 + } 1.103 + } 1.104 + 1.105 + return deferred.promise; 1.106 +} 1.107 + 1.108 +function reload(aTarget, aWaitForTargetEvent = "navigate") { 1.109 + aTarget.activeTab.reload(); 1.110 + return once(aTarget, aWaitForTargetEvent); 1.111 +} 1.112 + 1.113 +function test () { 1.114 + Task.spawn(spawnTest).then(finish, handleError); 1.115 +} 1.116 + 1.117 +function initBackend(aUrl) { 1.118 + info("Initializing a web audio editor front."); 1.119 + 1.120 + if (!DebuggerServer.initialized) { 1.121 + DebuggerServer.init(() => true); 1.122 + DebuggerServer.addBrowserActors(); 1.123 + } 1.124 + 1.125 + return Task.spawn(function*() { 1.126 + let tab = yield addTab(aUrl); 1.127 + let target = TargetFactory.forTab(tab); 1.128 + let debuggee = target.window.wrappedJSObject; 1.129 + 1.130 + yield target.makeRemote(); 1.131 + 1.132 + let front = new WebAudioFront(target.client, target.form); 1.133 + return [target, debuggee, front]; 1.134 + }); 1.135 +} 1.136 + 1.137 +function initWebAudioEditor(aUrl) { 1.138 + info("Initializing a web audio editor pane."); 1.139 + 1.140 + return Task.spawn(function*() { 1.141 + let tab = yield addTab(aUrl); 1.142 + let target = TargetFactory.forTab(tab); 1.143 + let debuggee = target.window.wrappedJSObject; 1.144 + 1.145 + yield target.makeRemote(); 1.146 + 1.147 + Services.prefs.setBoolPref("devtools.webaudioeditor.enabled", true); 1.148 + let toolbox = yield gDevTools.showToolbox(target, "webaudioeditor"); 1.149 + let panel = toolbox.getCurrentPanel(); 1.150 + return [target, debuggee, panel]; 1.151 + }); 1.152 +} 1.153 + 1.154 +function teardown(aPanel) { 1.155 + info("Destroying the web audio editor."); 1.156 + 1.157 + return Promise.all([ 1.158 + once(aPanel, "destroyed"), 1.159 + removeTab(aPanel.target.tab) 1.160 + ]).then(() => { 1.161 + let gBrowser = window.gBrowser; 1.162 + while (gBrowser.tabs.length > 1) { 1.163 + gBrowser.removeCurrentTab(); 1.164 + } 1.165 + gBrowser = null; 1.166 + }); 1.167 +} 1.168 + 1.169 +// Due to web audio will fire most events synchronously back-to-back, 1.170 +// and we can't yield them in a chain without missing actors, this allows 1.171 +// us to listen for `n` events and return a promise resolving to them. 1.172 +// 1.173 +// Takes a `front` object that is an event emitter, the number of 1.174 +// programs that should be listened to and waited on, and an optional 1.175 +// `onAdd` function that calls with the entire actors array on program link 1.176 +function getN (front, eventName, count, spread) { 1.177 + let actors = []; 1.178 + let deferred = Promise.defer(); 1.179 + front.on(eventName, function onEvent (...args) { 1.180 + let actor = args[0]; 1.181 + if (actors.length !== count) { 1.182 + actors.push(spread ? args : actor); 1.183 + } 1.184 + if (actors.length === count) { 1.185 + front.off(eventName, onEvent); 1.186 + deferred.resolve(actors); 1.187 + } 1.188 + }); 1.189 + return deferred.promise; 1.190 +} 1.191 + 1.192 +function get (front, eventName) { return getN(front, eventName, 1); } 1.193 +function get2 (front, eventName) { return getN(front, eventName, 2); } 1.194 +function get3 (front, eventName) { return getN(front, eventName, 3); } 1.195 +function getSpread (front, eventName) { return getN(front, eventName, 1, true); } 1.196 +function get2Spread (front, eventName) { return getN(front, eventName, 2, true); } 1.197 +function get3Spread (front, eventName) { return getN(front, eventName, 3, true); } 1.198 +function getNSpread (front, eventName, count) { return getN(front, eventName, count, true); } 1.199 + 1.200 +/** 1.201 + * Waits for the UI_GRAPH_RENDERED event to fire, but only 1.202 + * resolves when the graph was rendered with the correct count of 1.203 + * nodes and edges. 1.204 + */ 1.205 +function waitForGraphRendered (front, nodeCount, edgeCount) { 1.206 + let deferred = Promise.defer(); 1.207 + let eventName = front.EVENTS.UI_GRAPH_RENDERED; 1.208 + front.on(eventName, function onGraphRendered (_, nodes, edges) { 1.209 + if (nodes === nodeCount && edges === edgeCount) { 1.210 + front.off(eventName, onGraphRendered); 1.211 + deferred.resolve(); 1.212 + } 1.213 + }); 1.214 + return deferred.promise; 1.215 +} 1.216 + 1.217 +function checkVariableView (view, index, hash) { 1.218 + let scope = view.getScopeAtIndex(index); 1.219 + let variables = Object.keys(hash); 1.220 + variables.forEach(variable => { 1.221 + let aVar = scope.get(variable); 1.222 + is(aVar.target.querySelector(".name").getAttribute("value"), variable, 1.223 + "Correct property name for " + variable); 1.224 + is(aVar.target.querySelector(".value").getAttribute("value"), hash[variable], 1.225 + "Correct property value of " + hash[variable] + " for " + variable); 1.226 + }); 1.227 +} 1.228 + 1.229 +function modifyVariableView (win, view, index, prop, value) { 1.230 + let deferred = Promise.defer(); 1.231 + let scope = view.getScopeAtIndex(index); 1.232 + let aVar = scope.get(prop); 1.233 + scope.expand(); 1.234 + 1.235 + // Must wait for the scope DOM to be available to receive 1.236 + // events 1.237 + executeSoon(() => { 1.238 + let varValue = aVar.target.querySelector(".title > .value"); 1.239 + EventUtils.sendMouseEvent({ type: "mousedown" }, varValue, win); 1.240 + 1.241 + win.on(win.EVENTS.UI_SET_PARAM, handleSetting); 1.242 + win.on(win.EVENTS.UI_SET_PARAM_ERROR, handleSetting); 1.243 + 1.244 + info("Setting " + value + " for " + prop + "...."); 1.245 + let varInput = aVar.target.querySelector(".title > .element-value-input"); 1.246 + setText(varInput, value); 1.247 + EventUtils.sendKey("RETURN", win); 1.248 + }); 1.249 + 1.250 + function handleSetting (eventName) { 1.251 + win.off(win.EVENTS.UI_SET_PARAM, handleSetting); 1.252 + win.off(win.EVENTS.UI_SET_PARAM_ERROR, handleSetting); 1.253 + if (eventName === win.EVENTS.UI_SET_PARAM) 1.254 + deferred.resolve(); 1.255 + if (eventName === win.EVENTS.UI_SET_PARAM_ERROR) 1.256 + deferred.reject(); 1.257 + } 1.258 + 1.259 + return deferred.promise; 1.260 +} 1.261 + 1.262 +function clearText (aElement) { 1.263 + info("Clearing text..."); 1.264 + aElement.focus(); 1.265 + aElement.value = ""; 1.266 +} 1.267 + 1.268 +function setText (aElement, aText) { 1.269 + clearText(aElement); 1.270 + info("Setting text: " + aText); 1.271 + aElement.value = aText; 1.272 +} 1.273 + 1.274 +function findGraphEdge (win, source, target) { 1.275 + let selector = ".edgePaths .edgePath[data-source='" + source + "'][data-target='" + target + "']"; 1.276 + return win.document.querySelector(selector); 1.277 +} 1.278 + 1.279 +function findGraphNode (win, node) { 1.280 + let selector = ".nodes > g[data-id='" + node + "']"; 1.281 + return win.document.querySelector(selector); 1.282 +} 1.283 + 1.284 +function click (win, element) { 1.285 + EventUtils.sendMouseEvent({ type: "click" }, element, win); 1.286 +} 1.287 + 1.288 +function mouseOver (win, element) { 1.289 + EventUtils.sendMouseEvent({ type: "mouseover" }, element, win); 1.290 +} 1.291 + 1.292 +/** 1.293 + * List of audio node properties to test against expectations of the AudioNode actor 1.294 + */ 1.295 + 1.296 +const NODE_PROPERTIES = { 1.297 + "OscillatorNode": ["type", "frequency", "detune"], 1.298 + "GainNode": ["gain"], 1.299 + "DelayNode": ["delayTime"], 1.300 + "AudioBufferSourceNode": ["buffer", "playbackRate", "loop", "loopStart", "loopEnd"], 1.301 + "ScriptProcessorNode": ["bufferSize"], 1.302 + "PannerNode": ["panningModel", "distanceModel", "refDistance", "maxDistance", "rolloffFactor", "coneInnerAngle", "coneOuterAngle", "coneOuterGain"], 1.303 + "ConvolverNode": ["buffer", "normalize"], 1.304 + "DynamicsCompressorNode": ["threshold", "knee", "ratio", "reduction", "attack", "release"], 1.305 + "BiquadFilterNode": ["type", "frequency", "Q", "detune", "gain"], 1.306 + "WaveShaperNode": ["curve", "oversample"], 1.307 + "AnalyserNode": ["fftSize", "minDecibels", "maxDecibels", "smoothingTimeConstraint", "frequencyBinCount"], 1.308 + "AudioDestinationNode": [], 1.309 + "ChannelSplitterNode": [], 1.310 + "ChannelMergerNode": [] 1.311 +};