diff options
Diffstat (limited to 'devtools/client/webaudioeditor/controller.js')
-rw-r--r-- | devtools/client/webaudioeditor/controller.js | 232 |
1 files changed, 232 insertions, 0 deletions
diff --git a/devtools/client/webaudioeditor/controller.js b/devtools/client/webaudioeditor/controller.js new file mode 100644 index 000000000..248a2a6f3 --- /dev/null +++ b/devtools/client/webaudioeditor/controller.js @@ -0,0 +1,232 @@ +/* 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. + */ +var gAudioNodes = new AudioNodesCollection(); + +/** + * Initializes the web audio editor views + */ +function startupWebAudioEditor() { + return all([ + WebAudioEditorController.initialize(), + ContextView.initialize(), + InspectorView.initialize(), + PropertiesView.initialize(), + AutomationView.initialize() + ]); +} + +/** + * Destroys the web audio editor controller and views. + */ +function shutdownWebAudioEditor() { + return all([ + WebAudioEditorController.destroy(), + ContextView.destroy(), + InspectorView.destroy(), + PropertiesView.destroy(), + AutomationView.destroy() + ]); +} + +/** + * Functions handling target-related lifetime events. + */ +var WebAudioEditorController = { + /** + * Listen for events emitted by the current tab target. + */ + initialize: Task.async(function* () { + 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); + + // Store the AudioNode definitions from the WebAudioFront, if the method exists. + // If not, get the JSON directly. Using the actor method is preferable so the client + // knows exactly what methods are supported on the server. + let actorHasDefinition = yield gTarget.actorHasMethod("webaudio", "getDefinition"); + if (actorHasDefinition) { + AUDIO_NODE_DEFINITION = yield gFront.getDefinition(); + } else { + AUDIO_NODE_DEFINITION = require("devtools/server/actors/utils/audionodes.json"); + } + + // Make sure the backend is prepared to handle audio contexts. + // Since actors are created lazily on the first request to them, we need to send an + // early request to ensure the CallWatcherActor is running and watching for new window + // globals. + gFront.setup({ reload: false }); + }), + + /** + * Remove events emitted by the current tab target. + */ + destroy: function () { + 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(); + PropertiesView.resetUI(); + }, + + // Since node events (create, disconnect, connect) are all async, + // we have to make sure to wait that the node has finished creating + // before performing an operation on it. + getNode: function* (nodeActor) { + let id = nodeActor.actorID; + let node = gAudioNodes.get(id); + + if (!node) { + let { resolve, promise } = defer(); + gAudioNodes.on("add", function createNodeListener(createdNode) { + if (createdNode.id === id) { + gAudioNodes.off("add", createNodeListener); + resolve(createdNode); + } + }); + node = yield promise; + } + return node; + }, + + /** + * 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": { + // 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: function (nodeActor) { + 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 = yield WebAudioEditorController.getNode(sourceActor); + let dest = yield WebAudioEditorController.getNode(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 = yield WebAudioEditorController.getNode(sourceActor); + let dest = yield WebAudioEditorController.getNode(destActor); + source.connect(dest, param); + }), + + /** + * Called when a node is disconnected. + */ + _onDisconnectNode: Task.async(function* (nodeActor) { + let node = yield WebAudioEditorController.getNode(nodeActor); + node.disconnect(); + }), + + /** + * Called when a node param is changed. + */ + _onChangeParam: Task.async(function* ({ actor, param, value }) { + let node = yield WebAudioEditorController.getNode(actor); + window.emit(EVENTS.CHANGE_PARAM, node, param, value); + }) +}; |