/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ /** * A collection of `AudioNodeModel`s used throughout the editor * to keep track of audio nodes within the audio context. */ let gAudioNodes = new AudioNodesCollection(); /** * Initializes the web audio editor views */ function startupWebAudioEditor() { return all([ WebAudioEditorController.initialize(), ContextView.initialize(), InspectorView.initialize() ]); } /** * Destroys the web audio editor controller and views. */ function shutdownWebAudioEditor() { return all([ WebAudioEditorController.destroy(), ContextView.destroy(), InspectorView.destroy(), ]); } /** * Functions handling target-related lifetime events. */ let WebAudioEditorController = { /** * Listen for events emitted by the current tab target. */ initialize: function() { telemetry.toolOpened("webaudioeditor"); this._onTabNavigated = this._onTabNavigated.bind(this); this._onThemeChange = this._onThemeChange.bind(this); gTarget.on("will-navigate", this._onTabNavigated); gTarget.on("navigate", this._onTabNavigated); gFront.on("start-context", this._onStartContext); gFront.on("create-node", this._onCreateNode); gFront.on("connect-node", this._onConnectNode); gFront.on("connect-param", this._onConnectParam); gFront.on("disconnect-node", this._onDisconnectNode); gFront.on("change-param", this._onChangeParam); gFront.on("destroy-node", this._onDestroyNode); // Hook into theme change so we can change // the graph's marker styling, since we can't do this // with CSS gDevTools.on("pref-changed", this._onThemeChange); }, /** * Remove events emitted by the current tab target. */ destroy: function() { telemetry.toolClosed("webaudioeditor"); gTarget.off("will-navigate", this._onTabNavigated); gTarget.off("navigate", this._onTabNavigated); gFront.off("start-context", this._onStartContext); gFront.off("create-node", this._onCreateNode); gFront.off("connect-node", this._onConnectNode); gFront.off("connect-param", this._onConnectParam); gFront.off("disconnect-node", this._onDisconnectNode); gFront.off("change-param", this._onChangeParam); gFront.off("destroy-node", this._onDestroyNode); gDevTools.off("pref-changed", this._onThemeChange); }, /** * Called when page is reloaded to show the reload notice and waiting * for an audio context notice. */ reset: function () { $("#content").hidden = true; ContextView.resetUI(); InspectorView.resetUI(); }, // Since node create and connect are probably executed back to back, // and the controller's `_onCreateNode` needs to look up type, // the edge creation could be called before the graph node is actually // created. This way, we can check and listen for the event before // adding an edge. _waitForNodeCreation: function (sourceActor, destActor) { let deferred = defer(); let source = gAudioNodes.get(sourceActor.actorID); let dest = gAudioNodes.get(destActor.actorID); if (!source || !dest) { gAudioNodes.on("add", function createNodeListener (createdNode) { if (sourceActor.actorID === createdNode.id) source = createdNode; if (destActor.actorID === createdNode.id) dest = createdNode; if (source && dest) { gAudioNodes.off("add", createNodeListener); deferred.resolve([source, dest]); } }); } else { deferred.resolve([source, dest]); } return deferred.promise; }, /** * Fired when the devtools theme changes (light, dark, etc.) * so that the graph can update marker styling, as that * cannot currently be done with CSS. */ _onThemeChange: function (event, data) { window.emit(EVENTS.THEME_CHANGE, data.newValue); }, /** * Called for each location change in the debugged tab. */ _onTabNavigated: Task.async(function* (event, {isFrameSwitching}) { switch (event) { case "will-navigate": { // Make sure the backend is prepared to handle audio contexts. if (!isFrameSwitching) { yield gFront.setup({ reload: false }); } // Clear out current UI. this.reset(); // When switching to an iframe, ensure displaying the reload button. // As the document has already been loaded without being hooked. if (isFrameSwitching) { $("#reload-notice").hidden = false; $("#waiting-notice").hidden = true; } else { // Otherwise, we are loading a new top level document, // so we don't need to reload anymore and should receive // new node events. $("#reload-notice").hidden = true; $("#waiting-notice").hidden = false; } // Clear out stored audio nodes gAudioNodes.reset(); window.emit(EVENTS.UI_RESET); break; } case "navigate": { // TODO Case of bfcache, needs investigating // bug 994250 break; } } }), /** * Called after the first audio node is created in an audio context, * signaling that the audio context is being used. */ _onStartContext: function() { $("#reload-notice").hidden = true; $("#waiting-notice").hidden = true; $("#content").hidden = false; window.emit(EVENTS.START_CONTEXT); }, /** * Called when a new node is created. Creates an `AudioNodeView` instance * for tracking throughout the editor. */ _onCreateNode: Task.async(function* (nodeActor) { yield gAudioNodes.add(nodeActor); }), /** * Called on `destroy-node` when an AudioNode is GC'd. Removes * from the AudioNode array and fires an event indicating the removal. */ _onDestroyNode: function (nodeActor) { gAudioNodes.remove(gAudioNodes.get(nodeActor.actorID)); }, /** * Called when a node is connected to another node. */ _onConnectNode: Task.async(function* ({ source: sourceActor, dest: destActor }) { let [source, dest] = yield WebAudioEditorController._waitForNodeCreation(sourceActor, destActor); source.connect(dest); }), /** * Called when a node is conneceted to another node's AudioParam. */ _onConnectParam: Task.async(function* ({ source: sourceActor, dest: destActor, param }) { let [source, dest] = yield WebAudioEditorController._waitForNodeCreation(sourceActor, destActor); source.connect(dest, param); }), /** * Called when a node is disconnected. */ _onDisconnectNode: function(nodeActor) { let node = gAudioNodes.get(nodeActor.actorID); node.disconnect(); }, /** * Called when a node param is changed. */ _onChangeParam: function({ actor, param, value }) { window.emit(EVENTS.CHANGE_PARAM, gAudioNodes.get(actor.actorID), param, value); } };