browser/devtools/webaudioeditor/webaudioeditor-controller.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
michael@0 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4 "use strict";
michael@0 5
michael@0 6 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
michael@0 7
michael@0 8 Cu.import("resource://gre/modules/Services.jsm");
michael@0 9 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 10 Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
michael@0 11
michael@0 12 // Override DOM promises with Promise.jsm helpers
michael@0 13 const { defer, all } = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
michael@0 14
michael@0 15 const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
michael@0 16 const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
michael@0 17 const EventEmitter = require("devtools/toolkit/event-emitter");
michael@0 18 const STRINGS_URI = "chrome://browser/locale/devtools/webaudioeditor.properties"
michael@0 19 let { console } = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
michael@0 20
michael@0 21 // The panel's window global is an EventEmitter firing the following events:
michael@0 22 const EVENTS = {
michael@0 23 // Fired when the first AudioNode has been created, signifying
michael@0 24 // that the AudioContext is being used and should be tracked via the editor.
michael@0 25 START_CONTEXT: "WebAudioEditor:StartContext",
michael@0 26
michael@0 27 // On node creation, connect and disconnect.
michael@0 28 CREATE_NODE: "WebAudioEditor:CreateNode",
michael@0 29 CONNECT_NODE: "WebAudioEditor:ConnectNode",
michael@0 30 DISCONNECT_NODE: "WebAudioEditor:DisconnectNode",
michael@0 31
michael@0 32 // When a node gets GC'd.
michael@0 33 DESTROY_NODE: "WebAudioEditor:DestroyNode",
michael@0 34
michael@0 35 // On a node parameter's change.
michael@0 36 CHANGE_PARAM: "WebAudioEditor:ChangeParam",
michael@0 37
michael@0 38 // When the UI is reset from tab navigation.
michael@0 39 UI_RESET: "WebAudioEditor:UIReset",
michael@0 40
michael@0 41 // When a param has been changed via the UI and successfully
michael@0 42 // pushed via the actor to the raw audio node.
michael@0 43 UI_SET_PARAM: "WebAudioEditor:UISetParam",
michael@0 44
michael@0 45 // When an audio node is added to the list pane.
michael@0 46 UI_ADD_NODE_LIST: "WebAudioEditor:UIAddNodeList",
michael@0 47
michael@0 48 // When the Audio Context graph finishes rendering.
michael@0 49 // Is called with two arguments, first representing number of nodes
michael@0 50 // rendered, second being the number of edges rendered.
michael@0 51 UI_GRAPH_RENDERED: "WebAudioEditor:UIGraphRendered"
michael@0 52 };
michael@0 53
michael@0 54 /**
michael@0 55 * The current target and the Web Audio Editor front, set by this tool's host.
michael@0 56 */
michael@0 57 let gToolbox, gTarget, gFront;
michael@0 58
michael@0 59 /**
michael@0 60 * Track an array of audio nodes
michael@0 61 */
michael@0 62 let AudioNodes = [];
michael@0 63 let AudioNodeConnections = new WeakMap();
michael@0 64
michael@0 65
michael@0 66 // Light representation wrapping an AudioNode actor with additional properties
michael@0 67 function AudioNodeView (actor) {
michael@0 68 this.actor = actor;
michael@0 69 this.id = actor.actorID;
michael@0 70 }
michael@0 71
michael@0 72 // A proxy for the underlying AudioNodeActor to fetch its type
michael@0 73 // and subsequently assign the type to the instance.
michael@0 74 AudioNodeView.prototype.getType = Task.async(function* () {
michael@0 75 this.type = yield this.actor.getType();
michael@0 76 return this.type;
michael@0 77 });
michael@0 78
michael@0 79 // Helper method to create connections in the AudioNodeConnections
michael@0 80 // WeakMap for rendering
michael@0 81 AudioNodeView.prototype.connect = function (destination) {
michael@0 82 let connections = AudioNodeConnections.get(this);
michael@0 83 if (!connections) {
michael@0 84 connections = [];
michael@0 85 AudioNodeConnections.set(this, connections);
michael@0 86 }
michael@0 87 connections.push(destination);
michael@0 88 };
michael@0 89
michael@0 90 // Helper method to remove audio connections from the current AudioNodeView
michael@0 91 AudioNodeView.prototype.disconnect = function () {
michael@0 92 AudioNodeConnections.set(this, []);
michael@0 93 };
michael@0 94
michael@0 95 // Returns a promise that resolves to an array of objects containing
michael@0 96 // both a `param` name property and a `value` property.
michael@0 97 AudioNodeView.prototype.getParams = function () {
michael@0 98 return this.actor.getParams();
michael@0 99 };
michael@0 100
michael@0 101
michael@0 102 /**
michael@0 103 * Initializes the web audio editor views
michael@0 104 */
michael@0 105 function startupWebAudioEditor() {
michael@0 106 return all([
michael@0 107 WebAudioEditorController.initialize(),
michael@0 108 WebAudioGraphView.initialize(),
michael@0 109 WebAudioParamView.initialize()
michael@0 110 ]);
michael@0 111 }
michael@0 112
michael@0 113 /**
michael@0 114 * Destroys the web audio editor controller and views.
michael@0 115 */
michael@0 116 function shutdownWebAudioEditor() {
michael@0 117 return all([
michael@0 118 WebAudioEditorController.destroy(),
michael@0 119 WebAudioGraphView.destroy(),
michael@0 120 WebAudioParamView.destroy()
michael@0 121 ]);
michael@0 122 }
michael@0 123
michael@0 124 /**
michael@0 125 * Functions handling target-related lifetime events.
michael@0 126 */
michael@0 127 let WebAudioEditorController = {
michael@0 128 /**
michael@0 129 * Listen for events emitted by the current tab target.
michael@0 130 */
michael@0 131 initialize: function() {
michael@0 132 this._onTabNavigated = this._onTabNavigated.bind(this);
michael@0 133 gTarget.on("will-navigate", this._onTabNavigated);
michael@0 134 gTarget.on("navigate", this._onTabNavigated);
michael@0 135 gFront.on("start-context", this._onStartContext);
michael@0 136 gFront.on("create-node", this._onCreateNode);
michael@0 137 gFront.on("connect-node", this._onConnectNode);
michael@0 138 gFront.on("disconnect-node", this._onDisconnectNode);
michael@0 139 gFront.on("change-param", this._onChangeParam);
michael@0 140
michael@0 141 // Set up events to refresh the Graph view
michael@0 142 window.on(EVENTS.CREATE_NODE, this._onUpdatedContext);
michael@0 143 window.on(EVENTS.CONNECT_NODE, this._onUpdatedContext);
michael@0 144 window.on(EVENTS.DISCONNECT_NODE, this._onUpdatedContext);
michael@0 145 },
michael@0 146
michael@0 147 /**
michael@0 148 * Remove events emitted by the current tab target.
michael@0 149 */
michael@0 150 destroy: function() {
michael@0 151 gTarget.off("will-navigate", this._onTabNavigated);
michael@0 152 gTarget.off("navigate", this._onTabNavigated);
michael@0 153 gFront.off("start-context", this._onStartContext);
michael@0 154 gFront.off("create-node", this._onCreateNode);
michael@0 155 gFront.off("connect-node", this._onConnectNode);
michael@0 156 gFront.off("disconnect-node", this._onDisconnectNode);
michael@0 157 gFront.off("change-param", this._onChangeParam);
michael@0 158 window.off(EVENTS.CREATE_NODE, this._onUpdatedContext);
michael@0 159 window.off(EVENTS.CONNECT_NODE, this._onUpdatedContext);
michael@0 160 window.off(EVENTS.DISCONNECT_NODE, this._onUpdatedContext);
michael@0 161 },
michael@0 162
michael@0 163 /**
michael@0 164 * Called when a new audio node is created, or the audio context
michael@0 165 * routing changes.
michael@0 166 */
michael@0 167 _onUpdatedContext: function () {
michael@0 168 WebAudioGraphView.draw();
michael@0 169 },
michael@0 170
michael@0 171 /**
michael@0 172 * Called for each location change in the debugged tab.
michael@0 173 */
michael@0 174 _onTabNavigated: function(event) {
michael@0 175 switch (event) {
michael@0 176 case "will-navigate": {
michael@0 177 Task.spawn(function() {
michael@0 178 // Make sure the backend is prepared to handle audio contexts.
michael@0 179 yield gFront.setup({ reload: false });
michael@0 180
michael@0 181 // Reset UI to show "Waiting for Audio Context..." and clear out
michael@0 182 // current UI.
michael@0 183 WebAudioGraphView.resetUI();
michael@0 184 WebAudioParamView.resetUI();
michael@0 185
michael@0 186 // Clear out stored audio nodes
michael@0 187 AudioNodes.length = 0;
michael@0 188 AudioNodeConnections.clear();
michael@0 189 }).then(() => window.emit(EVENTS.UI_RESET));
michael@0 190 break;
michael@0 191 }
michael@0 192 case "navigate": {
michael@0 193 // TODO Case of bfcache, needs investigating
michael@0 194 // bug 994250
michael@0 195 break;
michael@0 196 }
michael@0 197 }
michael@0 198 },
michael@0 199
michael@0 200 /**
michael@0 201 * Called after the first audio node is created in an audio context,
michael@0 202 * signaling that the audio context is being used.
michael@0 203 */
michael@0 204 _onStartContext: function() {
michael@0 205 WebAudioGraphView.showContent();
michael@0 206 window.emit(EVENTS.START_CONTEXT);
michael@0 207 },
michael@0 208
michael@0 209 /**
michael@0 210 * Called when a new node is created. Creates an `AudioNodeView` instance
michael@0 211 * for tracking throughout the editor.
michael@0 212 */
michael@0 213 _onCreateNode: Task.async(function* (nodeActor) {
michael@0 214 let node = new AudioNodeView(nodeActor);
michael@0 215 yield node.getType();
michael@0 216 AudioNodes.push(node);
michael@0 217 window.emit(EVENTS.CREATE_NODE, node.id);
michael@0 218 }),
michael@0 219
michael@0 220 /**
michael@0 221 * Called when a node is connected to another node.
michael@0 222 */
michael@0 223 _onConnectNode: Task.async(function* ({ source: sourceActor, dest: destActor }) {
michael@0 224 // Since node create and connect are probably executed back to back,
michael@0 225 // and the controller's `_onCreateNode` needs to look up type,
michael@0 226 // the edge creation could be called before the graph node is actually
michael@0 227 // created. This way, we can check and listen for the event before
michael@0 228 // adding an edge.
michael@0 229 let [source, dest] = yield waitForNodeCreation(sourceActor, destActor);
michael@0 230
michael@0 231 source.connect(dest);
michael@0 232 window.emit(EVENTS.CONNECT_NODE, source.id, dest.id);
michael@0 233
michael@0 234 function waitForNodeCreation (sourceActor, destActor) {
michael@0 235 let deferred = defer();
michael@0 236 let source = getViewNodeByActor(sourceActor);
michael@0 237 let dest = getViewNodeByActor(destActor);
michael@0 238
michael@0 239 if (!source || !dest)
michael@0 240 window.on(EVENTS.CREATE_NODE, function createNodeListener (_, id) {
michael@0 241 let createdNode = getViewNodeById(id);
michael@0 242 if (equalActors(sourceActor, createdNode.actor))
michael@0 243 source = createdNode;
michael@0 244 if (equalActors(destActor, createdNode.actor))
michael@0 245 dest = createdNode;
michael@0 246 if (source && dest) {
michael@0 247 window.off(EVENTS.CREATE_NODE, createNodeListener);
michael@0 248 deferred.resolve([source, dest]);
michael@0 249 }
michael@0 250 });
michael@0 251 else
michael@0 252 deferred.resolve([source, dest]);
michael@0 253 return deferred.promise;
michael@0 254 }
michael@0 255 }),
michael@0 256
michael@0 257 /**
michael@0 258 * Called when a node is disconnected.
michael@0 259 */
michael@0 260 _onDisconnectNode: function(nodeActor) {
michael@0 261 let node = getViewNodeByActor(nodeActor);
michael@0 262 node.disconnect();
michael@0 263 window.emit(EVENTS.DISCONNECT_NODE, node.id);
michael@0 264 },
michael@0 265
michael@0 266 /**
michael@0 267 * Called when a node param is changed.
michael@0 268 */
michael@0 269 _onChangeParam: function({ actor, param, value }) {
michael@0 270 window.emit(EVENTS.CHANGE_PARAM, getViewNodeByActor(actor), param, value);
michael@0 271 }
michael@0 272 };
michael@0 273
michael@0 274 /**
michael@0 275 * Convenient way of emitting events from the panel window.
michael@0 276 */
michael@0 277 EventEmitter.decorate(this);
michael@0 278
michael@0 279 /**
michael@0 280 * DOM query helper.
michael@0 281 */
michael@0 282 function $(selector, target = document) { return target.querySelector(selector); }
michael@0 283 function $$(selector, target = document) { return target.querySelectorAll(selector); }
michael@0 284
michael@0 285 /**
michael@0 286 * Compare `actorID` between two actors to determine if they're corresponding
michael@0 287 * to the same underlying actor.
michael@0 288 */
michael@0 289 function equalActors (actor1, actor2) {
michael@0 290 return actor1.actorID === actor2.actorID;
michael@0 291 }
michael@0 292
michael@0 293 /**
michael@0 294 * Returns the corresponding ViewNode by actor
michael@0 295 */
michael@0 296 function getViewNodeByActor (actor) {
michael@0 297 for (let i = 0; i < AudioNodes.length; i++) {
michael@0 298 if (equalActors(AudioNodes[i].actor, actor))
michael@0 299 return AudioNodes[i];
michael@0 300 }
michael@0 301 return null;
michael@0 302 }
michael@0 303
michael@0 304 /**
michael@0 305 * Returns the corresponding ViewNode by actorID
michael@0 306 */
michael@0 307 function getViewNodeById (id) {
michael@0 308 return getViewNodeByActor({ actorID: id });
michael@0 309 }
michael@0 310

mercurial