summaryrefslogtreecommitdiffstats
path: root/devtools/client/webaudioeditor/controller.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/webaudioeditor/controller.js')
-rw-r--r--devtools/client/webaudioeditor/controller.js232
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);
+ })
+};