content/media/webaudio/AudioDestinationNode.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 #include "AudioDestinationNode.h"
     8 #include "mozilla/dom/AudioDestinationNodeBinding.h"
     9 #include "mozilla/Preferences.h"
    10 #include "AudioChannelAgent.h"
    11 #include "AudioChannelService.h"
    12 #include "AudioNodeEngine.h"
    13 #include "AudioNodeStream.h"
    14 #include "MediaStreamGraph.h"
    15 #include "OfflineAudioCompletionEvent.h"
    16 #include "nsIInterfaceRequestorUtils.h"
    17 #include "nsIDocShell.h"
    18 #include "nsIPermissionManager.h"
    19 #include "nsIScriptObjectPrincipal.h"
    20 #include "nsServiceManagerUtils.h"
    21 #include "nsIAppShell.h"
    22 #include "nsWidgetsCID.h"
    24 namespace mozilla {
    25 namespace dom {
    27 static uint8_t gWebAudioOutputKey;
    29 class OfflineDestinationNodeEngine : public AudioNodeEngine
    30 {
    31 public:
    32   typedef AutoFallibleTArray<nsAutoArrayPtr<float>, 2> InputChannels;
    34   OfflineDestinationNodeEngine(AudioDestinationNode* aNode,
    35                                uint32_t aNumberOfChannels,
    36                                uint32_t aLength,
    37                                float aSampleRate)
    38     : AudioNodeEngine(aNode)
    39     , mWriteIndex(0)
    40     , mLength(aLength)
    41     , mSampleRate(aSampleRate)
    42   {
    43     // These allocations might fail if content provides a huge number of
    44     // channels or size, but it's OK since we'll deal with the failure
    45     // gracefully.
    46     if (mInputChannels.SetLength(aNumberOfChannels)) {
    47       static const fallible_t fallible = fallible_t();
    48       for (uint32_t i = 0; i < aNumberOfChannels; ++i) {
    49         mInputChannels[i] = new(fallible) float[aLength];
    50         if (!mInputChannels[i]) {
    51           mInputChannels.Clear();
    52           break;
    53         }
    54       }
    55     }
    56   }
    58   virtual void ProcessBlock(AudioNodeStream* aStream,
    59                             const AudioChunk& aInput,
    60                             AudioChunk* aOutput,
    61                             bool* aFinished) MOZ_OVERRIDE
    62   {
    63     // Do this just for the sake of political correctness; this output
    64     // will not go anywhere.
    65     *aOutput = aInput;
    67     // Handle the case of allocation failure in the input buffer
    68     if (mInputChannels.IsEmpty()) {
    69       return;
    70     }
    72     if (mWriteIndex >= mLength) {
    73       NS_ASSERTION(mWriteIndex == mLength, "Overshot length");
    74       // Don't record any more.
    75       return;
    76     }
    78     // Record our input buffer
    79     MOZ_ASSERT(mWriteIndex < mLength, "How did this happen?");
    80     const uint32_t duration = std::min(WEBAUDIO_BLOCK_SIZE, mLength - mWriteIndex);
    81     const uint32_t commonChannelCount = std::min(mInputChannels.Length(),
    82                                                  aInput.mChannelData.Length());
    83     // First, copy as many channels in the input as we have
    84     for (uint32_t i = 0; i < commonChannelCount; ++i) {
    85       if (aInput.IsNull()) {
    86         PodZero(mInputChannels[i] + mWriteIndex, duration);
    87       } else {
    88         const float* inputBuffer = static_cast<const float*>(aInput.mChannelData[i]);
    89         if (duration == WEBAUDIO_BLOCK_SIZE) {
    90           // Use the optimized version of the copy with scale operation
    91           AudioBlockCopyChannelWithScale(inputBuffer, aInput.mVolume,
    92                                          mInputChannels[i] + mWriteIndex);
    93         } else {
    94           if (aInput.mVolume == 1.0f) {
    95             PodCopy(mInputChannels[i] + mWriteIndex, inputBuffer, duration);
    96           } else {
    97             for (uint32_t j = 0; j < duration; ++j) {
    98               mInputChannels[i][mWriteIndex + j] = aInput.mVolume * inputBuffer[j];
    99             }
   100           }
   101         }
   102       }
   103     }
   104     // Then, silence all of the remaining channels
   105     for (uint32_t i = commonChannelCount; i < mInputChannels.Length(); ++i) {
   106       PodZero(mInputChannels[i] + mWriteIndex, duration);
   107     }
   108     mWriteIndex += duration;
   110     if (mWriteIndex >= mLength) {
   111       NS_ASSERTION(mWriteIndex == mLength, "Overshot length");
   112       // Go to finished state. When the graph's current time eventually reaches
   113       // the end of the stream, then the main thread will be notified and we'll
   114       // shut down the AudioContext.
   115       *aFinished = true;
   116     }
   117   }
   119   void FireOfflineCompletionEvent(AudioDestinationNode* aNode)
   120   {
   121     AudioContext* context = aNode->Context();
   122     context->Shutdown();
   123     // Shutdown drops self reference, but the context is still referenced by aNode,
   124     // which is strongly referenced by the runnable that called
   125     // AudioDestinationNode::FireOfflineCompletionEvent.
   127     AutoPushJSContext cx(context->GetJSContext());
   128     if (!cx) {
   130       return;
   131     }
   132     JSAutoRequest ar(cx);
   134     // Create the input buffer
   135     ErrorResult rv;
   136     nsRefPtr<AudioBuffer> renderedBuffer =
   137       AudioBuffer::Create(context, mInputChannels.Length(),
   138                           mLength, mSampleRate, cx, rv);
   139     if (rv.Failed()) {
   140       return;
   141     }
   142     for (uint32_t i = 0; i < mInputChannels.Length(); ++i) {
   143       renderedBuffer->SetRawChannelContents(cx, i, mInputChannels[i]);
   144     }
   146     nsRefPtr<OfflineAudioCompletionEvent> event =
   147         new OfflineAudioCompletionEvent(context, nullptr, nullptr);
   148     event->InitEvent(renderedBuffer);
   149     context->DispatchTrustedEvent(event);
   150   }
   152   virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
   153   {
   154     size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
   155     amount += mInputChannels.SizeOfExcludingThis(aMallocSizeOf);
   156     return amount;
   157   }
   159   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
   160   {
   161     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   162   }
   164 private:
   165   // The input to the destination node is recorded in the mInputChannels buffer.
   166   // When this buffer fills up with mLength frames, the buffered input is sent
   167   // to the main thread in order to dispatch OfflineAudioCompletionEvent.
   168   InputChannels mInputChannels;
   169   // An index representing the next offset in mInputChannels to be written to.
   170   uint32_t mWriteIndex;
   171   // How many frames the OfflineAudioContext intends to produce.
   172   uint32_t mLength;
   173   float mSampleRate;
   174 };
   176 class DestinationNodeEngine : public AudioNodeEngine
   177 {
   178 public:
   179   explicit DestinationNodeEngine(AudioDestinationNode* aNode)
   180     : AudioNodeEngine(aNode)
   181     , mVolume(1.0f)
   182   {
   183   }
   185   virtual void ProcessBlock(AudioNodeStream* aStream,
   186                             const AudioChunk& aInput,
   187                             AudioChunk* aOutput,
   188                             bool* aFinished) MOZ_OVERRIDE
   189   {
   190     *aOutput = aInput;
   191     aOutput->mVolume *= mVolume;
   192   }
   194   virtual void SetDoubleParameter(uint32_t aIndex, double aParam) MOZ_OVERRIDE
   195   {
   196     if (aIndex == VOLUME) {
   197       mVolume = aParam;
   198     }
   199   }
   201   enum Parameters {
   202     VOLUME,
   203   };
   205   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
   206   {
   207     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   208   }
   210 private:
   211   float mVolume;
   212 };
   214 static bool UseAudioChannelService()
   215 {
   216   return Preferences::GetBool("media.useAudioChannelService");
   217 }
   219 NS_IMPL_CYCLE_COLLECTION_INHERITED(AudioDestinationNode, AudioNode,
   220                                    mAudioChannelAgent)
   222 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(AudioDestinationNode)
   223   NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
   224   NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback)
   225   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
   226 NS_INTERFACE_MAP_END_INHERITING(AudioNode)
   228 NS_IMPL_ADDREF_INHERITED(AudioDestinationNode, AudioNode)
   229 NS_IMPL_RELEASE_INHERITED(AudioDestinationNode, AudioNode)
   231 AudioDestinationNode::AudioDestinationNode(AudioContext* aContext,
   232                                            bool aIsOffline,
   233                                            AudioChannel aChannel,
   234                                            uint32_t aNumberOfChannels,
   235                                            uint32_t aLength,
   236                                            float aSampleRate)
   237   : AudioNode(aContext,
   238               aIsOffline ? aNumberOfChannels : 2,
   239               ChannelCountMode::Explicit,
   240               ChannelInterpretation::Speakers)
   241   , mFramesToProduce(aLength)
   242   , mAudioChannel(AudioChannel::Normal)
   243   , mIsOffline(aIsOffline)
   244   , mHasFinished(false)
   245   , mExtraCurrentTime(0)
   246   , mExtraCurrentTimeSinceLastStartedBlocking(0)
   247   , mExtraCurrentTimeUpdatedSinceLastStableState(false)
   248 {
   249   MediaStreamGraph* graph = aIsOffline ?
   250                             MediaStreamGraph::CreateNonRealtimeInstance(aSampleRate) :
   251                             MediaStreamGraph::GetInstance();
   252   AudioNodeEngine* engine = aIsOffline ?
   253                             new OfflineDestinationNodeEngine(this, aNumberOfChannels,
   254                                                              aLength, aSampleRate) :
   255                             static_cast<AudioNodeEngine*>(new DestinationNodeEngine(this));
   257   mStream = graph->CreateAudioNodeStream(engine, MediaStreamGraph::EXTERNAL_STREAM);
   258   mStream->SetAudioChannelType(aChannel);
   259   mStream->AddMainThreadListener(this);
   260   mStream->AddAudioOutput(&gWebAudioOutputKey);
   262   if (aChannel != AudioChannel::Normal) {
   263     ErrorResult rv;
   264     SetMozAudioChannelType(aChannel, rv);
   265   }
   267   if (!aIsOffline && UseAudioChannelService()) {
   268     nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(GetOwner());
   269     if (target) {
   270       target->AddSystemEventListener(NS_LITERAL_STRING("visibilitychange"), this,
   271                                      /* useCapture = */ true,
   272                                      /* wantsUntrusted = */ false);
   273     }
   275     CreateAudioChannelAgent();
   276   }
   277 }
   279 size_t
   280 AudioDestinationNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
   281 {
   282   size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
   283   // Might be useful in the future:
   284   // - mAudioChannelAgent
   285   return amount;
   286 }
   288 size_t
   289 AudioDestinationNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
   290 {
   291   return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   292 }
   294 void
   295 AudioDestinationNode::DestroyMediaStream()
   296 {
   297   if (mAudioChannelAgent && !Context()->IsOffline()) {
   298     mAudioChannelAgent->StopPlaying();
   299     mAudioChannelAgent = nullptr;
   301     nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(GetOwner());
   302     NS_ENSURE_TRUE_VOID(target);
   304     target->RemoveSystemEventListener(NS_LITERAL_STRING("visibilitychange"), this,
   305                                       /* useCapture = */ true);
   306   }
   308   if (!mStream)
   309     return;
   311   mStream->RemoveMainThreadListener(this);
   312   MediaStreamGraph* graph = mStream->Graph();
   313   if (graph->IsNonRealtime()) {
   314     MediaStreamGraph::DestroyNonRealtimeInstance(graph);
   315   }
   316   AudioNode::DestroyMediaStream();
   317 }
   319 void
   320 AudioDestinationNode::NotifyMainThreadStateChanged()
   321 {
   322   if (mStream->IsFinished() && !mHasFinished) {
   323     mHasFinished = true;
   324     if (mIsOffline) {
   325       nsCOMPtr<nsIRunnable> runnable =
   326         NS_NewRunnableMethod(this, &AudioDestinationNode::FireOfflineCompletionEvent);
   327       NS_DispatchToCurrentThread(runnable);
   328     }
   329   }
   330 }
   332 void
   333 AudioDestinationNode::FireOfflineCompletionEvent()
   334 {
   335   AudioNodeStream* stream = static_cast<AudioNodeStream*>(Stream());
   336   OfflineDestinationNodeEngine* engine =
   337     static_cast<OfflineDestinationNodeEngine*>(stream->Engine());
   338   engine->FireOfflineCompletionEvent(this);
   339 }
   341 uint32_t
   342 AudioDestinationNode::MaxChannelCount() const
   343 {
   344   return Context()->MaxChannelCount();
   345 }
   347 void
   348 AudioDestinationNode::SetChannelCount(uint32_t aChannelCount, ErrorResult& aRv)
   349 {
   350   if (aChannelCount > MaxChannelCount()) {
   351     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
   352     return;
   353   }
   355   AudioNode::SetChannelCount(aChannelCount, aRv);
   356 }
   358 void
   359 AudioDestinationNode::Mute()
   360 {
   361   MOZ_ASSERT(Context() && !Context()->IsOffline());
   362   SendDoubleParameterToStream(DestinationNodeEngine::VOLUME, 0.0f);
   363 }
   365 void
   366 AudioDestinationNode::Unmute()
   367 {
   368   MOZ_ASSERT(Context() && !Context()->IsOffline());
   369   SendDoubleParameterToStream(DestinationNodeEngine::VOLUME, 1.0f);
   370 }
   372 void
   373 AudioDestinationNode::OfflineShutdown()
   374 {
   375   MOZ_ASSERT(Context() && Context()->IsOffline(),
   376              "Should only be called on a valid OfflineAudioContext");
   378   MediaStreamGraph::DestroyNonRealtimeInstance(mStream->Graph());
   379   mOfflineRenderingRef.Drop(this);
   380 }
   382 JSObject*
   383 AudioDestinationNode::WrapObject(JSContext* aCx)
   384 {
   385   return AudioDestinationNodeBinding::Wrap(aCx, this);
   386 }
   388 void
   389 AudioDestinationNode::StartRendering()
   390 {
   391   mOfflineRenderingRef.Take(this);
   392   mStream->Graph()->StartNonRealtimeProcessing(TrackRate(Context()->SampleRate()), mFramesToProduce);
   393 }
   395 void
   396 AudioDestinationNode::SetCanPlay(bool aCanPlay)
   397 {
   398   mStream->SetTrackEnabled(AudioNodeStream::AUDIO_TRACK, aCanPlay);
   399 }
   401 NS_IMETHODIMP
   402 AudioDestinationNode::HandleEvent(nsIDOMEvent* aEvent)
   403 {
   404   nsAutoString type;
   405   aEvent->GetType(type);
   407   if (!type.EqualsLiteral("visibilitychange")) {
   408     return NS_ERROR_FAILURE;
   409   }
   411   nsCOMPtr<nsIDocShell> docshell = do_GetInterface(GetOwner());
   412   NS_ENSURE_TRUE(docshell, NS_ERROR_FAILURE);
   414   bool isActive = false;
   415   docshell->GetIsActive(&isActive);
   417   mAudioChannelAgent->SetVisibilityState(isActive);
   418   return NS_OK;
   419 }
   421 NS_IMETHODIMP
   422 AudioDestinationNode::CanPlayChanged(int32_t aCanPlay)
   423 {
   424   SetCanPlay(aCanPlay == AudioChannelState::AUDIO_CHANNEL_STATE_NORMAL);
   425   return NS_OK;
   426 }
   428 NS_IMETHODIMP
   429 AudioDestinationNode::WindowVolumeChanged()
   430 {
   431   MOZ_ASSERT(mAudioChannelAgent);
   433   if (!mStream) {
   434     return NS_OK;
   435   }
   437   float volume;
   438   nsresult rv = mAudioChannelAgent->GetWindowVolume(&volume);
   439   NS_ENSURE_SUCCESS(rv, rv);
   441   mStream->SetAudioOutputVolume(&gWebAudioOutputKey, volume);
   442   return NS_OK;
   443 }
   445 AudioChannel
   446 AudioDestinationNode::MozAudioChannelType() const
   447 {
   448   return mAudioChannel;
   449 }
   451 void
   452 AudioDestinationNode::SetMozAudioChannelType(AudioChannel aValue, ErrorResult& aRv)
   453 {
   454   if (Context()->IsOffline()) {
   455     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   456     return;
   457   }
   459   if (aValue != mAudioChannel &&
   460       CheckAudioChannelPermissions(aValue)) {
   461     mAudioChannel = aValue;
   463     if (mAudioChannelAgent) {
   464       CreateAudioChannelAgent();
   465     }
   466   }
   467 }
   469 bool
   470 AudioDestinationNode::CheckAudioChannelPermissions(AudioChannel aValue)
   471 {
   472   if (!Preferences::GetBool("media.useAudioChannelService")) {
   473     return true;
   474   }
   476   // Only normal channel doesn't need permission.
   477   if (aValue == AudioChannel::Normal) {
   478     return true;
   479   }
   481   // Maybe this audio channel is equal to the default one.
   482   if (aValue == AudioChannelService::GetDefaultAudioChannel()) {
   483     return true;
   484   }
   486   nsCOMPtr<nsIPermissionManager> permissionManager =
   487     do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
   488   if (!permissionManager) {
   489     return false;
   490   }
   492   nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(GetOwner());
   493   NS_ASSERTION(sop, "Window didn't QI to nsIScriptObjectPrincipal!");
   494   nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
   496   uint32_t perm = nsIPermissionManager::UNKNOWN_ACTION;
   498   nsCString channel;
   499   channel.AssignASCII(AudioChannelValues::strings[uint32_t(aValue)].value,
   500                       AudioChannelValues::strings[uint32_t(aValue)].length);
   501   permissionManager->TestExactPermissionFromPrincipal(principal,
   502     nsCString(NS_LITERAL_CSTRING("audio-channel-") + channel).get(),
   503     &perm);
   505   return perm == nsIPermissionManager::ALLOW_ACTION;
   506 }
   508 void
   509 AudioDestinationNode::CreateAudioChannelAgent()
   510 {
   511   if (mAudioChannelAgent) {
   512     mAudioChannelAgent->StopPlaying();
   513   }
   515   mAudioChannelAgent = new AudioChannelAgent();
   516   mAudioChannelAgent->InitWithWeakCallback(GetOwner(),
   517                                            static_cast<int32_t>(mAudioChannel),
   518                                            this);
   520   nsCOMPtr<nsIDocShell> docshell = do_GetInterface(GetOwner());
   521   if (docshell) {
   522     bool isActive = false;
   523     docshell->GetIsActive(&isActive);
   524     mAudioChannelAgent->SetVisibilityState(isActive);
   525   }
   527   int32_t state = 0;
   528   mAudioChannelAgent->StartPlaying(&state);
   529   SetCanPlay(state == AudioChannelState::AUDIO_CHANNEL_STATE_NORMAL);
   530 }
   532 void
   533 AudioDestinationNode::NotifyStableState()
   534 {
   535   mExtraCurrentTimeUpdatedSinceLastStableState = false;
   536 }
   538 static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
   540 void
   541 AudioDestinationNode::ScheduleStableStateNotification()
   542 {
   543   nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID);
   544   if (appShell) {
   545     nsCOMPtr<nsIRunnable> event =
   546       NS_NewRunnableMethod(this, &AudioDestinationNode::NotifyStableState);
   547     appShell->RunInStableState(event);
   548   }
   549 }
   551 double
   552 AudioDestinationNode::ExtraCurrentTime()
   553 {
   554   if (!mStartedBlockingDueToBeingOnlyNode.IsNull() &&
   555       !mExtraCurrentTimeUpdatedSinceLastStableState) {
   556     mExtraCurrentTimeUpdatedSinceLastStableState = true;
   557     mExtraCurrentTimeSinceLastStartedBlocking =
   558       (TimeStamp::Now() - mStartedBlockingDueToBeingOnlyNode).ToSeconds();
   559     ScheduleStableStateNotification();
   560   }
   561   return mExtraCurrentTime + mExtraCurrentTimeSinceLastStartedBlocking;
   562 }
   564 void
   565 AudioDestinationNode::SetIsOnlyNodeForContext(bool aIsOnlyNode)
   566 {
   567   if (!mStartedBlockingDueToBeingOnlyNode.IsNull() == aIsOnlyNode) {
   568     // Nothing changed.
   569     return;
   570   }
   572   if (!mStream) {
   573     // DestroyMediaStream has been called, presumably during CC Unlink().
   574     return;
   575   }
   577   if (mIsOffline) {
   578     // Don't block the destination stream for offline AudioContexts, since
   579     // we expect the zero data produced when there are no other nodes to
   580     // show up in its result buffer. Also, we would get confused by adding
   581     // ExtraCurrentTime before StartRendering has even been called.
   582     return;
   583   }
   585   if (aIsOnlyNode) {
   586     mStream->ChangeExplicitBlockerCount(1);
   587     mStartedBlockingDueToBeingOnlyNode = TimeStamp::Now();
   588     // Don't do an update of mExtraCurrentTimeSinceLastStartedBlocking until the next stable state.
   589     mExtraCurrentTimeUpdatedSinceLastStableState = true;
   590     ScheduleStableStateNotification();
   591   } else {
   592     // Force update of mExtraCurrentTimeSinceLastStartedBlocking if necessary
   593     ExtraCurrentTime();
   594     mExtraCurrentTime += mExtraCurrentTimeSinceLastStartedBlocking;
   595     mExtraCurrentTimeSinceLastStartedBlocking = 0;
   596     mStream->ChangeExplicitBlockerCount(-1);
   597     mStartedBlockingDueToBeingOnlyNode = TimeStamp();
   598   }
   599 }
   601 }
   603 }

mercurial