Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* Any copyright is dedicated to the Public Domain. |
michael@0 | 2 | http://creativecommons.org/publicdomain/zero/1.0/ */ |
michael@0 | 3 | "use strict"; |
michael@0 | 4 | |
michael@0 | 5 | const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; |
michael@0 | 6 | |
michael@0 | 7 | let { Services } = Cu.import("resource://gre/modules/Services.jsm", {}); |
michael@0 | 8 | |
michael@0 | 9 | // Enable logging for all the tests. Both the debugger server and frontend will |
michael@0 | 10 | // be affected by this pref. |
michael@0 | 11 | let gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log"); |
michael@0 | 12 | Services.prefs.setBoolPref("devtools.debugger.log", true); |
michael@0 | 13 | |
michael@0 | 14 | let { Task } = Cu.import("resource://gre/modules/Task.jsm", {}); |
michael@0 | 15 | let { Promise } = Cu.import("resource://gre/modules/Promise.jsm", {}); |
michael@0 | 16 | let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {}); |
michael@0 | 17 | let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); |
michael@0 | 18 | let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {}); |
michael@0 | 19 | |
michael@0 | 20 | let { WebAudioFront } = devtools.require("devtools/server/actors/webaudio"); |
michael@0 | 21 | let TargetFactory = devtools.TargetFactory; |
michael@0 | 22 | |
michael@0 | 23 | const EXAMPLE_URL = "http://example.com/browser/browser/devtools/webaudioeditor/test/"; |
michael@0 | 24 | const SIMPLE_CONTEXT_URL = EXAMPLE_URL + "doc_simple-context.html"; |
michael@0 | 25 | const COMPLEX_CONTEXT_URL = EXAMPLE_URL + "doc_complex-context.html"; |
michael@0 | 26 | const SIMPLE_NODES_URL = EXAMPLE_URL + "doc_simple-node-creation.html"; |
michael@0 | 27 | |
michael@0 | 28 | // All tests are asynchronous. |
michael@0 | 29 | waitForExplicitFinish(); |
michael@0 | 30 | |
michael@0 | 31 | let gToolEnabled = Services.prefs.getBoolPref("devtools.webaudioeditor.enabled"); |
michael@0 | 32 | |
michael@0 | 33 | registerCleanupFunction(() => { |
michael@0 | 34 | info("finish() was called, cleaning up..."); |
michael@0 | 35 | Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging); |
michael@0 | 36 | Services.prefs.setBoolPref("devtools.webaudioeditor.enabled", gToolEnabled); |
michael@0 | 37 | Cu.forceGC(); |
michael@0 | 38 | }); |
michael@0 | 39 | |
michael@0 | 40 | function addTab(aUrl, aWindow) { |
michael@0 | 41 | info("Adding tab: " + aUrl); |
michael@0 | 42 | |
michael@0 | 43 | let deferred = Promise.defer(); |
michael@0 | 44 | let targetWindow = aWindow || window; |
michael@0 | 45 | let targetBrowser = targetWindow.gBrowser; |
michael@0 | 46 | |
michael@0 | 47 | targetWindow.focus(); |
michael@0 | 48 | let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl); |
michael@0 | 49 | let linkedBrowser = tab.linkedBrowser; |
michael@0 | 50 | |
michael@0 | 51 | linkedBrowser.addEventListener("load", function onLoad() { |
michael@0 | 52 | linkedBrowser.removeEventListener("load", onLoad, true); |
michael@0 | 53 | info("Tab added and finished loading: " + aUrl); |
michael@0 | 54 | deferred.resolve(tab); |
michael@0 | 55 | }, true); |
michael@0 | 56 | |
michael@0 | 57 | return deferred.promise; |
michael@0 | 58 | } |
michael@0 | 59 | |
michael@0 | 60 | function removeTab(aTab, aWindow) { |
michael@0 | 61 | info("Removing tab."); |
michael@0 | 62 | |
michael@0 | 63 | let deferred = Promise.defer(); |
michael@0 | 64 | let targetWindow = aWindow || window; |
michael@0 | 65 | let targetBrowser = targetWindow.gBrowser; |
michael@0 | 66 | let tabContainer = targetBrowser.tabContainer; |
michael@0 | 67 | |
michael@0 | 68 | tabContainer.addEventListener("TabClose", function onClose(aEvent) { |
michael@0 | 69 | tabContainer.removeEventListener("TabClose", onClose, false); |
michael@0 | 70 | info("Tab removed and finished closing."); |
michael@0 | 71 | deferred.resolve(); |
michael@0 | 72 | }, false); |
michael@0 | 73 | |
michael@0 | 74 | targetBrowser.removeTab(aTab); |
michael@0 | 75 | return deferred.promise; |
michael@0 | 76 | } |
michael@0 | 77 | |
michael@0 | 78 | function handleError(aError) { |
michael@0 | 79 | ok(false, "Got an error: " + aError.message + "\n" + aError.stack); |
michael@0 | 80 | finish(); |
michael@0 | 81 | } |
michael@0 | 82 | |
michael@0 | 83 | function once(aTarget, aEventName, aUseCapture = false) { |
michael@0 | 84 | info("Waiting for event: '" + aEventName + "' on " + aTarget + "."); |
michael@0 | 85 | |
michael@0 | 86 | let deferred = Promise.defer(); |
michael@0 | 87 | |
michael@0 | 88 | for (let [add, remove] of [ |
michael@0 | 89 | ["on", "off"], // Use event emitter before DOM events for consistency |
michael@0 | 90 | ["addEventListener", "removeEventListener"], |
michael@0 | 91 | ["addListener", "removeListener"] |
michael@0 | 92 | ]) { |
michael@0 | 93 | if ((add in aTarget) && (remove in aTarget)) { |
michael@0 | 94 | aTarget[add](aEventName, function onEvent(...aArgs) { |
michael@0 | 95 | aTarget[remove](aEventName, onEvent, aUseCapture); |
michael@0 | 96 | deferred.resolve(...aArgs); |
michael@0 | 97 | }, aUseCapture); |
michael@0 | 98 | break; |
michael@0 | 99 | } |
michael@0 | 100 | } |
michael@0 | 101 | |
michael@0 | 102 | return deferred.promise; |
michael@0 | 103 | } |
michael@0 | 104 | |
michael@0 | 105 | function reload(aTarget, aWaitForTargetEvent = "navigate") { |
michael@0 | 106 | aTarget.activeTab.reload(); |
michael@0 | 107 | return once(aTarget, aWaitForTargetEvent); |
michael@0 | 108 | } |
michael@0 | 109 | |
michael@0 | 110 | function test () { |
michael@0 | 111 | Task.spawn(spawnTest).then(finish, handleError); |
michael@0 | 112 | } |
michael@0 | 113 | |
michael@0 | 114 | function initBackend(aUrl) { |
michael@0 | 115 | info("Initializing a web audio editor front."); |
michael@0 | 116 | |
michael@0 | 117 | if (!DebuggerServer.initialized) { |
michael@0 | 118 | DebuggerServer.init(() => true); |
michael@0 | 119 | DebuggerServer.addBrowserActors(); |
michael@0 | 120 | } |
michael@0 | 121 | |
michael@0 | 122 | return Task.spawn(function*() { |
michael@0 | 123 | let tab = yield addTab(aUrl); |
michael@0 | 124 | let target = TargetFactory.forTab(tab); |
michael@0 | 125 | let debuggee = target.window.wrappedJSObject; |
michael@0 | 126 | |
michael@0 | 127 | yield target.makeRemote(); |
michael@0 | 128 | |
michael@0 | 129 | let front = new WebAudioFront(target.client, target.form); |
michael@0 | 130 | return [target, debuggee, front]; |
michael@0 | 131 | }); |
michael@0 | 132 | } |
michael@0 | 133 | |
michael@0 | 134 | function initWebAudioEditor(aUrl) { |
michael@0 | 135 | info("Initializing a web audio editor pane."); |
michael@0 | 136 | |
michael@0 | 137 | return Task.spawn(function*() { |
michael@0 | 138 | let tab = yield addTab(aUrl); |
michael@0 | 139 | let target = TargetFactory.forTab(tab); |
michael@0 | 140 | let debuggee = target.window.wrappedJSObject; |
michael@0 | 141 | |
michael@0 | 142 | yield target.makeRemote(); |
michael@0 | 143 | |
michael@0 | 144 | Services.prefs.setBoolPref("devtools.webaudioeditor.enabled", true); |
michael@0 | 145 | let toolbox = yield gDevTools.showToolbox(target, "webaudioeditor"); |
michael@0 | 146 | let panel = toolbox.getCurrentPanel(); |
michael@0 | 147 | return [target, debuggee, panel]; |
michael@0 | 148 | }); |
michael@0 | 149 | } |
michael@0 | 150 | |
michael@0 | 151 | function teardown(aPanel) { |
michael@0 | 152 | info("Destroying the web audio editor."); |
michael@0 | 153 | |
michael@0 | 154 | return Promise.all([ |
michael@0 | 155 | once(aPanel, "destroyed"), |
michael@0 | 156 | removeTab(aPanel.target.tab) |
michael@0 | 157 | ]).then(() => { |
michael@0 | 158 | let gBrowser = window.gBrowser; |
michael@0 | 159 | while (gBrowser.tabs.length > 1) { |
michael@0 | 160 | gBrowser.removeCurrentTab(); |
michael@0 | 161 | } |
michael@0 | 162 | gBrowser = null; |
michael@0 | 163 | }); |
michael@0 | 164 | } |
michael@0 | 165 | |
michael@0 | 166 | // Due to web audio will fire most events synchronously back-to-back, |
michael@0 | 167 | // and we can't yield them in a chain without missing actors, this allows |
michael@0 | 168 | // us to listen for `n` events and return a promise resolving to them. |
michael@0 | 169 | // |
michael@0 | 170 | // Takes a `front` object that is an event emitter, the number of |
michael@0 | 171 | // programs that should be listened to and waited on, and an optional |
michael@0 | 172 | // `onAdd` function that calls with the entire actors array on program link |
michael@0 | 173 | function getN (front, eventName, count, spread) { |
michael@0 | 174 | let actors = []; |
michael@0 | 175 | let deferred = Promise.defer(); |
michael@0 | 176 | front.on(eventName, function onEvent (...args) { |
michael@0 | 177 | let actor = args[0]; |
michael@0 | 178 | if (actors.length !== count) { |
michael@0 | 179 | actors.push(spread ? args : actor); |
michael@0 | 180 | } |
michael@0 | 181 | if (actors.length === count) { |
michael@0 | 182 | front.off(eventName, onEvent); |
michael@0 | 183 | deferred.resolve(actors); |
michael@0 | 184 | } |
michael@0 | 185 | }); |
michael@0 | 186 | return deferred.promise; |
michael@0 | 187 | } |
michael@0 | 188 | |
michael@0 | 189 | function get (front, eventName) { return getN(front, eventName, 1); } |
michael@0 | 190 | function get2 (front, eventName) { return getN(front, eventName, 2); } |
michael@0 | 191 | function get3 (front, eventName) { return getN(front, eventName, 3); } |
michael@0 | 192 | function getSpread (front, eventName) { return getN(front, eventName, 1, true); } |
michael@0 | 193 | function get2Spread (front, eventName) { return getN(front, eventName, 2, true); } |
michael@0 | 194 | function get3Spread (front, eventName) { return getN(front, eventName, 3, true); } |
michael@0 | 195 | function getNSpread (front, eventName, count) { return getN(front, eventName, count, true); } |
michael@0 | 196 | |
michael@0 | 197 | /** |
michael@0 | 198 | * Waits for the UI_GRAPH_RENDERED event to fire, but only |
michael@0 | 199 | * resolves when the graph was rendered with the correct count of |
michael@0 | 200 | * nodes and edges. |
michael@0 | 201 | */ |
michael@0 | 202 | function waitForGraphRendered (front, nodeCount, edgeCount) { |
michael@0 | 203 | let deferred = Promise.defer(); |
michael@0 | 204 | let eventName = front.EVENTS.UI_GRAPH_RENDERED; |
michael@0 | 205 | front.on(eventName, function onGraphRendered (_, nodes, edges) { |
michael@0 | 206 | if (nodes === nodeCount && edges === edgeCount) { |
michael@0 | 207 | front.off(eventName, onGraphRendered); |
michael@0 | 208 | deferred.resolve(); |
michael@0 | 209 | } |
michael@0 | 210 | }); |
michael@0 | 211 | return deferred.promise; |
michael@0 | 212 | } |
michael@0 | 213 | |
michael@0 | 214 | function checkVariableView (view, index, hash) { |
michael@0 | 215 | let scope = view.getScopeAtIndex(index); |
michael@0 | 216 | let variables = Object.keys(hash); |
michael@0 | 217 | variables.forEach(variable => { |
michael@0 | 218 | let aVar = scope.get(variable); |
michael@0 | 219 | is(aVar.target.querySelector(".name").getAttribute("value"), variable, |
michael@0 | 220 | "Correct property name for " + variable); |
michael@0 | 221 | is(aVar.target.querySelector(".value").getAttribute("value"), hash[variable], |
michael@0 | 222 | "Correct property value of " + hash[variable] + " for " + variable); |
michael@0 | 223 | }); |
michael@0 | 224 | } |
michael@0 | 225 | |
michael@0 | 226 | function modifyVariableView (win, view, index, prop, value) { |
michael@0 | 227 | let deferred = Promise.defer(); |
michael@0 | 228 | let scope = view.getScopeAtIndex(index); |
michael@0 | 229 | let aVar = scope.get(prop); |
michael@0 | 230 | scope.expand(); |
michael@0 | 231 | |
michael@0 | 232 | // Must wait for the scope DOM to be available to receive |
michael@0 | 233 | // events |
michael@0 | 234 | executeSoon(() => { |
michael@0 | 235 | let varValue = aVar.target.querySelector(".title > .value"); |
michael@0 | 236 | EventUtils.sendMouseEvent({ type: "mousedown" }, varValue, win); |
michael@0 | 237 | |
michael@0 | 238 | win.on(win.EVENTS.UI_SET_PARAM, handleSetting); |
michael@0 | 239 | win.on(win.EVENTS.UI_SET_PARAM_ERROR, handleSetting); |
michael@0 | 240 | |
michael@0 | 241 | info("Setting " + value + " for " + prop + "...."); |
michael@0 | 242 | let varInput = aVar.target.querySelector(".title > .element-value-input"); |
michael@0 | 243 | setText(varInput, value); |
michael@0 | 244 | EventUtils.sendKey("RETURN", win); |
michael@0 | 245 | }); |
michael@0 | 246 | |
michael@0 | 247 | function handleSetting (eventName) { |
michael@0 | 248 | win.off(win.EVENTS.UI_SET_PARAM, handleSetting); |
michael@0 | 249 | win.off(win.EVENTS.UI_SET_PARAM_ERROR, handleSetting); |
michael@0 | 250 | if (eventName === win.EVENTS.UI_SET_PARAM) |
michael@0 | 251 | deferred.resolve(); |
michael@0 | 252 | if (eventName === win.EVENTS.UI_SET_PARAM_ERROR) |
michael@0 | 253 | deferred.reject(); |
michael@0 | 254 | } |
michael@0 | 255 | |
michael@0 | 256 | return deferred.promise; |
michael@0 | 257 | } |
michael@0 | 258 | |
michael@0 | 259 | function clearText (aElement) { |
michael@0 | 260 | info("Clearing text..."); |
michael@0 | 261 | aElement.focus(); |
michael@0 | 262 | aElement.value = ""; |
michael@0 | 263 | } |
michael@0 | 264 | |
michael@0 | 265 | function setText (aElement, aText) { |
michael@0 | 266 | clearText(aElement); |
michael@0 | 267 | info("Setting text: " + aText); |
michael@0 | 268 | aElement.value = aText; |
michael@0 | 269 | } |
michael@0 | 270 | |
michael@0 | 271 | function findGraphEdge (win, source, target) { |
michael@0 | 272 | let selector = ".edgePaths .edgePath[data-source='" + source + "'][data-target='" + target + "']"; |
michael@0 | 273 | return win.document.querySelector(selector); |
michael@0 | 274 | } |
michael@0 | 275 | |
michael@0 | 276 | function findGraphNode (win, node) { |
michael@0 | 277 | let selector = ".nodes > g[data-id='" + node + "']"; |
michael@0 | 278 | return win.document.querySelector(selector); |
michael@0 | 279 | } |
michael@0 | 280 | |
michael@0 | 281 | function click (win, element) { |
michael@0 | 282 | EventUtils.sendMouseEvent({ type: "click" }, element, win); |
michael@0 | 283 | } |
michael@0 | 284 | |
michael@0 | 285 | function mouseOver (win, element) { |
michael@0 | 286 | EventUtils.sendMouseEvent({ type: "mouseover" }, element, win); |
michael@0 | 287 | } |
michael@0 | 288 | |
michael@0 | 289 | /** |
michael@0 | 290 | * List of audio node properties to test against expectations of the AudioNode actor |
michael@0 | 291 | */ |
michael@0 | 292 | |
michael@0 | 293 | const NODE_PROPERTIES = { |
michael@0 | 294 | "OscillatorNode": ["type", "frequency", "detune"], |
michael@0 | 295 | "GainNode": ["gain"], |
michael@0 | 296 | "DelayNode": ["delayTime"], |
michael@0 | 297 | "AudioBufferSourceNode": ["buffer", "playbackRate", "loop", "loopStart", "loopEnd"], |
michael@0 | 298 | "ScriptProcessorNode": ["bufferSize"], |
michael@0 | 299 | "PannerNode": ["panningModel", "distanceModel", "refDistance", "maxDistance", "rolloffFactor", "coneInnerAngle", "coneOuterAngle", "coneOuterGain"], |
michael@0 | 300 | "ConvolverNode": ["buffer", "normalize"], |
michael@0 | 301 | "DynamicsCompressorNode": ["threshold", "knee", "ratio", "reduction", "attack", "release"], |
michael@0 | 302 | "BiquadFilterNode": ["type", "frequency", "Q", "detune", "gain"], |
michael@0 | 303 | "WaveShaperNode": ["curve", "oversample"], |
michael@0 | 304 | "AnalyserNode": ["fftSize", "minDecibels", "maxDecibels", "smoothingTimeConstraint", "frequencyBinCount"], |
michael@0 | 305 | "AudioDestinationNode": [], |
michael@0 | 306 | "ChannelSplitterNode": [], |
michael@0 | 307 | "ChannelMergerNode": [] |
michael@0 | 308 | }; |