Bug 924870 - AudioContext.mozAudioChannel attribute, r=ehsan
authorAndrea Marchesini <amarchesini@mozilla.com>
Fri, 11 Oct 2013 13:55:47 +0200
changeset 165212 387ff170ae27d0cb5ed8661c7c09fec4a1b2bb02
parent 165211 06879533a0732b74c70cbc6ee17b8346a77dcf74
child 165213 9182050761f446cd7a20ab3490d7d57b32efbfd4
push id428
push userbbajaj@mozilla.com
push dateTue, 28 Jan 2014 00:16:25 +0000
treeherdermozilla-release@cd72a7ff3a75 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan
bugs924870
milestone27.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 924870 - AudioContext.mozAudioChannel attribute, r=ehsan
content/media/webaudio/AudioContext.cpp
content/media/webaudio/AudioContext.h
content/media/webaudio/AudioDestinationNode.cpp
content/media/webaudio/AudioDestinationNode.h
content/media/webaudio/test/mochitest.ini
content/media/webaudio/test/test_mozaudiochannel.html
dom/webidl/AudioContext.webidl
--- a/content/media/webaudio/AudioContext.cpp
+++ b/content/media/webaudio/AudioContext.cpp
@@ -603,10 +603,22 @@ AudioContext::Mute() const
 
 void
 AudioContext::Unmute() const
 {
   MOZ_ASSERT(!mIsOffline);
   mDestination->Unmute();
 }
 
+AudioChannel
+AudioContext::MozAudioChannelType() const
+{
+  return mDestination->MozAudioChannelType();
+}
+
+void
+AudioContext::SetMozAudioChannelType(AudioChannel aValue, ErrorResult& aRv)
+{
+  mDestination->SetMozAudioChannelType(aValue, aRv);
+}
+
 }
 }
--- a/content/media/webaudio/AudioContext.h
+++ b/content/media/webaudio/AudioContext.h
@@ -2,16 +2,17 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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/. */
 
 #ifndef AudioContext_h_
 #define AudioContext_h_
 
+#include "mozilla/dom/AudioContextBinding.h"
 #include "EnableWebAudioCheck.h"
 #include "MediaBufferDecoder.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/TypedArray.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsDOMEventTargetHelper.h"
@@ -233,16 +234,19 @@ public:
 
   uint32_t MaxChannelCount() const;
 
   void Mute() const;
   void Unmute() const;
 
   JSContext* GetJSContext() const;
 
+  AudioChannel MozAudioChannelType() const;
+  void SetMozAudioChannelType(AudioChannel aValue, ErrorResult& aRv);
+
 private:
   void RemoveFromDecodeQueue(WebAudioDecodeJob* aDecodeJob);
   void ShutdownDecoder();
 
   friend struct ::mozilla::WebAudioDecodeJob;
 
 private:
   // Note that it's important for mSampleRate to be initialized before
--- a/content/media/webaudio/AudioDestinationNode.cpp
+++ b/content/media/webaudio/AudioDestinationNode.cpp
@@ -9,17 +9,19 @@
 #include "mozilla/Preferences.h"
 #include "AudioChannelAgent.h"
 #include "AudioNodeEngine.h"
 #include "AudioNodeStream.h"
 #include "MediaStreamGraph.h"
 #include "OfflineAudioCompletionEvent.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIDocShell.h"
-#include "nsIDocument.h"
+#include "nsIPermissionManager.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsServiceManagerUtils.h"
 
 namespace mozilla {
 namespace dom {
 
 class OfflineDestinationNodeEngine : public AudioNodeEngine
 {
 public:
   typedef AutoFallibleTArray<nsAutoArrayPtr<float>, 2> InputChannels;
@@ -239,49 +241,37 @@ AudioDestinationNode::AudioDestinationNo
                                            uint32_t aNumberOfChannels,
                                            uint32_t aLength,
                                            float aSampleRate)
   : AudioNode(aContext,
               aIsOffline ? aNumberOfChannels : 2,
               ChannelCountMode::Explicit,
               ChannelInterpretation::Speakers)
   , mFramesToProduce(aLength)
+  , mAudioChannel(AudioChannel::Normal)
 {
   MediaStreamGraph* graph = aIsOffline ?
                             MediaStreamGraph::CreateNonRealtimeInstance() :
                             MediaStreamGraph::GetInstance();
   AudioNodeEngine* engine = aIsOffline ?
                             new OfflineDestinationNodeEngine(this, aNumberOfChannels,
                                                              aLength, aSampleRate) :
                             static_cast<AudioNodeEngine*>(new DestinationNodeEngine(this));
 
   mStream = graph->CreateAudioNodeStream(engine, MediaStreamGraph::EXTERNAL_STREAM);
 
   if (!aIsOffline && UseAudioChannelService()) {
-    mAudioChannelAgent = new AudioChannelAgent();
-    mAudioChannelAgent->InitWithWeakCallback(nsIAudioChannelAgent::AUDIO_AGENT_CHANNEL_NORMAL,
-                                             this);
-
     nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(GetOwner());
     if (target) {
       target->AddSystemEventListener(NS_LITERAL_STRING("visibilitychange"), this,
                                      /* useCapture = */ true,
                                      /* wantsUntrusted = */ false);
     }
 
-    nsCOMPtr<nsIDocShell> docshell = do_GetInterface(GetOwner());
-    if (docshell) {
-      bool isActive = false;
-      docshell->GetIsActive(&isActive);
-      mAudioChannelAgent->SetVisibilityState(isActive);
-    }
-
-    int32_t state = 0;
-    mAudioChannelAgent->StartPlaying(&state);
-    SetCanPlay(state == AudioChannelState::AUDIO_CHANNEL_STATE_NORMAL);
+    CreateAudioChannelAgent();
   }
 }
 
 void
 AudioDestinationNode::DestroyMediaStream()
 {
   if (mAudioChannelAgent && !Context()->IsOffline()) {
     mAudioChannelAgent->StopPlaying();
@@ -386,10 +376,122 @@ AudioDestinationNode::HandleEvent(nsIDOM
 
 NS_IMETHODIMP
 AudioDestinationNode::CanPlayChanged(int32_t aCanPlay)
 {
   SetCanPlay(aCanPlay == AudioChannelState::AUDIO_CHANNEL_STATE_NORMAL);
   return NS_OK;
 }
 
+AudioChannel
+AudioDestinationNode::MozAudioChannelType() const
+{
+  return mAudioChannel;
+}
+
+void
+AudioDestinationNode::SetMozAudioChannelType(AudioChannel aValue, ErrorResult& aRv)
+{
+  if (Context()->IsOffline()) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return;
+  }
+
+  if (aValue != mAudioChannel &&
+      CheckAudioChannelPermissions(aValue)) {
+    mAudioChannel = aValue;
+
+    if (mAudioChannelAgent) {
+      CreateAudioChannelAgent();
+    }
+  }
+}
+
+bool
+AudioDestinationNode::CheckAudioChannelPermissions(AudioChannel aValue)
+{
+  if (!Preferences::GetBool("media.useAudioChannelService")) {
+    return true;
+  }
+
+  // Only normal channel doesn't need permission.
+  if (aValue == AudioChannel::Normal) {
+    return true;
+  }
+
+  nsCOMPtr<nsIPermissionManager> permissionManager =
+    do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
+  if (!permissionManager) {
+    return false;
+  }
+
+  nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(GetOwner());
+  NS_ASSERTION(sop, "Window didn't QI to nsIScriptObjectPrincipal!");
+  nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
+
+  uint32_t perm = nsIPermissionManager::UNKNOWN_ACTION;
+
+  nsCString channel;
+  channel.AssignASCII(AudioChannelValues::strings[uint32_t(aValue)].value,
+                      AudioChannelValues::strings[uint32_t(aValue)].length);
+  permissionManager->TestExactPermissionFromPrincipal(principal,
+    nsCString(NS_LITERAL_CSTRING("audio-channel-") + channel).get(),
+    &perm);
+
+  return perm == nsIPermissionManager::ALLOW_ACTION;
+}
+
+void
+AudioDestinationNode::CreateAudioChannelAgent()
+{
+  if (mAudioChannelAgent) {
+    mAudioChannelAgent->StopPlaying();
+  }
+
+  AudioChannelType type = AUDIO_CHANNEL_NORMAL;
+  switch(mAudioChannel) {
+    case AudioChannel::Normal:
+      type = AUDIO_CHANNEL_NORMAL;
+      break;
+
+    case AudioChannel::Content:
+      type = AUDIO_CHANNEL_CONTENT;
+      break;
+
+    case AudioChannel::Notification:
+      type = AUDIO_CHANNEL_NOTIFICATION;
+      break;
+
+    case AudioChannel::Alarm:
+      type = AUDIO_CHANNEL_ALARM;
+      break;
+
+    case AudioChannel::Telephony:
+      type = AUDIO_CHANNEL_TELEPHONY;
+      break;
+
+    case AudioChannel::Ringer:
+      type = AUDIO_CHANNEL_RINGER;
+      break;
+
+    case AudioChannel::Publicnotification:
+      type = AUDIO_CHANNEL_PUBLICNOTIFICATION;
+      break;
+
+  }
+
+  mAudioChannelAgent = new AudioChannelAgent();
+  mAudioChannelAgent->InitWithWeakCallback(type, this);
+
+  nsCOMPtr<nsIDocShell> docshell = do_GetInterface(GetOwner());
+  if (docshell) {
+    bool isActive = false;
+    docshell->GetIsActive(&isActive);
+    mAudioChannelAgent->SetVisibilityState(isActive);
+  }
+
+  int32_t state = 0;
+  mAudioChannelAgent->StartPlaying(&state);
+  SetCanPlay(state == AudioChannelState::AUDIO_CHANNEL_STATE_NORMAL);
 }
 }
+
+}
--- a/content/media/webaudio/AudioDestinationNode.h
+++ b/content/media/webaudio/AudioDestinationNode.h
@@ -2,19 +2,21 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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/. */
 
 #ifndef AudioDestinationNode_h_
 #define AudioDestinationNode_h_
 
+#include "mozilla/dom/AudioContextBinding.h"
 #include "AudioNode.h"
 #include "nsIDOMEventListener.h"
 #include "nsIAudioChannelAgent.h"
+#include "AudioChannelCommon.h"
 
 namespace mozilla {
 namespace dom {
 
 class AudioContext;
 
 class AudioDestinationNode : public AudioNode
                            , public nsIDOMEventListener
@@ -54,22 +56,31 @@ public:
   void OfflineShutdown();
 
   // nsIDOMEventListener
   NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent);
 
   // nsIAudioChannelAgentCallback
   NS_IMETHOD CanPlayChanged(int32_t aCanPlay);
 
+  AudioChannel MozAudioChannelType() const;
+  void SetMozAudioChannelType(AudioChannel aValue, ErrorResult& aRv);
+
 private:
+  bool CheckAudioChannelPermissions(AudioChannel aValue);
+  void CreateAudioChannelAgent();
+
   void SetCanPlay(bool aCanPlay);
 
   SelfReference<AudioDestinationNode> mOfflineRenderingRef;
   uint32_t mFramesToProduce;
 
   nsCOMPtr<nsIAudioChannelAgent> mAudioChannelAgent;
+
+  // Audio Channel Type.
+  AudioChannel mAudioChannel;
 };
 
 }
 }
 
 #endif
 
--- a/content/media/webaudio/test/mochitest.ini
+++ b/content/media/webaudio/test/mochitest.ini
@@ -105,8 +105,9 @@ support-files =
 [test_scriptProcessorNode.html]
 [test_scriptProcessorNodeChannelCount.html]
 [test_scriptProcessorNodeZeroInputOutput.html]
 [test_singleSourceDest.html]
 [test_waveShaper.html]
 [test_waveShaperNoCurve.html]
 [test_waveShaperZeroLengthCurve.html]
 [test_audioDestinationNode.html]
+[test_mozaudiochannel.html]
new file mode 100644
--- /dev/null
+++ b/content/media/webaudio/test/test_mozaudiochannel.html
@@ -0,0 +1,90 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for mozaudiochannel</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+function test_basic() {
+  var ac = new AudioContext();
+  ok(ac, "AudioContext created");
+
+  // Default
+  is(ac.mozAudioChannelType, "normal", "Default ac channel == 'normal'");
+
+  // random wrong channel
+  ac.mozAudioChannelType = "foo";
+  is(ac.mozAudioChannelType, "normal", "Default ac channel == 'normal'");
+
+  // Unpermitted channels
+  ac.mozAudioChannelType = "content";
+  is(ac.mozAudioChannelType, "normal", "Default ac channel == 'normal'");
+
+  ac.mozAudioChannelType = "notification";
+  is(ac.mozAudioChannelType, "normal", "Default ac channel == 'normal'");
+
+  ac.mozAudioChannelType = "alarm";
+  is(ac.mozAudioChannelType, "normal", "Default ac channel == 'normal'");
+
+  ac.mozAudioChannelType = "telephony";
+  is(ac.mozAudioChannelType, "normal", "Default ac channel == 'normal'");
+
+  ac.mozAudioChannelType = "ringer";
+  is(ac.mozAudioChannelType, "normal", "Default ac channel == 'normal'");
+
+  ac.mozAudioChannelType = "publicnotification";
+  is(ac.mozAudioChannelType, "normal", "Default ac channel == 'normal'");
+
+  runTest();
+}
+
+function test_permission(aChannel) {
+  var ac = new AudioContext();
+  ok(ac, "AudioContext created");
+
+  is(ac.mozAudioChannelType, "normal", "Default ac channel == 'normal'");
+
+  SpecialPowers.pushPermissions(
+    [{ "type": "audio-channel-" + aChannel, "allow": 1, "context": document }],
+    function() {
+      ac.mozAudioChannelType = aChannel;
+      is(ac.mozAudioChannelType, aChannel, "Default ac channel == '" + aChannel + "'");
+      runTest();
+    }
+  );
+}
+
+var tests = [
+  test_basic,
+
+  function() { test_permission("content"); },
+  function() { test_permission("notification"); },
+  function() { test_permission("alarm"); },
+  function() { test_permission("telephony"); },
+  function() { test_permission("ringer"); },
+  function() { test_permission("publicnotification"); }
+];
+
+function runTest() {
+  if (!tests.length) {
+    SimpleTest.finish();
+    return;
+  }
+
+  var test = tests.shift();
+  test();
+}
+
+SpecialPowers.pushPrefEnv({"set": [["media.useAudioChannelService", true ]]}, runTest);
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/webidl/AudioContext.webidl
+++ b/dom/webidl/AudioContext.webidl
@@ -93,9 +93,24 @@ partial interface AudioContext {
 
     // Same as createScriptProcessor()
     [Creator, Throws, Pref="media.webaudio.legacy.AudioContext"]
     ScriptProcessorNode createJavaScriptNode(optional unsigned long bufferSize = 0,
                                              optional unsigned long numberOfInputChannels = 2,
                                              optional unsigned long numberOfOutputChannels = 2);
 };
 
+enum AudioChannel {
+  "normal",
+  "content",
+  "notification",
+  "alarm",
+  "telephony",
+  "ringer",
+  "publicnotification",
+};
 
+// Mozilla extensions
+partial interface AudioContext {
+  // Read HTMLMediaElement.webidl for more information about this attribute.
+  [Pref="media.useAudioChannelService", SetterThrows]
+  attribute AudioChannel mozAudioChannelType;
+};