Bug 1015783 - Add a devtools API for Web Audio; r=padenot,smaug
authorEhsan Akhgari <ehsan@mozilla.com>
Tue, 03 Jun 2014 22:51:48 -0400
changeset 206932 50f9f62bebb226a20dda15339ae5ea85f1cba594
parent 206931 38cd6935fc199e78c083a2b016bb181f9f115564
child 206933 b144d655a179191d81ea678b0ecb7db2d721fbc3
push id494
push userraliiev@mozilla.com
push dateMon, 25 Aug 2014 18:42:16 +0000
treeherdermozilla-release@a3cc3e46b571 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspadenot, smaug
bugs1015783, 980506
milestone32.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1015783 - Add a devtools API for Web Audio; r=padenot,smaug See bug 980506 for an extensive discussion about this. This patch adds three APIs to AudioNode in order for us to be able to build awesome devtools on top of it. * Weak reference API. This patch allows one to hold a weak reference to all AudioNode's using Components.utils.getWeakReference(). That way, the devtool's inspection code would not change the lifetime of AudioNodes. * AudioNode.id This is a chrome-only unique and monotonically incrementing ID for AudioNode objects. It is supposed to be used in order for the devtools to be able to identify a node without having to keep it alive. * webaudio-node-demise This is an observer notification that is called every time an AudioNode gets destroyed inside Gecko. The ID of the corresponding node is passed to this notification.
content/media/webaudio/AudioDestinationNode.cpp
content/media/webaudio/AudioDestinationNode.h
content/media/webaudio/AudioNode.cpp
content/media/webaudio/AudioNode.h
content/media/webaudio/MediaStreamAudioSourceNode.cpp
content/media/webaudio/test/chrome.ini
content/media/webaudio/test/moz.build
content/media/webaudio/test/test_AudioNodeDevtoolsAPI.html
dom/webidl/AudioNode.webidl
--- a/content/media/webaudio/AudioDestinationNode.cpp
+++ b/content/media/webaudio/AudioDestinationNode.cpp
@@ -222,17 +222,16 @@ static bool UseAudioChannelService()
 }
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(AudioDestinationNode, AudioNode,
                                    mAudioChannelAgent)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(AudioDestinationNode)
   NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
   NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback)
-  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
 NS_INTERFACE_MAP_END_INHERITING(AudioNode)
 
 NS_IMPL_ADDREF_INHERITED(AudioDestinationNode, AudioNode)
 NS_IMPL_RELEASE_INHERITED(AudioDestinationNode, AudioNode)
 
 AudioDestinationNode::AudioDestinationNode(AudioContext* aContext,
                                            bool aIsOffline,
                                            AudioChannel aChannel,
--- a/content/media/webaudio/AudioDestinationNode.h
+++ b/content/media/webaudio/AudioDestinationNode.h
@@ -7,27 +7,25 @@
 #ifndef AudioDestinationNode_h_
 #define AudioDestinationNode_h_
 
 #include "mozilla/dom/AudioChannelBinding.h"
 #include "AudioNode.h"
 #include "nsIDOMEventListener.h"
 #include "nsIAudioChannelAgent.h"
 #include "AudioChannelCommon.h"
-#include "nsWeakReference.h"
 
 namespace mozilla {
 namespace dom {
 
 class AudioContext;
 
 class AudioDestinationNode : public AudioNode
                            , public nsIDOMEventListener
                            , public nsIAudioChannelAgentCallback
-                           , public nsSupportsWeakReference
                            , public MainThreadMediaStreamListener
 {
 public:
   // This node type knows what MediaStreamGraph to use based on
   // whether it's in offline mode.
   AudioDestinationNode(AudioContext* aContext,
                        bool aIsOffline,
                        AudioChannel aChannel = AudioChannel::Normal,
--- a/content/media/webaudio/AudioNode.cpp
+++ b/content/media/webaudio/AudioNode.cpp
@@ -4,21 +4,24 @@
  * 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/. */
 
 #include "AudioNode.h"
 #include "mozilla/ErrorResult.h"
 #include "AudioNodeStream.h"
 #include "AudioNodeEngine.h"
 #include "mozilla/dom/AudioParam.h"
+#include "mozilla/Services.h"
+#include "nsIObserverService.h"
 
 namespace mozilla {
 namespace dom {
 
 static const uint32_t INVALID_PORT = 0xffffffff;
+static uint32_t gId = 0;
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(AudioNode)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(AudioNode, DOMEventTargetHelper)
   tmp->DisconnectFromGraph();
   if (tmp->mContext) {
     tmp->mContext->UpdateNodeCount(-1);
   }
@@ -44,39 +47,48 @@ AudioNode::Release()
     DisconnectFromGraph();
   }
   nsrefcnt r = DOMEventTargetHelper::Release();
   NS_LOG_RELEASE(this, r, "AudioNode");
   return r;
 }
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(AudioNode)
+  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
 AudioNode::AudioNode(AudioContext* aContext,
                      uint32_t aChannelCount,
                      ChannelCountMode aChannelCountMode,
                      ChannelInterpretation aChannelInterpretation)
   : DOMEventTargetHelper(aContext->GetParentObject())
   , mContext(aContext)
   , mChannelCount(aChannelCount)
   , mChannelCountMode(aChannelCountMode)
   , mChannelInterpretation(aChannelInterpretation)
+  , mId(gId++)
+#ifdef DEBUG
+  , mDemiseNotified(false)
+#endif
 {
   MOZ_ASSERT(aContext);
   DOMEventTargetHelper::BindToOwner(aContext->GetParentObject());
   SetIsDOMBinding();
   aContext->UpdateNodeCount(1);
 }
 
 AudioNode::~AudioNode()
 {
   MOZ_ASSERT(mInputNodes.IsEmpty());
   MOZ_ASSERT(mOutputNodes.IsEmpty());
   MOZ_ASSERT(mOutputParams.IsEmpty());
+#ifdef DEBUG
+  MOZ_ASSERT(mDemiseNotified,
+             "The webaudio-node-demise notification must have been sent");
+#endif
   if (mContext) {
     mContext->UpdateNodeCount(-1);
   }
 }
 
 size_t
 AudioNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
 {
@@ -380,16 +392,26 @@ AudioNode::DestroyMediaStream()
       MutexAutoLock lock(ns->Engine()->NodeMutex());
       MOZ_ASSERT(ns, "How come we don't have a stream here?");
       MOZ_ASSERT(ns->Engine()->Node() == this, "Invalid node reference");
       ns->Engine()->ClearNode();
     }
 
     mStream->Destroy();
     mStream = nullptr;
+
+    nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+    if (obs) {
+      nsAutoString id;
+      id.AppendPrintf("%u", mId);
+      obs->NotifyObservers(nullptr, "webaudio-node-demise", id.get());
+    }
+#ifdef DEBUG
+    mDemiseNotified = true;
+#endif
   }
 }
 
 void
 AudioNode::RemoveOutputParam(AudioParam* aParam)
 {
   mOutputParams.RemoveElement(aParam);
 }
--- a/content/media/webaudio/AudioNode.h
+++ b/content/media/webaudio/AudioNode.h
@@ -11,16 +11,17 @@
 #include "mozilla/dom/AudioNodeBinding.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsAutoPtr.h"
 #include "nsTArray.h"
 #include "AudioContext.h"
 #include "MediaStreamGraph.h"
 #include "WebAudioUtils.h"
 #include "mozilla/MemoryReporting.h"
+#include "nsWeakReference.h"
 
 namespace mozilla {
 
 namespace dom {
 
 class AudioContext;
 class AudioBufferSourceNode;
 class AudioParam;
@@ -77,17 +78,18 @@ private:
  * finished do so strictly *after* producing and returning their last block.
  * In this way, an engine that receives non-null input knows that the input
  * comes from nodes that are still alive and will keep their output nodes
  * alive for at least as long as it takes to process messages from the graph
  * thread.  i.e. the engine receiving non-null input knows that its node is
  * still alive, and will still be alive when it receives a message from the
  * engine.
  */
-class AudioNode : public DOMEventTargetHelper
+class AudioNode : public DOMEventTargetHelper,
+                  public nsSupportsWeakReference
 {
 protected:
   // You can only use refcounting to delete this object
   virtual ~AudioNode();
 
 public:
   AudioNode(AudioContext* aContext,
             uint32_t aChannelCount,
@@ -128,16 +130,18 @@ public:
   virtual void Disconnect(uint32_t aOutput, ErrorResult& aRv);
 
   // The following two virtual methods must be implemented by each node type
   // to provide their number of input and output ports. These numbers are
   // constant for the lifetime of the node. Both default to 1.
   virtual uint16_t NumberOfInputs() const { return 1; }
   virtual uint16_t NumberOfOutputs() const { return 1; }
 
+  uint32_t Id() const { return mId; }
+
   uint32_t ChannelCount() const { return mChannelCount; }
   virtual void SetChannelCount(uint32_t aChannelCount, ErrorResult& aRv)
   {
     if (aChannelCount == 0 ||
         aChannelCount > WebAudioUtils::MaxChannelCount) {
       aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
       return;
     }
@@ -261,14 +265,20 @@ private:
   // AudioParam::mInputNodes of the mOutputParams entry. We won't necessarily be
   // able to identify the exact matching entry, since mOutputParams doesn't
   // include the port identifiers and the same node could be connected on
   // multiple ports.
   nsTArray<nsRefPtr<AudioParam> > mOutputParams;
   uint32_t mChannelCount;
   ChannelCountMode mChannelCountMode;
   ChannelInterpretation mChannelInterpretation;
+  const uint32_t mId;
+#ifdef DEBUG
+  // In debug builds, check to make sure that the node demise notification has
+  // been properly sent before the node is destroyed.
+  bool mDemiseNotified;
+#endif
 };
 
 }
 }
 
 #endif
--- a/content/media/webaudio/MediaStreamAudioSourceNode.cpp
+++ b/content/media/webaudio/MediaStreamAudioSourceNode.cpp
@@ -37,17 +37,17 @@ MediaStreamAudioSourceNode::MediaStreamA
               ChannelInterpretation::Speakers),
     mInputStream(aMediaStream)
 {
   AudioNodeEngine* engine = new MediaStreamAudioSourceNodeEngine(this);
   mStream = aContext->Graph()->CreateAudioNodeExternalInputStream(engine);
   ProcessedMediaStream* outputStream = static_cast<ProcessedMediaStream*>(mStream.get());
   mInputPort = outputStream->AllocateInputPort(aMediaStream->GetStream(),
                                                MediaInputPort::FLAG_BLOCK_INPUT);
-  mInputStream->AddConsumerToKeepAlive(this);
+  mInputStream->AddConsumerToKeepAlive(static_cast<nsIDOMEventTarget*>(this));
 
   PrincipalChanged(mInputStream); // trigger enabling/disabling of the connector
   mInputStream->AddPrincipalChangeObserver(this);
 }
 
 MediaStreamAudioSourceNode::~MediaStreamAudioSourceNode()
 {
   if (mInputStream) {
new file mode 100644
--- /dev/null
+++ b/content/media/webaudio/test/chrome.ini
@@ -0,0 +1,3 @@
+[DEFAULT]
+
+[test_AudioNodeDevtoolsAPI.html]
--- a/content/media/webaudio/test/moz.build
+++ b/content/media/webaudio/test/moz.build
@@ -3,8 +3,12 @@
 # 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/.
 
 MOCHITEST_MANIFESTS += [
     'blink/mochitest.ini',
     'mochitest.ini',
 ]
+
+MOCHITEST_CHROME_MANIFESTS += [
+    'chrome.ini'
+]
new file mode 100644
--- /dev/null
+++ b/content/media/webaudio/test/test_AudioNodeDevtoolsAPI.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test the devtool AudioNode API</title>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+  Components.utils.import('resource://gre/modules/Services.jsm');
+
+  SimpleTest.waitForExplicitFinish();
+
+  var ac = new AudioContext();
+  var ids;
+  var weak;
+  (function() {
+    var src1 = ac.createBufferSource();
+    var src2 = ac.createBufferSource();
+    ok(src2.id > src1.id, "The ID should be monotonic");
+    ok(src1.id > ac.destination.id, "The ID of the destination node should be the lowest");
+    ids = [src1.id, src2.id];
+    weak = Components.utils.getWeakReference(src1);
+    is(weak.get(), src1, "The node should support a weak reference");
+  })();
+  function observer(subject, topic, data) {
+    var id = parseInt(data);
+    var index = ids.indexOf(id);
+    if (index != -1) {
+      info("Dropping id " + id + " at index " + index);
+      ids.splice(index, 1);
+      if (ids.length == 0) {
+        SimpleTest.executeSoon(function() {
+          is(weak.get(), null, "The weak reference must be dropped now");
+          Services.obs.removeObserver(observer, "webaudio-node-demise");
+          SimpleTest.finish();
+        });
+      }
+    }
+  }
+  Services.obs.addObserver(observer, "webaudio-node-demise", false);
+
+  forceCC();
+  forceCC();
+
+  function forceCC() {
+    SpecialPowers.DOMWindowUtils.cycleCollect();
+    SpecialPowers.DOMWindowUtils.garbageCollect();
+    SpecialPowers.DOMWindowUtils.garbageCollect();
+  }
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/webidl/AudioNode.webidl
+++ b/dom/webidl/AudioNode.webidl
@@ -38,8 +38,14 @@ interface AudioNode : EventTarget {
     [SetterThrows]
     attribute unsigned long channelCount;
     [SetterThrows]
     attribute ChannelCountMode channelCountMode;
     attribute ChannelInterpretation channelInterpretation;
 
 };
 
+// Mozilla extension
+partial interface AudioNode {
+  [ChromeOnly]
+  readonly attribute unsigned long id;
+};
+