controller.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  3. * You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. /**
  5. * A collection of `AudioNodeModel`s used throughout the editor
  6. * to keep track of audio nodes within the audio context.
  7. */
  8. var gAudioNodes = new AudioNodesCollection();
  9. /**
  10. * Initializes the web audio editor views
  11. */
  12. function startupWebAudioEditor() {
  13. return all([
  14. WebAudioEditorController.initialize(),
  15. ContextView.initialize(),
  16. InspectorView.initialize(),
  17. PropertiesView.initialize(),
  18. AutomationView.initialize()
  19. ]);
  20. }
  21. /**
  22. * Destroys the web audio editor controller and views.
  23. */
  24. function shutdownWebAudioEditor() {
  25. return all([
  26. WebAudioEditorController.destroy(),
  27. ContextView.destroy(),
  28. InspectorView.destroy(),
  29. PropertiesView.destroy(),
  30. AutomationView.destroy()
  31. ]);
  32. }
  33. /**
  34. * Functions handling target-related lifetime events.
  35. */
  36. var WebAudioEditorController = {
  37. /**
  38. * Listen for events emitted by the current tab target.
  39. */
  40. initialize: Task.async(function* () {
  41. this._onTabNavigated = this._onTabNavigated.bind(this);
  42. this._onThemeChange = this._onThemeChange.bind(this);
  43. gTarget.on("will-navigate", this._onTabNavigated);
  44. gTarget.on("navigate", this._onTabNavigated);
  45. gFront.on("start-context", this._onStartContext);
  46. gFront.on("create-node", this._onCreateNode);
  47. gFront.on("connect-node", this._onConnectNode);
  48. gFront.on("connect-param", this._onConnectParam);
  49. gFront.on("disconnect-node", this._onDisconnectNode);
  50. gFront.on("change-param", this._onChangeParam);
  51. gFront.on("destroy-node", this._onDestroyNode);
  52. // Hook into theme change so we can change
  53. // the graph's marker styling, since we can't do this
  54. // with CSS
  55. gDevTools.on("pref-changed", this._onThemeChange);
  56. // Store the AudioNode definitions from the WebAudioFront, if the method exists.
  57. // If not, get the JSON directly. Using the actor method is preferable so the client
  58. // knows exactly what methods are supported on the server.
  59. let actorHasDefinition = yield gTarget.actorHasMethod("webaudio", "getDefinition");
  60. if (actorHasDefinition) {
  61. AUDIO_NODE_DEFINITION = yield gFront.getDefinition();
  62. } else {
  63. AUDIO_NODE_DEFINITION = require("devtools/server/actors/utils/audionodes.json");
  64. }
  65. // Make sure the backend is prepared to handle audio contexts.
  66. // Since actors are created lazily on the first request to them, we need to send an
  67. // early request to ensure the CallWatcherActor is running and watching for new window
  68. // globals.
  69. gFront.setup({ reload: false });
  70. }),
  71. /**
  72. * Remove events emitted by the current tab target.
  73. */
  74. destroy: function () {
  75. gTarget.off("will-navigate", this._onTabNavigated);
  76. gTarget.off("navigate", this._onTabNavigated);
  77. gFront.off("start-context", this._onStartContext);
  78. gFront.off("create-node", this._onCreateNode);
  79. gFront.off("connect-node", this._onConnectNode);
  80. gFront.off("connect-param", this._onConnectParam);
  81. gFront.off("disconnect-node", this._onDisconnectNode);
  82. gFront.off("change-param", this._onChangeParam);
  83. gFront.off("destroy-node", this._onDestroyNode);
  84. gDevTools.off("pref-changed", this._onThemeChange);
  85. },
  86. /**
  87. * Called when page is reloaded to show the reload notice and waiting
  88. * for an audio context notice.
  89. */
  90. reset: function () {
  91. $("#content").hidden = true;
  92. ContextView.resetUI();
  93. InspectorView.resetUI();
  94. PropertiesView.resetUI();
  95. },
  96. // Since node events (create, disconnect, connect) are all async,
  97. // we have to make sure to wait that the node has finished creating
  98. // before performing an operation on it.
  99. getNode: function* (nodeActor) {
  100. let id = nodeActor.actorID;
  101. let node = gAudioNodes.get(id);
  102. if (!node) {
  103. let { resolve, promise } = defer();
  104. gAudioNodes.on("add", function createNodeListener(createdNode) {
  105. if (createdNode.id === id) {
  106. gAudioNodes.off("add", createNodeListener);
  107. resolve(createdNode);
  108. }
  109. });
  110. node = yield promise;
  111. }
  112. return node;
  113. },
  114. /**
  115. * Fired when the devtools theme changes (light, dark, etc.)
  116. * so that the graph can update marker styling, as that
  117. * cannot currently be done with CSS.
  118. */
  119. _onThemeChange: function (event, data) {
  120. window.emit(EVENTS.THEME_CHANGE, data.newValue);
  121. },
  122. /**
  123. * Called for each location change in the debugged tab.
  124. */
  125. _onTabNavigated: Task.async(function* (event, {isFrameSwitching}) {
  126. switch (event) {
  127. case "will-navigate": {
  128. // Clear out current UI.
  129. this.reset();
  130. // When switching to an iframe, ensure displaying the reload button.
  131. // As the document has already been loaded without being hooked.
  132. if (isFrameSwitching) {
  133. $("#reload-notice").hidden = false;
  134. $("#waiting-notice").hidden = true;
  135. } else {
  136. // Otherwise, we are loading a new top level document,
  137. // so we don't need to reload anymore and should receive
  138. // new node events.
  139. $("#reload-notice").hidden = true;
  140. $("#waiting-notice").hidden = false;
  141. }
  142. // Clear out stored audio nodes
  143. gAudioNodes.reset();
  144. window.emit(EVENTS.UI_RESET);
  145. break;
  146. }
  147. case "navigate": {
  148. // TODO Case of bfcache, needs investigating
  149. // bug 994250
  150. break;
  151. }
  152. }
  153. }),
  154. /**
  155. * Called after the first audio node is created in an audio context,
  156. * signaling that the audio context is being used.
  157. */
  158. _onStartContext: function () {
  159. $("#reload-notice").hidden = true;
  160. $("#waiting-notice").hidden = true;
  161. $("#content").hidden = false;
  162. window.emit(EVENTS.START_CONTEXT);
  163. },
  164. /**
  165. * Called when a new node is created. Creates an `AudioNodeView` instance
  166. * for tracking throughout the editor.
  167. */
  168. _onCreateNode: function (nodeActor) {
  169. gAudioNodes.add(nodeActor);
  170. },
  171. /**
  172. * Called on `destroy-node` when an AudioNode is GC'd. Removes
  173. * from the AudioNode array and fires an event indicating the removal.
  174. */
  175. _onDestroyNode: function (nodeActor) {
  176. gAudioNodes.remove(gAudioNodes.get(nodeActor.actorID));
  177. },
  178. /**
  179. * Called when a node is connected to another node.
  180. */
  181. _onConnectNode: Task.async(function* ({ source: sourceActor, dest: destActor }) {
  182. let source = yield WebAudioEditorController.getNode(sourceActor);
  183. let dest = yield WebAudioEditorController.getNode(destActor);
  184. source.connect(dest);
  185. }),
  186. /**
  187. * Called when a node is conneceted to another node's AudioParam.
  188. */
  189. _onConnectParam: Task.async(function* ({ source: sourceActor, dest: destActor, param }) {
  190. let source = yield WebAudioEditorController.getNode(sourceActor);
  191. let dest = yield WebAudioEditorController.getNode(destActor);
  192. source.connect(dest, param);
  193. }),
  194. /**
  195. * Called when a node is disconnected.
  196. */
  197. _onDisconnectNode: Task.async(function* (nodeActor) {
  198. let node = yield WebAudioEditorController.getNode(nodeActor);
  199. node.disconnect();
  200. }),
  201. /**
  202. * Called when a node param is changed.
  203. */
  204. _onChangeParam: Task.async(function* ({ actor, param, value }) {
  205. let node = yield WebAudioEditorController.getNode(actor);
  206. window.emit(EVENTS.CHANGE_PARAM, node, param, value);
  207. })
  208. };