Bug 866434 - Part 1: Make it possible to connect an AudioNode to an AudioParam; r=roc
authorEhsan Akhgari <ehsan@mozilla.com>
Wed, 01 May 2013 18:59:02 -0400
changeset 141679 dde95c320e087ad02a0ff75569f65c334b323b4c
parent 141678 c10cca0c48ec698b0a27e3b9d51cc6e68ba78356
child 141680 6c0be12d00aa36cb309048214a7b3b4e28bf25eb
push id2579
push userakeybl@mozilla.com
push dateMon, 24 Jun 2013 18:52:47 +0000
treeherdermozilla-beta@b69b7de8a05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs866434
milestone23.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 866434 - Part 1: Make it possible to connect an AudioNode to an AudioParam; r=roc
content/media/webaudio/AudioNode.cpp
content/media/webaudio/AudioNode.h
content/media/webaudio/AudioParam.cpp
content/media/webaudio/AudioParam.h
content/media/webaudio/ScriptProcessorNode.h
dom/webidl/AudioNode.webidl
--- a/content/media/webaudio/AudioNode.cpp
+++ b/content/media/webaudio/AudioNode.cpp
@@ -8,24 +8,28 @@
 #include "AudioContext.h"
 #include "nsContentUtils.h"
 #include "mozilla/ErrorResult.h"
 #include "AudioNodeStream.h"
 
 namespace mozilla {
 namespace dom {
 
+static const uint32_t INVALID_PORT = 0xffffffff;
+
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(AudioNode, nsDOMEventTargetHelper)
   tmp->DisconnectFromGraph();
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputNodes)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputParams)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(AudioNode, nsDOMEventTargetHelper)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContext)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputNodes)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputParams)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_ADDREF_INHERITED(AudioNode, nsDOMEventTargetHelper)
 
 NS_IMETHODIMP_(nsrefcnt)
 AudioNode::Release()
 {
   if (mRefCnt.get() == 1) {
@@ -54,16 +58,17 @@ AudioNode::AudioNode(AudioContext* aCont
   nsDOMEventTargetHelper::BindToOwner(aContext->GetParentObject());
   SetIsDOMBinding();
 }
 
 AudioNode::~AudioNode()
 {
   MOZ_ASSERT(mInputNodes.IsEmpty());
   MOZ_ASSERT(mOutputNodes.IsEmpty());
+  MOZ_ASSERT(mOutputParams.IsEmpty());
 }
 
 static uint32_t
 FindIndexOfNode(const nsTArray<AudioNode::InputNode>& aInputNodes, const AudioNode* aNode)
 {
   for (uint32_t i = 0; i < aInputNodes.Length(); ++i) {
     if (aInputNodes[i].mInputNode == aNode) {
       return i;
@@ -109,16 +114,26 @@ AudioNode::DisconnectFromGraph()
     nsRefPtr<AudioNode> output = mOutputNodes[i].forget();
     mOutputNodes.RemoveElementAt(i);
     uint32_t inputIndex = FindIndexOfNode(output->mInputNodes, this);
     // It doesn't matter which one we remove, since we're going to remove all
     // entries for this node anyway.
     output->mInputNodes.RemoveElementAt(inputIndex);
   }
 
+  while (!mOutputParams.IsEmpty()) {
+    uint32_t i = mOutputParams.Length() - 1;
+    nsRefPtr<AudioParam> output = mOutputParams[i].forget();
+    mOutputParams.RemoveElementAt(i);
+    uint32_t inputIndex = FindIndexOfNode(output->InputNodes(), this);
+    // It doesn't matter which one we remove, since we're going to remove all
+    // entries for this node anyway.
+    output->RemoveInputNode(inputIndex);
+  }
+
   DestroyMediaStream();
 }
 
 void
 AudioNode::Connect(AudioNode& aDestination, uint32_t aOutput,
                    uint32_t aInput, ErrorResult& aRv)
 {
   if (aOutput >= NumberOfOutputs() ||
@@ -155,16 +170,43 @@ AudioNode::Connect(AudioNode& aDestinati
       ps->AllocateInputPort(mStream, MediaInputPort::FLAG_BLOCK_INPUT);
   }
 
   // This connection may have connected a panner and a source.
   Context()->UpdatePannerSource();
 }
 
 void
+AudioNode::Connect(AudioParam& aDestination, uint32_t aOutput,
+                   ErrorResult& aRv)
+{
+  if (aOutput >= NumberOfOutputs()) {
+    aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+    return;
+  }
+
+  if (Context() != aDestination.GetParentObject()) {
+    aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+    return;
+  }
+
+  if (FindIndexOfNodeWithPorts(aDestination.InputNodes(), this, INVALID_PORT, aOutput) !=
+      nsTArray<AudioNode::InputNode>::NoIndex) {
+    // connection already exists.
+    return;
+  }
+
+  mOutputParams.AppendElement(&aDestination);
+  InputNode* input = aDestination.AppendInputNode();
+  input->mInputNode = this;
+  input->mInputPort = INVALID_PORT;
+  input->mOutputPort = aOutput;
+}
+
+void
 AudioNode::SendDoubleParameterToStream(uint32_t aIndex, double aValue)
 {
   AudioNodeStream* ns = static_cast<AudioNodeStream*>(mStream.get());
   MOZ_ASSERT(ns, "How come we don't have a stream here?");
   ns->SetDoubleParameter(aIndex, aValue);
 }
 
 void
@@ -219,16 +261,31 @@ AudioNode::Disconnect(uint32_t aOutput, 
         // others, and it's not correct to remove them all since some of them
         // could be for different output ports.
         mOutputNodes.RemoveElementAt(i);
         break;
       }
     }
   }
 
+  for (int32_t i = mOutputParams.Length() - 1; i >= 0; --i) {
+    AudioParam* dest = mOutputParams[i];
+    for (int32_t j = dest->InputNodes().Length() - 1; j >= 0; --j) {
+      const InputNode& input = dest->InputNodes()[j];
+      if (input.mInputNode == this && input.mOutputPort == aOutput) {
+        dest->RemoveInputNode(j);
+        // Remove one instance of 'dest' from mOutputParams. There could be
+        // others, and it's not correct to remove them all since some of them
+        // could be for different output ports.
+        mOutputParams.RemoveElementAt(i);
+        break;
+      }
+    }
+  }
+
   // This disconnection may have disconnected a panner and a source.
   Context()->UpdatePannerSource();
 }
 
 void
 AudioNode::DestroyMediaStream()
 {
   if (mStream) {
@@ -244,10 +301,16 @@ AudioNode::DestroyMediaStream()
       ns->Engine()->ClearNode();
     }
 
     mStream->Destroy();
     mStream = nullptr;
   }
 }
 
+void
+AudioNode::RemoveOutputParam(AudioParam* aParam)
+{
+  mOutputParams.RemoveElement(aParam);
+}
+
 }
 }
--- a/content/media/webaudio/AudioNode.h
+++ b/content/media/webaudio/AudioNode.h
@@ -21,16 +21,17 @@
 struct JSContext;
 
 namespace mozilla {
 
 class ErrorResult;
 
 namespace dom {
 
+class AudioParam;
 struct ThreeDPoint;
 
 template<class T>
 class SelfReference {
 public:
   SelfReference() : mHeld(false) {}
   ~SelfReference()
   {
@@ -103,16 +104,19 @@ public:
   AudioContext* Context() const
   {
     return mContext;
   }
 
   virtual void Connect(AudioNode& aDestination, uint32_t aOutput,
                        uint32_t aInput, ErrorResult& aRv);
 
+  virtual void Connect(AudioParam& aDestination, uint32_t aOutput,
+                       ErrorResult& aRv);
+
   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 uint32_t NumberOfInputs() const { return 1; }
   virtual uint32_t NumberOfOutputs() const { return 1; }
 
@@ -148,28 +152,31 @@ public:
         mStreamPort->Destroy();
       }
     }
 
     // Weak reference.
     AudioNode* mInputNode;
     nsRefPtr<MediaInputPort> mStreamPort;
     // The index of the input port this node feeds into.
+    // This is not used for connections to AudioParams.
     uint32_t mInputPort;
     // The index of the output port this node comes out of.
     uint32_t mOutputPort;
   };
 
   MediaStream* Stream() { return mStream; }
 
   const nsTArray<InputNode>& InputNodes() const
   {
     return mInputNodes;
   }
 
+  void RemoveOutputParam(AudioParam* aParam);
+
 private:
   // This could possibly delete 'this'.
   void DisconnectFromGraph();
 
 protected:
   static void Callback(AudioNode* aNode) { /* not implemented */ }
 
   // Helpers for sending different value types to streams
@@ -192,16 +199,22 @@ private:
   // For every InputNode, there is a corresponding entry in mOutputNodes of the
   // InputNode's mInputNode.
   nsTArray<InputNode> mInputNodes;
   // For every mOutputNode entry, there is a corresponding entry in mInputNodes
   // of the mOutputNode entry. We won't necessarily be able to identify the
   // exact matching entry, since mOutputNodes doesn't include the port
   // identifiers and the same node could be connected on multiple ports.
   nsTArray<nsRefPtr<AudioNode> > mOutputNodes;
+  // For every mOutputParams entry, there is a corresponding entry in
+  // 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;
 };
 
 }
 }
 
--- a/content/media/webaudio/AudioParam.cpp
+++ b/content/media/webaudio/AudioParam.cpp
@@ -8,17 +8,40 @@
 #include "nsContentUtils.h"
 #include "nsIDOMWindow.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/AudioParamBinding.h"
 
 namespace mozilla {
 namespace dom {
 
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_1(AudioParam, mNode)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AudioParam)
+  tmp->DisconnectFromGraph();
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mNode)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AudioParam)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNode)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(AudioParam)
+
+NS_IMPL_CYCLE_COLLECTING_NATIVE_ADDREF(AudioParam)
+
+NS_IMETHODIMP_(nsrefcnt)
+AudioParam::Release()
+{
+  if (mRefCnt.get() == 1) {
+    // We are about to be deleted, disconnect the object from the graph before
+    // the derived type is destroyed.
+    DisconnectFromGraph();
+  }
+  NS_IMPL_CC_NATIVE_RELEASE_BODY(AudioParam)
+}
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(AudioParam, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(AudioParam, Release)
 
 AudioParam::AudioParam(AudioNode* aNode,
                        AudioParam::CallbackType aCallback,
                        float aDefaultValue)
   : AudioParamTimeline(aDefaultValue)
@@ -26,19 +49,35 @@ AudioParam::AudioParam(AudioNode* aNode,
   , mCallback(aCallback)
   , mDefaultValue(aDefaultValue)
 {
   SetIsDOMBinding();
 }
 
 AudioParam::~AudioParam()
 {
+  MOZ_ASSERT(mInputNodes.IsEmpty());
 }
 
 JSObject*
 AudioParam::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
 {
   return AudioParamBinding::Wrap(aCx, aScope, this);
 }
 
+void
+AudioParam::DisconnectFromGraph()
+{
+  // Addref this temporarily so the refcount bumping below doesn't destroy us
+  // prematurely
+  nsRefPtr<AudioParam> kungFuDeathGrip = this;
+
+  while (!mInputNodes.IsEmpty()) {
+    uint32_t i = mInputNodes.Length() - 1;
+    nsRefPtr<AudioNode> input = mInputNodes[i].mInputNode;
+    mInputNodes.RemoveElementAt(i);
+    input->RemoveOutputParam(this);
+  }
+}
+
 }
 }
 
--- a/content/media/webaudio/AudioParam.h
+++ b/content/media/webaudio/AudioParam.h
@@ -32,17 +32,18 @@ class AudioParam MOZ_FINAL : public nsWr
 public:
   typedef void (*CallbackType)(AudioNode*);
 
   AudioParam(AudioNode* aNode,
              CallbackType aCallback,
              float aDefaultValue);
   virtual ~AudioParam();
 
-  NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(AudioParam)
+  NS_IMETHOD_(nsrefcnt) AddRef(void);
+  NS_IMETHOD_(nsrefcnt) Release(void);
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(AudioParam)
 
   AudioContext* GetParentObject() const
   {
     return mNode->Context();
   }
 
   virtual JSObject* WrapObject(JSContext* aCx,
@@ -99,18 +100,42 @@ public:
     mCallback(mNode);
   }
 
   float DefaultValue() const
   {
     return mDefaultValue;
   }
 
+  const nsTArray<AudioNode::InputNode>& InputNodes() const
+  {
+    return mInputNodes;
+  }
+
+  void RemoveInputNode(uint32_t aIndex)
+  {
+    mInputNodes.RemoveElementAt(aIndex);
+  }
+
+  AudioNode::InputNode* AppendInputNode()
+  {
+    return mInputNodes.AppendElement();
+  }
+
+  void DisconnectFromGraph();
+
+protected:
+  nsCycleCollectingAutoRefCnt mRefCnt;
+  NS_DECL_OWNINGTHREAD
+
 private:
   nsRefPtr<AudioNode> mNode;
+  // For every InputNode, there is a corresponding entry in mOutputParams of the
+  // InputNode's mInputNode.
+  nsTArray<AudioNode::InputNode> mInputNodes;
   CallbackType mCallback;
   const float mDefaultValue;
 };
 
 }
 }
 
 #endif
--- a/content/media/webaudio/ScriptProcessorNode.h
+++ b/content/media/webaudio/ScriptProcessorNode.h
@@ -41,16 +41,25 @@ public:
                        uint32_t aInput, ErrorResult& aRv) MOZ_OVERRIDE
   {
     AudioNode::Connect(aDestination, aOutput, aInput, aRv);
     if (!aRv.Failed()) {
       mPlayingRef.Take(this);
     }
   }
 
+  virtual void Connect(AudioParam& aDestination, uint32_t aOutput,
+                       ErrorResult& aRv) MOZ_OVERRIDE
+  {
+    AudioNode::Connect(aDestination, aOutput, aRv);
+    if (!aRv.Failed()) {
+      mPlayingRef.Take(this);
+    }
+  }
+
   virtual void Disconnect(uint32_t aOutput, ErrorResult& aRv) MOZ_OVERRIDE
   {
     AudioNode::Disconnect(aOutput, aRv);
     if (!aRv.Failed()) {
       mPlayingRef.Drop(this);
     }
   }
 
--- a/dom/webidl/AudioNode.webidl
+++ b/dom/webidl/AudioNode.webidl
@@ -21,20 +21,18 @@ enum ChannelInterpretation {
     "discrete"
 };
 
 [PrefControlled]
 interface AudioNode : EventTarget {
 
     [Throws]
     void connect(AudioNode destination, optional unsigned long output = 0, optional unsigned long input = 0);
-
-    // [Throws]
-    // void connect(AudioParam destination, optional unsigned long output = 0);
-
+    [Throws]
+    void connect(AudioParam destination, optional unsigned long output = 0);
     [Throws]
     void disconnect(optional unsigned long output = 0);
 
     readonly attribute AudioContext context;
     readonly attribute unsigned long numberOfInputs;
     readonly attribute unsigned long numberOfOutputs;
 
     // Channel up-mixing and down-mixing rules for all inputs.