Bug 804837. Part 0: Rework the connection and input/output port logic for Web Audio nodes; r=ehsan

Here's what this patch does:
-- Makes AudioNodes mostly not use nsWrapperCache. AudioDestinationNode
still does.
-- Rename MaxNumberOfInputs/Outputs to NumberOfInputs/Outputs, and have them
default to 1 in AudioNode.
-- Allow any number of nodes to be connected to any given input/output port.
This commit is contained in:
Robert O'Callahan 2013-01-23 19:50:18 -05:00
parent d50fa15151
commit fc44cd635c
21 changed files with 147 additions and 204 deletions

View File

@ -24,10 +24,9 @@ AudioBufferSourceNode::AudioBufferSourceNode(AudioContext* aContext)
}
JSObject*
AudioBufferSourceNode::WrapObject(JSContext* aCx, JSObject* aScope,
bool* aTriedToWrap)
AudioBufferSourceNode::WrapObject(JSContext* aCx, JSObject* aScope)
{
return AudioBufferSourceNodeBinding::Wrap(aCx, aScope, this, aTriedToWrap);
return AudioBufferSourceNodeBinding::Wrap(aCx, aScope, this);
}
void

View File

@ -21,8 +21,7 @@ public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AudioBufferSourceNode, AudioSourceNode)
virtual JSObject* WrapObject(JSContext* aCx, JSObject* aScope,
bool* aTriedToWrap);
virtual JSObject* WrapObject(JSContext* aCx, JSObject* aScope);
void Start(double) { /* no-op for now */ }
void Stop(double) { /* no-op for now */ }

View File

@ -6,15 +6,25 @@
#include "AudioDestinationNode.h"
#include "mozilla/dom/AudioDestinationNodeBinding.h"
#include "nsContentUtils.h"
namespace mozilla {
namespace dom {
NS_IMPL_ISUPPORTS_INHERITED0(AudioDestinationNode, AudioNode)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(AudioDestinationNode, AudioNode)
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(AudioDestinationNode, AudioNode) \
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(AudioDestinationNode)
AudioDestinationNode::AudioDestinationNode(AudioContext* aContext)
: AudioNode(aContext)
{
SetIsDOMBinding();
}
JSObject*
@ -26,4 +36,3 @@ AudioDestinationNode::WrapObject(JSContext* aCx, JSObject* aScope,
}
}

View File

@ -14,21 +14,24 @@ namespace dom {
class AudioContext;
class AudioDestinationNode : public AudioNode
/**
* Need to have an nsWrapperCache on AudioDestinationNodes since
* AudioContext.destination returns them.
*/
class AudioDestinationNode : public AudioNode,
public nsWrapperCache
{
public:
explicit AudioDestinationNode(AudioContext* aContext);
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(AudioDestinationNode,
AudioNode)
virtual JSObject* WrapObject(JSContext* aCx, JSObject* aScope,
bool* aTriedToWrap);
virtual uint32_t MaxNumberOfInputs() const MOZ_FINAL MOZ_OVERRIDE
{
return 1;
}
virtual uint32_t MaxNumberOfOutputs() const MOZ_FINAL MOZ_OVERRIDE
virtual uint32_t NumberOfOutputs() const MOZ_FINAL MOZ_OVERRIDE
{
return 0;
}

View File

@ -14,29 +14,27 @@ namespace dom {
inline void
ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
mozilla::dom::AudioNode::Output& aField,
AudioNode::InputNode& aField,
const char* aName,
unsigned aFlags)
{
CycleCollectionNoteChild(aCallback, aField.mDestination.get(), aName, aFlags);
CycleCollectionNoteChild(aCallback, aField.mInputNode.get(), aName, aFlags);
}
inline void
ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
mozilla::dom::AudioNode::Input& aField,
const char* aName,
unsigned aFlags)
ImplCycleCollectionUnlink(nsCycleCollectionTraversalCallback& aCallback,
AudioNode::InputNode& aField,
const char* aName,
unsigned aFlags)
{
CycleCollectionNoteChild(aCallback, aField.mSource.get(), aName, aFlags);
aField.mInputNode = nullptr;
}
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_3(AudioNode,
mContext, mInputs, mOutputs)
NS_IMPL_CYCLE_COLLECTION_3(AudioNode, mContext, mInputNodes, mOutputNodes)
NS_IMPL_CYCLE_COLLECTING_ADDREF(AudioNode)
NS_IMPL_CYCLE_COLLECTING_RELEASE(AudioNode)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AudioNode)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
@ -44,15 +42,34 @@ AudioNode::AudioNode(AudioContext* aContext)
: mContext(aContext)
{
MOZ_ASSERT(aContext);
SetIsDOMBinding();
}
AudioNode::~AudioNode()
{
MOZ_ASSERT(mInputNodes.IsEmpty());
MOZ_ASSERT(mOutputNodes.IsEmpty());
}
static uint32_t
FindIndexOfNodeWithPorts(const nsTArray<AudioNode::InputNode>& aInputNodes, const AudioNode* aNode,
uint32_t aInputPort, uint32_t aOutputPort)
{
for (uint32_t i = 0; i < aInputNodes.Length(); ++i) {
if (aInputNodes[i].mInputNode == aNode &&
aInputNodes[i].mInputPort == aInputPort &&
aInputNodes[i].mOutputPort == aOutputPort) {
return i;
}
}
return nsTArray<AudioNode::InputNode>::NoIndex;
}
void
AudioNode::Connect(AudioNode& aDestination, uint32_t aOutput,
uint32_t aInput, ErrorResult& aRv)
{
if (aOutput >= MaxNumberOfOutputs() ||
aInput >= aDestination.MaxNumberOfInputs()) {
if (aOutput >= NumberOfOutputs() ||
aInput >= aDestination.NumberOfInputs()) {
aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
return;
}
@ -62,14 +79,23 @@ AudioNode::Connect(AudioNode& aDestination, uint32_t aOutput,
return;
}
// XXX handle cycle detection per spec
if (FindIndexOfNodeWithPorts(aDestination.mInputNodes, this, aInput, aOutput) !=
nsTArray<AudioNode::InputNode>::NoIndex) {
// connection already exists.
return;
}
Output output(&aDestination, aInput);
mOutputs.EnsureLengthAtLeast(aOutput + 1);
mOutputs.ReplaceElementAt(aOutput, output);
Input input(this, aOutput);
aDestination.mInputs.EnsureLengthAtLeast(aInput + 1);
aDestination.mInputs.ReplaceElementAt(aInput, input);
// The MediaStreamGraph will handle cycle detection. We don't need to do it
// here.
// Addref this temporarily so the refcount bumping below doesn't destroy us
nsRefPtr<AudioNode> kungFuDeathGrip = this;
mOutputNodes.AppendElement(&aDestination);
InputNode* input = aDestination.mInputNodes.AppendElement();
input->mInputNode = this;
input->mInputPort = aInput;
input->mOutputPort = aOutput;
}
void
@ -80,27 +106,26 @@ AudioNode::Disconnect(uint32_t aOutput, ErrorResult& aRv)
return;
}
// We do a copy of the objects to AddRef source and destination
// objects so that they don't go away before we're done here.
const Output output = mOutputs[aOutput];
if (!output) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return;
// Disconnect everything connected to this output. First find the
// corresponding inputs and remove them.
nsAutoTArray<nsRefPtr<AudioNode>,4> outputsToUpdate;
for (int32_t i = mOutputNodes.Length() - 1; i >= 0; --i) {
AudioNode* dest = mOutputNodes[i];
for (int32_t j = dest->mInputNodes.Length() - 1; j >= 0; --j) {
InputNode& input = dest->mInputNodes[j];
if (input.mInputNode == this && input.mOutputPort == aOutput) {
dest->mInputNodes.RemoveElementAt(j);
// Remove one instance of 'dest' from mOutputNodes. There could be
// others, and it's not correct to remove them all since some of them
// could be for different output ports.
*outputsToUpdate.AppendElement() = mOutputNodes[i].forget();
mOutputNodes.RemoveElementAt(i);
break;
}
}
}
const Input input = output.mDestination->mInputs[output.mInput];
MOZ_ASSERT(Context() == output.mDestination->Context());
MOZ_ASSERT(aOutput == input.mOutput);
if (!input || input.mSource != this) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return;
}
output.mDestination->mInputs.ReplaceElementAt(output.mInput, Input());
mOutputs.ReplaceElementAt(input.mOutput, Output());
}
}
}

View File

@ -7,7 +7,6 @@
#ifndef AudioNode_h_
#define AudioNode_h_
#include "nsWrapperCache.h"
#include "nsCycleCollectionParticipant.h"
#include "mozilla/Attributes.h"
#include "EnableWebAudioCheck.h"
@ -24,15 +23,14 @@ class ErrorResult;
namespace dom {
class AudioNode : public nsISupports,
public nsWrapperCache,
public EnableWebAudioCheck
{
public:
explicit AudioNode(AudioContext* aContext);
virtual ~AudioNode() {}
virtual ~AudioNode();
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AudioNode)
NS_DECL_CYCLE_COLLECTION_CLASS(AudioNode)
AudioContext* GetParentObject() const
{
@ -49,71 +47,33 @@ public:
void Disconnect(uint32_t aOutput, ErrorResult& aRv);
uint32_t NumberOfInputs() const
{
return mInputs.Length();
}
uint32_t NumberOfOutputs() const
{
return mOutputs.Length();
}
// The following two virtual methods must be implemented by each node type
// to provide the maximum number of input and outputs they accept.
virtual uint32_t MaxNumberOfInputs() const = 0;
virtual uint32_t MaxNumberOfOutputs() const = 0;
// 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; }
struct Output {
enum { InvalidIndex = 0xffffffff };
Output()
: mInput(InvalidIndex)
{
}
Output(AudioNode* aDestination, uint32_t aInput)
: mDestination(aDestination),
mInput(aInput)
{
}
// Check whether the slot is valid
typedef void**** ConvertibleToBool;
operator ConvertibleToBool() const {
return ConvertibleToBool(mDestination && mInput != InvalidIndex);
}
nsRefPtr<AudioNode> mDestination;
// This is an index into mDestination->mInputs which specifies the Input
// object corresponding to this Output node.
const uint32_t mInput;
};
struct Input {
enum { InvalidIndex = 0xffffffff };
Input()
: mOutput(InvalidIndex)
{
}
Input(AudioNode* aSource, uint32_t aOutput)
: mSource(aSource),
mOutput(aOutput)
{
}
// Check whether the slot is valid
typedef void**** ConvertibleToBool;
operator ConvertibleToBool() const {
return ConvertibleToBool(mSource && mOutput != InvalidIndex);
}
nsRefPtr<AudioNode> mSource;
// This is an index into mSource->mOutputs which specifies the Output
// object corresponding to this Input node.
const uint32_t mOutput;
struct InputNode {
// Strong reference.
// May be null if the source node has gone away.
nsRefPtr<AudioNode> mInputNode;
// The index of the input port this node feeds into.
uint32_t mInputPort;
// The index of the output port this node comes out of.
uint32_t mOutputPort;
};
private:
nsRefPtr<AudioContext> mContext;
nsTArray<Input> mInputs;
nsTArray<Output> mOutputs;
// 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;
};
}

View File

@ -19,15 +19,10 @@ public:
NS_DECL_ISUPPORTS_INHERITED
virtual uint32_t MaxNumberOfInputs() const MOZ_FINAL MOZ_OVERRIDE
virtual uint32_t NumberOfInputs() const MOZ_FINAL MOZ_OVERRIDE
{
return 0;
}
virtual uint32_t MaxNumberOfOutputs() const MOZ_FINAL MOZ_OVERRIDE
{
return 1;
}
};
}

View File

@ -37,10 +37,9 @@ BiquadFilterNode::BiquadFilterNode(AudioContext* aContext)
}
JSObject*
BiquadFilterNode::WrapObject(JSContext* aCx, JSObject* aScope,
bool* aTriedToWrap)
BiquadFilterNode::WrapObject(JSContext* aCx, JSObject* aScope)
{
return BiquadFilterNodeBinding::Wrap(aCx, aScope, this, aTriedToWrap);
return BiquadFilterNodeBinding::Wrap(aCx, aScope, this);
}
}

View File

@ -37,17 +37,7 @@ public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(BiquadFilterNode, AudioNode)
virtual JSObject* WrapObject(JSContext* aCx, JSObject* aScope,
bool* aTriedToWrap);
virtual uint32_t MaxNumberOfInputs() const MOZ_FINAL MOZ_OVERRIDE
{
return 1;
}
virtual uint32_t MaxNumberOfOutputs() const MOZ_FINAL MOZ_OVERRIDE
{
return 1;
}
virtual JSObject* WrapObject(JSContext* aCx, JSObject* aScope);
uint16_t Type() const
{

View File

@ -21,15 +21,14 @@ NS_IMPL_RELEASE_INHERITED(DelayNode, AudioNode)
DelayNode::DelayNode(AudioContext* aContext, double aMaxDelay)
: AudioNode(aContext)
, mDelay(new AudioParam(aContext, 0.0f, 0.0f, aMaxDelay))
, mDelay(new AudioParam(aContext, 0.0f, 0.0f, float(aMaxDelay)))
{
}
JSObject*
DelayNode::WrapObject(JSContext* aCx, JSObject* aScope,
bool* aTriedToWrap)
DelayNode::WrapObject(JSContext* aCx, JSObject* aScope)
{
return DelayNodeBinding::Wrap(aCx, aScope, this, aTriedToWrap);
return DelayNodeBinding::Wrap(aCx, aScope, this);
}
}

View File

@ -23,17 +23,7 @@ public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DelayNode, AudioNode)
virtual JSObject* WrapObject(JSContext* aCx, JSObject* aScope,
bool* aTriedToWrap);
virtual uint32_t MaxNumberOfInputs() const MOZ_FINAL MOZ_OVERRIDE
{
return 1;
}
virtual uint32_t MaxNumberOfOutputs() const MOZ_FINAL MOZ_OVERRIDE
{
return 1;
}
virtual JSObject* WrapObject(JSContext* aCx, JSObject* aScope);
AudioParam* DelayTime() const
{

View File

@ -36,10 +36,9 @@ DynamicsCompressorNode::DynamicsCompressorNode(AudioContext* aContext)
}
JSObject*
DynamicsCompressorNode::WrapObject(JSContext* aCx, JSObject* aScope,
bool* aTriedToWrap)
DynamicsCompressorNode::WrapObject(JSContext* aCx, JSObject* aScope)
{
return DynamicsCompressorNodeBinding::Wrap(aCx, aScope, this, aTriedToWrap);
return DynamicsCompressorNodeBinding::Wrap(aCx, aScope, this);
}
}

View File

@ -23,17 +23,7 @@ public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DynamicsCompressorNode, AudioNode)
virtual JSObject* WrapObject(JSContext* aCx, JSObject* aScope,
bool* aTriedToWrap);
virtual uint32_t MaxNumberOfInputs() const MOZ_FINAL MOZ_OVERRIDE
{
return 1;
}
virtual uint32_t MaxNumberOfOutputs() const MOZ_FINAL MOZ_OVERRIDE
{
return 1;
}
virtual JSObject* WrapObject(JSContext* aCx, JSObject* aScope);
AudioParam* Threshold() const
{

View File

@ -26,10 +26,9 @@ GainNode::GainNode(AudioContext* aContext)
}
JSObject*
GainNode::WrapObject(JSContext* aCx, JSObject* aScope,
bool* aTriedToWrap)
GainNode::WrapObject(JSContext* aCx, JSObject* aScope)
{
return GainNodeBinding::Wrap(aCx, aScope, this, aTriedToWrap);
return GainNodeBinding::Wrap(aCx, aScope, this);
}
}

View File

@ -23,17 +23,7 @@ public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(GainNode, AudioNode)
virtual JSObject* WrapObject(JSContext* aCx, JSObject* aScope,
bool* aTriedToWrap);
virtual uint32_t MaxNumberOfInputs() const MOZ_FINAL MOZ_OVERRIDE
{
return 1;
}
virtual uint32_t MaxNumberOfOutputs() const MOZ_FINAL MOZ_OVERRIDE
{
return 1;
}
virtual JSObject* WrapObject(JSContext* aCx, JSObject* aScope);
AudioParam* Gain() const
{

View File

@ -46,6 +46,7 @@ EXPORTS_mozilla/dom := \
BiquadFilterNode.h \
DelayNode.h \
DynamicsCompressorNode.h \
EnableWebAudioCheck.h \
GainNode.h \
PannerNode.h \
$(NULL)

View File

@ -27,10 +27,9 @@ PannerNode::PannerNode(AudioContext* aContext)
}
JSObject*
PannerNode::WrapObject(JSContext* aCx, JSObject* aScope,
bool* aTriedToWrap)
PannerNode::WrapObject(JSContext* aCx, JSObject* aScope)
{
return PannerNodeBinding::Wrap(aCx, aScope, this, aTriedToWrap);
return PannerNodeBinding::Wrap(aCx, aScope, this);
}
}

View File

@ -36,17 +36,7 @@ class PannerNode : public AudioNode
public:
explicit PannerNode(AudioContext* aContext);
virtual JSObject* WrapObject(JSContext* aCx, JSObject* aScope,
bool* aTriedToWrap);
virtual uint32_t MaxNumberOfInputs() const MOZ_FINAL MOZ_OVERRIDE
{
return 1;
}
virtual uint32_t MaxNumberOfOutputs() const MOZ_FINAL MOZ_OVERRIDE
{
return 1;
}
virtual JSObject* WrapObject(JSContext* aCx, JSObject* aScope);
uint16_t PanningModel() const
{

View File

@ -22,13 +22,13 @@ addLoadEvent(function() {
var destination = context.destination;
is(destination.context, context, "Destination node has proper context");
is(destination.context, context, "Destination node has proper context");
is(destination.numberOfInputs, 0, "Destination node has 0 inputs");
is(destination.numberOfInputs, 1, "Destination node has 1 inputs");
is(destination.numberOfOutputs, 0, "Destination node has 0 outputs");
var source = context.createBufferSource();
is(source.context, context, "Source node has proper context");
is(source.numberOfInputs, 0, "Source node has 0 inputs");
is(source.numberOfOutputs, 0, "Source node has 0 outputs");
is(source.numberOfOutputs, 1, "Source node has 1 outputs");
ok(!source.buffer, "Source node should not have a buffer when it's created");
source.buffer = buffer;

View File

@ -97,6 +97,10 @@ DOMInterfaces = {
'resultNotAddRefed': [ 'destination', 'listener' ],
},
'AudioBufferSourceNode': {
'wrapperCache': False
},
'AudioListener' : {
'nativeOwnership': 'refcounted'
},
@ -115,6 +119,7 @@ DOMInterfaces = {
'BiquadFilterNode': {
'resultNotAddRefed': [ 'frequency', 'q', 'gain' ],
'wrapperCache': False
},
'Blob': [
@ -190,6 +195,7 @@ DOMInterfaces = {
'DelayNode': [
{
'resultNotAddRefed': [ 'delayTime' ],
'wrapperCache': False
}],
'Document': [
@ -242,14 +248,14 @@ DOMInterfaces = {
}
},
'DynamicsCompressorNode': [
{
'DynamicsCompressorNode': {
'resultNotAddRefed': [ 'threshold', 'knee', 'ratio',
'reduction', 'attack', 'release' ],
'binaryNames': {
'release': 'getRelease'
}
}],
},
'wrapperCache': False
},
'Element': {
'hasXPConnectImpls': True,
@ -316,10 +322,10 @@ DOMInterfaces = {
'nativeType': 'JSObject'
}],
'GainNode': [
{
'GainNode': {
'resultNotAddRefed': [ 'gain' ],
}],
'wrapperCache': False
},
'HTMLAnchorElement': {
'binaryNames': {
@ -548,6 +554,7 @@ DOMInterfaces = {
'PannerNode': [
{
'resultNotAddRefed': [ 'coneGain', 'distanceGain' ],
'wrapperCache': False
}],
'Performance': {

View File

@ -13,10 +13,10 @@
[PrefControlled]
interface AudioBufferSourceNode : AudioSourceNode {
const unsigned short UNSCHEDULED_STATE = 0;
const unsigned short SCHEDULED_STATE = 1;
const unsigned short PLAYING_STATE = 2;
const unsigned short FINISHED_STATE = 3;
//const unsigned short UNSCHEDULED_STATE = 0;
//const unsigned short SCHEDULED_STATE = 1;
//const unsigned short PLAYING_STATE = 2;
//const unsigned short FINISHED_STATE = 3;
//readonly attribute unsigned short playbackState;