Backed out changeset bcfbdb934c37 (bug 1113086) for breaking cpp tests with timeouts in TestAudioChannelService.exe
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Sat, 11 Jul 2015 14:14:58 +0200
changeset 252444 727b321502a6a9ea22dbcb3cd496d3db9c444a80
parent 252443 245563f697d152a0801662ec3622e46e7c3ad76e
child 252445 4120dbd77a5dd1fc8399833752f4d1ce38294485
push id62151
push usercbook@mozilla.com
push dateSat, 11 Jul 2015 12:15:21 +0000
treeherdermozilla-inbound@727b321502a6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1113086
milestone42.0a1
backs outbcfbdb934c379b70c8ba584ce69cd9a424309aa7
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
Backed out changeset bcfbdb934c37 (bug 1113086) for breaking cpp tests with timeouts in TestAudioChannelService.exe
browser/installer/package-manifest.in
dom/audiochannel/AudioChannelAgent.cpp
dom/audiochannel/AudioChannelAgent.h
dom/audiochannel/AudioChannelCommon.h
dom/audiochannel/AudioChannelService.cpp
dom/audiochannel/AudioChannelService.h
dom/audiochannel/AudioChannelServiceChild.cpp
dom/audiochannel/AudioChannelServiceChild.h
dom/audiochannel/moz.build
dom/audiochannel/nsIAudioChannelAgent.idl
dom/audiochannel/nsIAudioChannelService.idl
dom/audiochannel/tests/AudioChannelChromeScript.js
dom/audiochannel/tests/TestAudioChannelService.cpp
dom/audiochannel/tests/audio.ogg
dom/audiochannel/tests/file_audio.html
dom/audiochannel/tests/file_telephonyPolicy.html
dom/audiochannel/tests/mochitest.ini
dom/audiochannel/tests/moz.build
dom/audiochannel/tests/test_audioChannelChange.html
dom/audiochannel/tests/test_telephonyPolicy.html
dom/base/nsGkAtomList.h
dom/base/nsGlobalWindow.cpp
dom/base/nsPIDOMWindow.h
dom/browser-element/BrowserElementAudioChannel.cpp
dom/browser-element/BrowserElementAudioChannel.h
dom/browser-element/BrowserElementChildPreload.js
dom/browser-element/BrowserElementParent.cpp
dom/browser-element/BrowserElementParent.js
dom/browser-element/mochitest/browserElement_AudioChannel.js
dom/browser-element/mochitest/file_audio.html
dom/browser-element/mochitest/iframe_file_audio.html
dom/browser-element/mochitest/mochitest-oop.ini
dom/browser-element/mochitest/mochitest.ini
dom/browser-element/mochitest/test_browserElement_inproc_AudioChannel.html
dom/browser-element/mochitest/test_browserElement_oop_AudioChannel.html
dom/browser-element/moz.build
dom/browser-element/nsIBrowserElementAPI.idl
dom/camera/DOMCameraControl.cpp
dom/fmradio/FMRadio.cpp
dom/fmradio/FMRadio.h
dom/html/HTMLAudioElement.cpp
dom/html/HTMLMediaElement.cpp
dom/html/HTMLMediaElement.h
dom/html/nsBrowserElement.cpp
dom/html/nsBrowserElement.h
dom/html/nsGenericHTMLFrameElement.cpp
dom/html/nsGenericHTMLFrameElement.h
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PBrowser.ipdl
dom/ipc/PContent.ipdl
dom/ipc/ProcessPriorityManager.cpp
dom/ipc/TabChild.cpp
dom/ipc/TabMessageUtils.h
dom/ipc/TabParent.cpp
dom/ipc/TabParent.h
dom/media/webaudio/AudioDestinationNode.cpp
dom/media/webaudio/AudioDestinationNode.h
dom/media/webaudio/moz.build
dom/media/webaudio/test/browser.ini
dom/media/webaudio/test/browser_mozAudioChannel.html
dom/media/webaudio/test/browser_mozAudioChannel.js
dom/media/webaudio/test/browser_mozAudioChannel_muted.html
dom/media/webaudio/test/browser_mozAudioChannel_muted.js
dom/speakermanager/SpeakerManager.cpp
dom/speakermanager/SpeakerManagerService.cpp
dom/speakermanager/SpeakerManagerServiceChild.cpp
dom/system/gonk/AudioChannelManager.cpp
dom/system/gonk/AudioManager.cpp
dom/webidl/BrowserElementAudioChannel.webidl
dom/webidl/moz.build
layout/build/nsLayoutModule.cpp
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -883,18 +883,16 @@ bin/libfreebl_32int64_3.so
 @RESPATH@/webapprt/modules/RemoteDebugger.jsm
 @RESPATH@/webapprt/modules/WebRTCHandler.jsm
 #endif
 
 @RESPATH@/components/DataStore.manifest
 @RESPATH@/components/DataStoreImpl.js
 @RESPATH@/components/dom_datastore.xpt
 
-@RESPATH@/components/dom_audiochannel.xpt
-
 ; Shutdown Terminator
 @RESPATH@/components/nsTerminatorTelemetry.js
 @RESPATH@/components/terminator.manifest
 
 #if defined(CLANG_CXX)
 #if defined(MOZ_ASAN) || defined(MOZ_TSAN)
 @BINPATH@/llvm-symbolizer
 #endif
--- a/dom/audiochannel/AudioChannelAgent.cpp
+++ b/dom/audiochannel/AudioChannelAgent.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
 #include "AudioChannelAgent.h"
+#include "AudioChannelCommon.h"
 #include "AudioChannelService.h"
 #include "nsIDOMWindow.h"
 #include "nsPIDOMWindow.h"
 #include "nsXULAppAPI.h"
 
 using namespace mozilla::dom;
 
 NS_IMPL_CYCLE_COLLECTION(AudioChannelAgent, mWindow, mCallback)
@@ -20,16 +21,18 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(AudioChannelAgent)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(AudioChannelAgent)
 
 AudioChannelAgent::AudioChannelAgent()
   : mAudioChannelType(AUDIO_AGENT_CHANNEL_ERROR)
   , mIsRegToService(false)
+  , mVisible(true)
+  , mWithVideo(false)
 {
 }
 
 AudioChannelAgent::~AudioChannelAgent()
 {
   if (mIsRegToService) {
     StopPlaying();
   }
@@ -58,20 +61,31 @@ NS_IMETHODIMP
 AudioChannelAgent::InitWithWeakCallback(nsIDOMWindow* aWindow,
                                         int32_t aChannelType,
                                         nsIAudioChannelAgentCallback *aCallback)
 {
   return InitInternal(aWindow, aChannelType, aCallback,
                       /* useWeakRef = */ true);
 }
 
+/* void initWithVideo(in nsIDOMWindow window, in long channelType,
+ *                    in nsIAudioChannelAgentCallback callback, in boolean weak); */
+NS_IMETHODIMP
+AudioChannelAgent::InitWithVideo(nsIDOMWindow* aWindow, int32_t aChannelType,
+                                 nsIAudioChannelAgentCallback *aCallback,
+                                 bool aUseWeakRef)
+{
+  return InitInternal(aWindow, aChannelType, aCallback, aUseWeakRef,
+                      /* withVideo = */ true);
+}
+
 nsresult
 AudioChannelAgent::InitInternal(nsIDOMWindow* aWindow, int32_t aChannelType,
                                 nsIAudioChannelAgentCallback *aCallback,
-                                bool aUseWeakRef)
+                                bool aUseWeakRef, bool aWithVideo)
 {
   // We syncd the enum of channel type between nsIAudioChannelAgent.idl and
   // AudioChannelBinding.h the same.
   MOZ_ASSERT(int(AUDIO_AGENT_CHANNEL_NORMAL) == int(AudioChannel::Normal) &&
              int(AUDIO_AGENT_CHANNEL_CONTENT) == int(AudioChannel::Content) &&
              int(AUDIO_AGENT_CHANNEL_NOTIFICATION) == int(AudioChannel::Notification) &&
              int(AUDIO_AGENT_CHANNEL_ALARM) == int(AudioChannel::Alarm) &&
              int(AUDIO_AGENT_CHANNEL_TELEPHONY) == int(AudioChannel::Telephony) &&
@@ -81,70 +95,91 @@ AudioChannelAgent::InitInternal(nsIDOMWi
 
   if (mAudioChannelType != AUDIO_AGENT_CHANNEL_ERROR ||
       aChannelType > AUDIO_AGENT_CHANNEL_PUBLICNOTIFICATION ||
       aChannelType < AUDIO_AGENT_CHANNEL_NORMAL) {
     return NS_ERROR_FAILURE;
   }
 
   if (aWindow) {
-    nsCOMPtr<nsIDOMWindow> topWindow;
-    aWindow->GetScriptableTop(getter_AddRefs(topWindow));
-    MOZ_ASSERT(topWindow);
+    nsCOMPtr<nsPIDOMWindow> pWindow = do_QueryInterface(aWindow);
+    if (!pWindow->IsInnerWindow()) {
+      pWindow = pWindow->GetCurrentInnerWindow();
+    }
 
-    mWindow = do_QueryInterface(topWindow);
-    mWindow = mWindow->GetOuterWindow();
+    mWindow = pWindow.forget();
   }
 
   mAudioChannelType = aChannelType;
 
   if (aUseWeakRef) {
     mWeakCallback = do_GetWeakReference(aCallback);
   } else {
     mCallback = aCallback;
   }
 
+  mWithVideo = aWithVideo;
+
   return NS_OK;
 }
 
 /* boolean startPlaying (); */
-NS_IMETHODIMP AudioChannelAgent::StartPlaying(float *aVolume, bool* aMuted)
+NS_IMETHODIMP AudioChannelAgent::StartPlaying(int32_t *_retval)
 {
-  MOZ_ASSERT(aVolume);
-  MOZ_ASSERT(aMuted);
-
-  nsRefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
+  AudioChannelService *service = AudioChannelService::GetOrCreateAudioChannelService();
   if (mAudioChannelType == AUDIO_AGENT_CHANNEL_ERROR ||
       service == nullptr || mIsRegToService) {
     return NS_ERROR_FAILURE;
   }
 
   service->RegisterAudioChannelAgent(this,
-    static_cast<AudioChannel>(mAudioChannelType));
-
-  service->GetState(mWindow, mAudioChannelType, aVolume, aMuted);
-
+    static_cast<AudioChannel>(mAudioChannelType), mWithVideo);
+  *_retval = service->GetState(this, !mVisible);
   mIsRegToService = true;
   return NS_OK;
 }
 
 /* void stopPlaying (); */
 NS_IMETHODIMP AudioChannelAgent::StopPlaying(void)
 {
   if (mAudioChannelType == AUDIO_AGENT_CHANNEL_ERROR ||
       !mIsRegToService) {
     return NS_ERROR_FAILURE;
   }
 
-  nsRefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
+  AudioChannelService *service = AudioChannelService::GetOrCreateAudioChannelService();
   service->UnregisterAudioChannelAgent(this);
   mIsRegToService = false;
   return NS_OK;
 }
 
+/* void setVisibilityState (in boolean visible); */
+NS_IMETHODIMP AudioChannelAgent::SetVisibilityState(bool visible)
+{
+  bool oldVisibility = mVisible;
+
+  nsCOMPtr<nsIAudioChannelAgentCallback> callback = GetCallback();
+
+  mVisible = visible;
+  if (mIsRegToService && oldVisibility != mVisible && callback) {
+    AudioChannelService *service = AudioChannelService::GetOrCreateAudioChannelService();
+    callback->CanPlayChanged(service->GetState(this, !mVisible));
+  }
+  return NS_OK;
+}
+
+void AudioChannelAgent::NotifyAudioChannelStateChanged()
+{
+  nsCOMPtr<nsIAudioChannelAgentCallback> callback = GetCallback();
+  if (callback) {
+    AudioChannelService *service = AudioChannelService::GetOrCreateAudioChannelService();
+    callback->CanPlayChanged(service->GetState(this, !mVisible));
+  }
+}
+
 already_AddRefed<nsIAudioChannelAgentCallback>
 AudioChannelAgent::GetCallback()
 {
   nsCOMPtr<nsIAudioChannelAgentCallback> callback = mCallback;
   if (!callback) {
     callback = do_QueryReferent(mWeakCallback);
   }
   return callback.forget();
@@ -153,22 +188,25 @@ AudioChannelAgent::GetCallback()
 void
 AudioChannelAgent::WindowVolumeChanged()
 {
   nsCOMPtr<nsIAudioChannelAgentCallback> callback = GetCallback();
   if (!callback) {
     return;
   }
 
-  float volume = 1.0;
-  bool muted = false;
-
-  nsRefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
-  service->GetState(mWindow, mAudioChannelType, &volume, &muted);
-
-  callback->WindowVolumeChanged(volume, muted);
+  callback->WindowVolumeChanged();
 }
 
-uint64_t
-AudioChannelAgent::WindowID() const
+NS_IMETHODIMP
+AudioChannelAgent::GetWindowVolume(float* aVolume)
 {
-  return mWindow ? mWindow->WindowID() : 0;
+  NS_ENSURE_ARG_POINTER(aVolume);
+
+  nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(mWindow);
+  if (!win) {
+    *aVolume = 1.0f;
+    return NS_OK;
+  }
+
+  *aVolume = win->GetAudioGlobalVolume();
+  return NS_OK;
 }
--- a/dom/audiochannel/AudioChannelAgent.h
+++ b/dom/audiochannel/AudioChannelAgent.h
@@ -12,57 +12,56 @@
 #include "nsCOMPtr.h"
 #include "nsWeakPtr.h"
 
 #define NS_AUDIOCHANNELAGENT_CONTRACTID "@mozilla.org/audiochannelagent;1"
 // f27688e2-3dd7-11e2-904e-10bf48d64bd4
 #define NS_AUDIOCHANNELAGENT_CID {0xf27688e2, 0x3dd7, 0x11e2, \
       {0x90, 0x4e, 0x10, 0xbf, 0x48, 0xd6, 0x4b, 0xd4}}
 
-class nsPIDOMWindow;
+class nsIDOMWindow;
 
 namespace mozilla {
 namespace dom {
 
 /* Header file */
 class AudioChannelAgent : public nsIAudioChannelAgent
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_NSIAUDIOCHANNELAGENT
 
   NS_DECL_CYCLE_COLLECTION_CLASS(AudioChannelAgent)
 
   AudioChannelAgent();
+  virtual void NotifyAudioChannelStateChanged();
 
   void WindowVolumeChanged();
 
-  nsPIDOMWindow* Window() const
+  nsIDOMWindow* Window() const
   {
     return mWindow;
   }
 
-  uint64_t WindowID() const;
-
 private:
   virtual ~AudioChannelAgent();
 
   // Returns mCallback if that's non-null, or otherwise tries to get an
   // nsIAudioChannelAgentCallback out of mWeakCallback.
   already_AddRefed<nsIAudioChannelAgentCallback> GetCallback();
 
   nsresult InitInternal(nsIDOMWindow* aWindow, int32_t aAudioAgentType,
                         nsIAudioChannelAgentCallback* aCallback,
-                        bool aUseWeakRef);
+                        bool aUseWeakRef, bool aWithVideo=false);
 
-  nsCOMPtr<nsPIDOMWindow> mWindow;
+  nsCOMPtr<nsIDOMWindow> mWindow;
   nsCOMPtr<nsIAudioChannelAgentCallback> mCallback;
-
   nsWeakPtr mWeakCallback;
-
   int32_t mAudioChannelType;
   bool mIsRegToService;
+  bool mVisible;
+  bool mWithVideo;
 };
 
 } // namespace dom
 } // namespace mozilla
 #endif
 
rename from dom/webidl/BrowserElementAudioChannel.webidl
rename to dom/audiochannel/AudioChannelCommon.h
--- a/dom/webidl/BrowserElementAudioChannel.webidl
+++ b/dom/audiochannel/AudioChannelCommon.h
@@ -1,37 +1,24 @@
-/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/.
- */
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-[Pref="dom.mozBrowserFramesEnabled",
- CheckPermissions="browser"]
-interface BrowserElementAudioChannel : EventTarget {
-  readonly attribute AudioChannel name;
-
-  // This event is dispatched when this audiochannel is actually in used by the
-  // app or one of the sub-iframes.
-  attribute EventHandler onactivestatechanged;
+#ifndef mozilla_dom_audiochannelcommon_h__
+#define mozilla_dom_audiochannelcommon_h__
 
-  [Throws]
-  DOMRequest getVolume();
-
-  [Throws]
-  DOMRequest setVolume(float aVolume);
+namespace mozilla {
+namespace dom {
 
-  [Throws]
-  DOMRequest getMuted();
-
-  [Throws]
-  DOMRequest setMuted(boolean aMuted);
-
-  [Throws]
-  DOMRequest isActive();
+enum AudioChannelState {
+  AUDIO_CHANNEL_STATE_NORMAL = 0,
+  AUDIO_CHANNEL_STATE_MUTED,
+  AUDIO_CHANNEL_STATE_FADED,
+  AUDIO_CHANNEL_STATE_LAST
 };
 
-partial interface BrowserElementPrivileged {
-  [Constant, Cached, Throws,
-   Pref="dom.mozBrowserFramesEnabled",
-   CheckPermissions="browser"]
-  readonly attribute sequence<BrowserElementAudioChannel> allowedAudioChannels;
-};
+} // namespace dom
+} // namespace mozilla
+
+#endif
+
--- a/dom/audiochannel/AudioChannelService.cpp
+++ b/dom/audiochannel/AudioChannelService.cpp
@@ -1,27 +1,26 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
 #include "AudioChannelService.h"
+#include "AudioChannelServiceChild.h"
 
 #include "base/basictypes.h"
 
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/unused.h"
 
-#include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/ContentParent.h"
 
 #include "nsContentUtils.h"
-#include "nsIScriptSecurityManager.h"
 #include "nsISupportsPrimitives.h"
 #include "nsThreadUtils.h"
 #include "nsHashPropertyBag.h"
 #include "nsComponentManagerUtils.h"
 #include "nsPIDOMWindow.h"
 #include "nsServiceManagerUtils.h"
 #include "mozilla/dom/SettingChangeNotificationBinding.h"
 
@@ -33,125 +32,100 @@
 #endif
 
 #include "mozilla/Preferences.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::hal;
 
-namespace {
-
-void
-NotifyChannelActive(uint64_t aWindowID, AudioChannel aAudioChannel,
-                    bool aActive)
+// When a inner-window is destroyed we have to mute all the related
+// AudioChannelAgents. In order to do this we have to notify them after purging
+// AudioChannelService::mAgents.
+struct MOZ_STACK_CLASS WindowDestroyedEnumeratorData
 {
-  nsCOMPtr<nsIObserverService> observerService =
-    services::GetObserverService();
-  if (NS_WARN_IF(!observerService)) {
-    return;
-  }
-
-  nsCOMPtr<nsISupportsPRUint64> wrapper =
-    do_CreateInstance(NS_SUPPORTS_PRUINT64_CONTRACTID);
-  if (!wrapper) {
-     return;
-  }
-
-  wrapper->SetData(aWindowID);
-
-  nsAutoString name;
-  AudioChannelService::GetAudioChannelString(aAudioChannel, name);
+  explicit WindowDestroyedEnumeratorData(uint64_t aInnerID)
+    : mInnerID(aInnerID)
+  {}
 
-  nsAutoCString topic;
-  topic.Assign("audiochannel-activity-");
-  topic.Append(NS_ConvertUTF16toUTF8(name));
-
-  observerService->NotifyObservers(wrapper, topic.get(),
-                                   aActive
-                                     ? MOZ_UTF16("active") : MOZ_UTF16("inactive"));
-}
-
-already_AddRefed<nsPIDOMWindow>
-GetTopWindow(nsIDOMWindow* aWindow)
-{
-  MOZ_ASSERT(aWindow);
-
-  nsCOMPtr<nsIDOMWindow> topWindow;
-  aWindow->GetScriptableTop(getter_AddRefs(topWindow));
-  MOZ_ASSERT(topWindow);
-
-  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(topWindow);
-  window = window->GetOuterWindow();
-
-  return window.forget();
-}
-
-} // anonymous namespace
+  nsTArray<nsRefPtr<AudioChannelAgent>> mAgents;
+  uint64_t mInnerID;
+};
 
 StaticRefPtr<AudioChannelService> gAudioChannelService;
 
 // Mappings from 'mozaudiochannel' attribute strings to an enumeration.
 static const nsAttrValue::EnumTable kMozAudioChannelAttributeTable[] = {
   { "normal",             (int16_t)AudioChannel::Normal },
   { "content",            (int16_t)AudioChannel::Content },
   { "notification",       (int16_t)AudioChannel::Notification },
   { "alarm",              (int16_t)AudioChannel::Alarm },
   { "telephony",          (int16_t)AudioChannel::Telephony },
   { "ringer",             (int16_t)AudioChannel::Ringer },
   { "publicnotification", (int16_t)AudioChannel::Publicnotification },
   { nullptr }
 };
 
-/* static */ already_AddRefed<AudioChannelService>
-AudioChannelService::GetOrCreate()
+// static
+AudioChannelService*
+AudioChannelService::GetAudioChannelService()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (!gAudioChannelService) {
-    gAudioChannelService = new AudioChannelService();
+  if (!XRE_IsParentProcess()) {
+    return AudioChannelServiceChild::GetAudioChannelService();
   }
 
-  nsRefPtr<AudioChannelService> service = gAudioChannelService.get();
-  return service.forget();
+  return gAudioChannelService;
+
+}
+
+// static
+AudioChannelService*
+AudioChannelService::GetOrCreateAudioChannelService()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!XRE_IsParentProcess()) {
+    return AudioChannelServiceChild::GetOrCreateAudioChannelService();
+  }
+
+  // If we already exist, exit early
+  if (gAudioChannelService) {
+    return gAudioChannelService;
+  }
+
+  // Create new instance, register, return
+  nsRefPtr<AudioChannelService> service = new AudioChannelService();
+  MOZ_ASSERT(service);
+
+  gAudioChannelService = service;
+  return gAudioChannelService;
 }
 
 void
 AudioChannelService::Shutdown()
 {
+  if (!XRE_IsParentProcess()) {
+    return AudioChannelServiceChild::Shutdown();
+  }
+
   if (gAudioChannelService) {
-    if (XRE_GetProcessType() == GeckoProcessType_Default) {
-      nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
-      if (obs) {
-        obs->RemoveObserver(gAudioChannelService, "ipc:content-shutdown");
-        obs->RemoveObserver(gAudioChannelService, "xpcom-shutdown");
-        obs->RemoveObserver(gAudioChannelService, "inner-window-destroyed");
-#ifdef MOZ_WIDGET_GONK
-        // To monitor the volume settings based on audio channel.
-        obs->RemoveObserver(gAudioChannelService, "mozsettings-changed");
-#endif
-      }
-    }
-
     gAudioChannelService = nullptr;
   }
 }
 
-NS_INTERFACE_MAP_BEGIN(AudioChannelService)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAudioChannelService)
-  NS_INTERFACE_MAP_ENTRY(nsIAudioChannelService)
-  NS_INTERFACE_MAP_ENTRY(nsIObserver)
-NS_INTERFACE_MAP_END
-
-NS_IMPL_ADDREF(AudioChannelService)
-NS_IMPL_RELEASE(AudioChannelService)
+NS_IMPL_ISUPPORTS(AudioChannelService, nsIObserver, nsITimerCallback)
 
 AudioChannelService::AudioChannelService()
-  : mDisabled(false)
-  , mDefChannelChildID(CONTENT_PROCESS_ID_UNKNOWN)
+: mCurrentHigherChannel(-1)
+, mCurrentVisibleHigherChannel(-1)
+, mPlayableHiddenContentChildID(CONTENT_PROCESS_ID_UNKNOWN)
+, mDisabled(false)
+, mDefChannelChildID(CONTENT_PROCESS_ID_UNKNOWN)
 {
   if (XRE_IsParentProcess()) {
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     if (obs) {
       obs->AddObserver(this, "ipc:content-shutdown", false);
       obs->AddObserver(this, "xpcom-shutdown", false);
       obs->AddObserver(this, "inner-window-destroyed", false);
 #ifdef MOZ_WIDGET_GONK
@@ -163,207 +137,744 @@ AudioChannelService::AudioChannelService
 }
 
 AudioChannelService::~AudioChannelService()
 {
 }
 
 void
 AudioChannelService::RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
-                                               AudioChannel aChannel)
+                                               AudioChannel aChannel,
+                                               bool aWithVideo)
 {
   if (mDisabled) {
     return;
   }
 
-  uint64_t windowID = aAgent->WindowID();
-  AudioChannelWindow* winData = mWindows.LookupOrAdd(windowID);
-
-  MOZ_ASSERT(!winData->mAgents.Get(aAgent));
-
-  AudioChannel* audioChannel = new AudioChannel(aChannel);
-  winData->mAgents.Put(aAgent, audioChannel);
-
-  ++winData->mChannels[(uint32_t)aChannel].mNumberOfAgents;
-
-  // The first one, we must inform the BrowserElementAudioChannel.
-  if (winData->mChannels[(uint32_t)aChannel].mNumberOfAgents == 1) {
-    NotifyChannelActive(aAgent->WindowID(), aChannel, true);
-  }
+  AudioChannelAgentData* data = new AudioChannelAgentData(aChannel,
+                                true /* aElementHidden */,
+                                AUDIO_CHANNEL_STATE_MUTED /* aState */,
+                                aWithVideo);
+  mAgents.Put(aAgent, data);
+  RegisterType(aChannel, CONTENT_PROCESS_ID_MAIN, aWithVideo);
 
   // If this is the first agent for this window, we must notify the observers.
-  if (winData->mAgents.Count() == 1) {
+  uint32_t count = CountWindow(aAgent->Window());
+  if (count == 1) {
     nsCOMPtr<nsIObserverService> observerService =
       services::GetObserverService();
     if (observerService) {
       observerService->NotifyObservers(ToSupports(aAgent->Window()),
                                        "media-playback",
                                        NS_LITERAL_STRING("active").get());
     }
   }
 }
 
 void
+AudioChannelService::RegisterType(AudioChannel aChannel, uint64_t aChildID,
+                                  bool aWithVideo)
+{
+  if (mDisabled) {
+    return;
+  }
+
+  AudioChannelInternalType type = GetInternalType(aChannel, true);
+  mChannelCounters[type].AppendElement(aChildID);
+
+  if (XRE_IsParentProcess()) {
+
+    // We must keep the childIds in order to decide which app is allowed to play
+    // with then telephony channel.
+    if (aChannel == AudioChannel::Telephony) {
+      RegisterTelephonyChild(aChildID);
+    }
+
+    // Since there is another telephony registered, we can unregister old one
+    // immediately.
+    if (mDeferTelChannelTimer && aChannel == AudioChannel::Telephony) {
+      mDeferTelChannelTimer->Cancel();
+      mDeferTelChannelTimer = nullptr;
+      UnregisterTypeInternal(aChannel, mTimerElementHidden, mTimerChildID,
+                             false);
+    }
+
+    if (aWithVideo) {
+      mWithVideoChildIDs.AppendElement(aChildID);
+    }
+
+    // No hidden content channel can be playable if there is a content channel
+    // in foreground (bug 855208), nor if there is a normal channel with video
+    // in foreground (bug 894249).
+    if (type == AUDIO_CHANNEL_INT_CONTENT ||
+        (type == AUDIO_CHANNEL_INT_NORMAL &&
+         mWithVideoChildIDs.Contains(aChildID))) {
+      mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN;
+    }
+    // One hidden content channel can be playable only when there is no any
+    // content channel in the foreground, and no normal channel with video in
+    // foreground.
+    else if (type == AUDIO_CHANNEL_INT_CONTENT_HIDDEN &&
+        mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty()) {
+      mPlayableHiddenContentChildID = aChildID;
+    }
+
+    // In order to avoid race conditions, it's safer to notify any existing
+    // agent any time a new one is registered.
+    SendAudioChannelChangedNotification(aChildID);
+    SendNotification();
+  }
+}
+
+void
 AudioChannelService::UnregisterAudioChannelAgent(AudioChannelAgent* aAgent)
 {
   if (mDisabled) {
     return;
   }
 
-  uint64_t windowID = aAgent->WindowID();
-  AudioChannelWindow* winData = nullptr;
-  if (!mWindows.Get(windowID, &winData)) {
-    return;
-  }
+  nsAutoPtr<AudioChannelAgentData> data;
+  mAgents.RemoveAndForget(aAgent, data);
 
-  nsAutoPtr<AudioChannel> audioChannel;
-  winData->mAgents.RemoveAndForget(aAgent, audioChannel);
-  if (audioChannel) {
-    MOZ_ASSERT(winData->mChannels[(uint32_t)*audioChannel].mNumberOfAgents > 0);
-
-    --winData->mChannels[(uint32_t)*audioChannel].mNumberOfAgents;
-
-    // The last one, we must inform the BrowserElementAudioChannel.
-    if (winData->mChannels[(uint32_t)*audioChannel].mNumberOfAgents == 0) {
-      NotifyChannelActive(aAgent->WindowID(), *audioChannel, false);
-    }
+  if (data) {
+    UnregisterType(data->mChannel, data->mElementHidden,
+                   CONTENT_PROCESS_ID_MAIN, data->mWithVideo);
   }
 
 #ifdef MOZ_WIDGET_GONK
   bool active = AnyAudioChannelIsActive();
   for (uint32_t i = 0; i < mSpeakerManager.Length(); i++) {
     mSpeakerManager[i]->SetAudioChannelActive(active);
   }
 #endif
 
   // If this is the last agent for this window, we must notify the observers.
-  if (winData->mAgents.Count() == 0) {
+  uint32_t count = CountWindow(aAgent->Window());
+  if (count == 0) {
     nsCOMPtr<nsIObserverService> observerService =
       services::GetObserverService();
     if (observerService) {
       observerService->NotifyObservers(ToSupports(aAgent->Window()),
                                        "media-playback",
                                        NS_LITERAL_STRING("inactive").get());
     }
   }
 }
 
 void
-AudioChannelService::GetState(nsPIDOMWindow* aWindow, uint32_t aAudioChannel,
-                              float* aVolume, bool* aMuted)
+AudioChannelService::UnregisterType(AudioChannel aChannel,
+                                    bool aElementHidden,
+                                    uint64_t aChildID,
+                                    bool aWithVideo)
 {
-  MOZ_ASSERT(!aWindow || aWindow->IsOuterWindow());
-  MOZ_ASSERT(aVolume && aMuted);
-  MOZ_ASSERT(aAudioChannel < NUMBER_OF_AUDIO_CHANNELS);
-
-  *aVolume = 1.0;
-  *aMuted = false;
-
-  if (!aWindow || !aWindow->IsOuterWindow()) {
+  if (mDisabled) {
     return;
   }
 
-  AudioChannelWindow* winData = nullptr;
-  nsCOMPtr<nsPIDOMWindow> window = aWindow;
+  // There are two reasons to defer the decrease of telephony channel.
+  // 1. User can have time to remove device from his ear before music resuming.
+  // 2. Give BT SCO to be disconnected before starting to connect A2DP.
+  if (XRE_IsParentProcess()) {
+
+    if (aChannel == AudioChannel::Telephony) {
+      UnregisterTelephonyChild(aChildID);
+    }
+
+    if (aChannel == AudioChannel::Telephony &&
+        (mChannelCounters[AUDIO_CHANNEL_INT_TELEPHONY_HIDDEN].Length() +
+         mChannelCounters[AUDIO_CHANNEL_INT_TELEPHONY].Length()) == 1) {
+      mTimerElementHidden = aElementHidden;
+      mTimerChildID = aChildID;
+      mDeferTelChannelTimer = do_CreateInstance("@mozilla.org/timer;1");
+      mDeferTelChannelTimer->InitWithCallback(this, 1500, nsITimer::TYPE_ONE_SHOT);
+      return;
+    }
+  }
+
+  UnregisterTypeInternal(aChannel, aElementHidden, aChildID, aWithVideo);
+}
 
-  // The volume must be calculated based on the window hierarchy. Here we go up
-  // to the top window and we calculate the volume and the muted flag.
-  do {
-    if (mWindows.Get(window->WindowID(), &winData)) {
-      *aVolume *= winData->mChannels[aAudioChannel].mVolume;
-      *aMuted = *aMuted || winData->mChannels[aAudioChannel].mMuted;
+void
+AudioChannelService::UnregisterTypeInternal(AudioChannel aChannel,
+                                            bool aElementHidden,
+                                            uint64_t aChildID,
+                                            bool aWithVideo)
+{
+  // The array may contain multiple occurrence of this appId but
+  // this should remove only the first one.
+  AudioChannelInternalType type = GetInternalType(aChannel, aElementHidden);
+  MOZ_ASSERT(mChannelCounters[type].Contains(aChildID));
+  mChannelCounters[type].RemoveElement(aChildID);
+
+  // In order to avoid race conditions, it's safer to notify any existing
+  // agent any time a new one is registered.
+  if (XRE_IsParentProcess()) {
+    // No hidden content channel is playable if the original playable hidden
+    // process does not need to play audio from background anymore.
+    if (aChannel == AudioChannel::Content &&
+        mPlayableHiddenContentChildID == aChildID &&
+        !mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN].Contains(aChildID)) {
+      mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN;
+    }
+
+    if (aWithVideo) {
+      MOZ_ASSERT(mWithVideoChildIDs.Contains(aChildID));
+      mWithVideoChildIDs.RemoveElement(aChildID);
     }
 
-    *aVolume *= window->GetAudioVolume();
-    *aMuted = *aMuted || window->GetAudioMuted();
+    SendAudioChannelChangedNotification(aChildID);
+    SendNotification();
+  }
+}
+
+void
+AudioChannelService::UpdateChannelType(AudioChannel aChannel,
+                                       uint64_t aChildID,
+                                       bool aElementHidden,
+                                       bool aElementWasHidden)
+{
+  // Calculate the new and old internal type and update the hashtable if needed.
+  AudioChannelInternalType newType = GetInternalType(aChannel, aElementHidden);
+  AudioChannelInternalType oldType = GetInternalType(aChannel, aElementWasHidden);
+
+  if (newType != oldType) {
+    mChannelCounters[newType].AppendElement(aChildID);
+    MOZ_ASSERT(mChannelCounters[oldType].Contains(aChildID));
+    mChannelCounters[oldType].RemoveElement(aChildID);
+  }
+
+  // No hidden content channel can be playable if there is a content channel
+  // in foreground (bug 855208), nor if there is a normal channel with video
+  // in foreground (bug 894249).
+  if (newType == AUDIO_CHANNEL_INT_CONTENT ||
+      (newType == AUDIO_CHANNEL_INT_NORMAL &&
+       mWithVideoChildIDs.Contains(aChildID))) {
+    mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN;
+  }
+  // If there is no content channel in foreground and no normal channel with
+  // video in foreground, the last content channel which goes from foreground
+  // to background can be playable.
+  else if (oldType == AUDIO_CHANNEL_INT_CONTENT &&
+      newType == AUDIO_CHANNEL_INT_CONTENT_HIDDEN &&
+      mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty()) {
+    mPlayableHiddenContentChildID = aChildID;
+  }
+}
+
+AudioChannelState
+AudioChannelService::GetState(AudioChannelAgent* aAgent, bool aElementHidden)
+{
+  AudioChannelAgentData* data;
+  if (!mAgents.Get(aAgent, &data)) {
+    return AUDIO_CHANNEL_STATE_MUTED;
+  }
+
+  bool oldElementHidden = data->mElementHidden;
+  // Update visibility.
+  data->mElementHidden = aElementHidden;
+
+  data->mState = GetStateInternal(data->mChannel, CONTENT_PROCESS_ID_MAIN,
+                                aElementHidden, oldElementHidden);
+  #ifdef MOZ_WIDGET_GONK
+  /** Only modify the speaker status when
+   *  (1) apps in the foreground.
+   *  (2) apps in the backgrund and inactive.
+   *  Notice : check the state when the visible status is stable, because there
+   *  has lantency in passing the visibility events.
+   **/
+  bool active = AnyAudioChannelIsActive();
+  if (aElementHidden == oldElementHidden &&
+      (!aElementHidden || (aElementHidden && !active))) {
+    for (uint32_t i = 0; i < mSpeakerManager.Length(); i++) {
+      mSpeakerManager[i]->SetAudioChannelActive(active);
+    }
+  }
+  #endif
+
+  return data->mState;
+}
 
-    nsCOMPtr<nsIDOMWindow> win;
-    window->GetScriptableParent(getter_AddRefs(win));
-    if (window == win) {
+AudioChannelState
+AudioChannelService::GetStateInternal(AudioChannel aChannel, uint64_t aChildID,
+                                      bool aElementHidden,
+                                      bool aElementWasHidden)
+{
+  UpdateChannelType(aChannel, aChildID, aElementHidden, aElementWasHidden);
+
+  // Calculating the new and old type and update the hashtable if needed.
+  AudioChannelInternalType newType = GetInternalType(aChannel, aElementHidden);
+  AudioChannelInternalType oldType = GetInternalType(aChannel,
+                                                     aElementWasHidden);
+
+  if (newType != oldType &&
+      (aChannel == AudioChannel::Content ||
+       (aChannel == AudioChannel::Normal &&
+        mWithVideoChildIDs.Contains(aChildID)))) {
+    SendNotification();
+  }
+
+  SendAudioChannelChangedNotification(aChildID);
+
+  // Let play any visible audio channel.
+  if (!aElementHidden) {
+    if (CheckVolumeFadedCondition(newType, aElementHidden)) {
+      return AUDIO_CHANNEL_STATE_FADED;
+    }
+    return CheckTelephonyPolicy(aChannel, aChildID);
+  }
+
+  // We are not visible, maybe we have to mute.
+  if (newType == AUDIO_CHANNEL_INT_NORMAL_HIDDEN ||
+      (newType == AUDIO_CHANNEL_INT_CONTENT_HIDDEN &&
+       // One process can have multiple content channels; and during the
+       // transition from foreground to background, its content channels will be
+       // updated with correct visibility status one by one. All its content
+       // channels should remain playable until all of their visibility statuses
+       // have been updated as hidden. After all its content channels have been
+       // updated properly as hidden, mPlayableHiddenContentChildID is used to
+       // check whether this background process is playable or not.
+       !(mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].Contains(aChildID) ||
+         (mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty() &&
+          mPlayableHiddenContentChildID == aChildID)))) {
+    return AUDIO_CHANNEL_STATE_MUTED;
+  }
+
+  // After checking the condition on normal & content channel, if the state
+  // is not on muted then checking other higher channels type here.
+  if (ChannelsActiveWithHigherPriorityThan(newType)) {
+    MOZ_ASSERT(newType != AUDIO_CHANNEL_INT_NORMAL_HIDDEN);
+    if (CheckVolumeFadedCondition(newType, aElementHidden)) {
+      return AUDIO_CHANNEL_STATE_FADED;
+    }
+    return AUDIO_CHANNEL_STATE_MUTED;
+  }
+
+  return CheckTelephonyPolicy(aChannel, aChildID);
+}
+
+AudioChannelState
+AudioChannelService::CheckTelephonyPolicy(AudioChannel aChannel,
+                                          uint64_t aChildID)
+{
+  // Only the latest childID is allowed to play with telephony channel.
+  if (aChannel != AudioChannel::Telephony) {
+    return AUDIO_CHANNEL_STATE_NORMAL;
+  }
+
+  MOZ_ASSERT(!mTelephonyChildren.IsEmpty());
+
+#if DEBUG
+  bool found = false;
+  for (uint32_t i = 0, len = mTelephonyChildren.Length(); i < len; ++i) {
+    if (mTelephonyChildren[i].mChildID == aChildID) {
+      found = true;
       break;
     }
-
-    window = do_QueryInterface(win);
+  }
 
-    // If there is no parent, or we are the toplevel we don't continue.
-  } while (window && window != aWindow);
+  MOZ_ASSERT(found);
+#endif
+
+  return mTelephonyChildren.LastElement().mChildID == aChildID
+           ? AUDIO_CHANNEL_STATE_NORMAL : AUDIO_CHANNEL_STATE_MUTED;
 }
 
-PLDHashOperator
-AudioChannelService::TelephonyChannelIsActiveEnumerator(
-                                        const uint64_t& aWindowID,
-                                        nsAutoPtr<AudioChannelWindow>& aWinData,
-                                        void* aPtr)
+bool
+AudioChannelService::CheckVolumeFadedCondition(AudioChannelInternalType aType,
+                                               bool aElementHidden)
 {
-  bool* isActive = static_cast<bool*>(aPtr);
-  *isActive =
-    aWinData->mChannels[(uint32_t)AudioChannel::Telephony].mNumberOfAgents != 0 &&
-   !aWinData->mChannels[(uint32_t)AudioChannel::Telephony].mMuted;
-  return *isActive ? PL_DHASH_STOP : PL_DHASH_NEXT;
+  // Only normal & content channels are considered
+  if (aType > AUDIO_CHANNEL_INT_CONTENT_HIDDEN) {
+    return false;
+  }
+
+  // Consider that audio from notification is with short duration
+  // so just fade the volume not pause it
+  if (mChannelCounters[AUDIO_CHANNEL_INT_NOTIFICATION].IsEmpty() &&
+      mChannelCounters[AUDIO_CHANNEL_INT_NOTIFICATION_HIDDEN].IsEmpty()) {
+    return false;
+  }
+
+  // Since this element is on the foreground, it can be allowed to play always.
+  // So return true directly when there is any notification channel alive.
+  if (aElementHidden == false) {
+   return true;
+  }
+
+  // If element is on the background, it is possible paused by channels higher
+  // then notification.
+  for (int i = AUDIO_CHANNEL_INT_LAST - 1;
+    i != AUDIO_CHANNEL_INT_NOTIFICATION_HIDDEN; --i) {
+    if (!mChannelCounters[i].IsEmpty()) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool
+AudioChannelService::ContentOrNormalChannelIsActive()
+{
+  return !mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty() ||
+         !mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN].IsEmpty() ||
+         !mChannelCounters[AUDIO_CHANNEL_INT_NORMAL].IsEmpty();
 }
 
 bool
 AudioChannelService::TelephonyChannelIsActive()
 {
-  // TODO: no child process check.
-
-  bool active = false;
-  mWindows.Enumerate(TelephonyChannelIsActiveEnumerator, &active);
-  return active;
+  return !mChannelCounters[AUDIO_CHANNEL_INT_TELEPHONY].IsEmpty() ||
+         !mChannelCounters[AUDIO_CHANNEL_INT_TELEPHONY_HIDDEN].IsEmpty();
 }
 
 bool
 AudioChannelService::ProcessContentOrNormalChannelIsActive(uint64_t aChildID)
 {
-/* TODO
-  AudioChannelChildData* data;
-  if (!mData.Get(aChildID, &data)) {
-    return false;
+  return mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].Contains(aChildID) ||
+         mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN].Contains(aChildID) ||
+         mChannelCounters[AUDIO_CHANNEL_INT_NORMAL].Contains(aChildID);
+}
+
+void
+AudioChannelService::SetDefaultVolumeControlChannel(int32_t aChannel,
+                                                    bool aVisible)
+{
+  SetDefaultVolumeControlChannelInternal(aChannel, aVisible,
+                                         CONTENT_PROCESS_ID_MAIN);
+}
+
+void
+AudioChannelService::SetDefaultVolumeControlChannelInternal(int32_t aChannel,
+                                                            bool aVisible,
+                                                            uint64_t aChildID)
+{
+  if (!XRE_IsParentProcess()) {
+    return;
   }
 
-  return data->mChannels[(uint32_t)AudioChannel::Content].mNumberOfAgents != 0 ||
-         data->mChannels[(uint32_t)AudioChannel::Normal].mNumberOfAgents != 0;
-*/
-  return true;
+  // If this child is in the background and mDefChannelChildID is set to
+  // others then it means other child in the foreground already set it's
+  // own default channel already.
+  if (!aVisible && mDefChannelChildID != aChildID) {
+    return;
+  }
+  // Workaround for the call screen app. The call screen app is running on the
+  // main process, that will results in wrong visible state. Because we use the
+  // docshell's active state as visible state, the main process is always
+  // active. Therefore, we will see the strange situation that the visible
+  // state of the call screen is always true. If the mDefChannelChildID is set
+  // to others then it means other child in the foreground already set it's
+  // own default channel already.
+  // Summary :
+  //   Child process : foreground app always can set type.
+  //   Parent process : check the mDefChannelChildID.
+  else if (aChildID == CONTENT_PROCESS_ID_MAIN &&
+           mDefChannelChildID != CONTENT_PROCESS_ID_UNKNOWN) {
+    return;
+  }
+
+  mDefChannelChildID = aVisible ? aChildID : CONTENT_PROCESS_ID_UNKNOWN;
+  nsAutoString channelName;
+  if (aChannel == -1) {
+    channelName.AssignASCII("unknown");
+  } else {
+    GetAudioChannelString(static_cast<AudioChannel>(aChannel), channelName);
+  }
+
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (obs) {
+    obs->NotifyObservers(nullptr, "default-volume-channel-changed",
+                         channelName.get());
+  }
 }
 
-PLDHashOperator
-AudioChannelService::AnyAudioChannelIsActiveEnumerator(
-                                        const uint64_t& aWindowID,
-                                        nsAutoPtr<AudioChannelWindow>& aWinData,
-                                        void* aPtr)
+void
+AudioChannelService::SendAudioChannelChangedNotification(uint64_t aChildID)
 {
-  bool* isActive = static_cast<bool*>(aPtr);
-  for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) {
-    if (aWinData->mChannels[kMozAudioChannelAttributeTable[i].value].mNumberOfAgents
-        != 0) {
-      *isActive = true;
+  if (!XRE_IsParentProcess()) {
+    return;
+  }
+
+  nsRefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
+  props->SetPropertyAsUint64(NS_LITERAL_STRING("childID"), aChildID);
+
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (obs) {
+    obs->NotifyObservers(static_cast<nsIWritablePropertyBag*>(props),
+                         "audio-channel-process-changed", nullptr);
+  }
+
+  // Calculating the most important active channel.
+  int32_t higher = -1;
+
+  // Top-Down in the hierarchy for visible elements
+  if (!mChannelCounters[AUDIO_CHANNEL_INT_PUBLICNOTIFICATION].IsEmpty()) {
+    higher = static_cast<int32_t>(AudioChannel::Publicnotification);
+  }
+
+  else if (!mChannelCounters[AUDIO_CHANNEL_INT_RINGER].IsEmpty()) {
+    higher = static_cast<int32_t>(AudioChannel::Ringer);
+  }
+
+  else if (!mChannelCounters[AUDIO_CHANNEL_INT_TELEPHONY].IsEmpty()) {
+    higher = static_cast<int32_t>(AudioChannel::Telephony);
+  }
+
+  else if (!mChannelCounters[AUDIO_CHANNEL_INT_ALARM].IsEmpty()) {
+    higher = static_cast<int32_t>(AudioChannel::Alarm);
+  }
+
+  else if (!mChannelCounters[AUDIO_CHANNEL_INT_NOTIFICATION].IsEmpty()) {
+    higher = static_cast<int32_t>(AudioChannel::Notification);
+  }
+
+  else if (!mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty()) {
+    higher = static_cast<int32_t>(AudioChannel::Content);
+  }
+
+  else if (!mChannelCounters[AUDIO_CHANNEL_INT_NORMAL].IsEmpty()) {
+    higher = static_cast<int32_t>(AudioChannel::Normal);
+  }
+
+  int32_t visibleHigher = higher;
+
+  // Top-Down in the hierarchy for non-visible elements
+  // And we can ignore normal channel because it can't play in the background.
+  int32_t index;
+  for (index = 0; kMozAudioChannelAttributeTable[index].tag; ++index);
+
+  for (--index;
+       kMozAudioChannelAttributeTable[index].value > higher &&
+       kMozAudioChannelAttributeTable[index].value > (int16_t)AudioChannel::Normal;
+       --index) {
+    // Each channel type will be split to fg and bg for recording the state,
+    // so here need to do a translation.
+    if (mChannelCounters[index * 2 + 1].IsEmpty()) {
+      continue;
+    }
+
+    if (kMozAudioChannelAttributeTable[index].value == (int16_t)AudioChannel::Content) {
+      if (mPlayableHiddenContentChildID != CONTENT_PROCESS_ID_UNKNOWN) {
+        higher = kMozAudioChannelAttributeTable[index].value;
+        break;
+      }
+    } else {
+      higher = kMozAudioChannelAttributeTable[index].value;
       break;
     }
   }
 
-  return *isActive ? PL_DHASH_STOP : PL_DHASH_NEXT;
+  if (higher != mCurrentHigherChannel) {
+    mCurrentHigherChannel = higher;
+
+    nsString channelName;
+    if (mCurrentHigherChannel != -1) {
+      GetAudioChannelString(static_cast<AudioChannel>(mCurrentHigherChannel),
+                            channelName);
+    } else {
+      channelName.AssignLiteral("none");
+    }
+
+    if (obs) {
+      obs->NotifyObservers(nullptr, "audio-channel-changed", channelName.get());
+    }
+  }
+
+  if (visibleHigher != mCurrentVisibleHigherChannel) {
+    mCurrentVisibleHigherChannel = visibleHigher;
+
+    nsString channelName;
+    if (mCurrentVisibleHigherChannel != -1) {
+      GetAudioChannelString(static_cast<AudioChannel>(mCurrentVisibleHigherChannel),
+                            channelName);
+    } else {
+      channelName.AssignLiteral("none");
+    }
+
+    if (obs) {
+      obs->NotifyObservers(nullptr, "visible-audio-channel-changed", channelName.get());
+    }
+  }
+}
+
+PLDHashOperator
+AudioChannelService::NotifyEnumerator(AudioChannelAgent* aAgent,
+                                      AudioChannelAgentData* aData, void* aUnused)
+{
+  MOZ_ASSERT(aAgent);
+  aAgent->NotifyAudioChannelStateChanged();
+  return PL_DHASH_NEXT;
+}
+
+class NotifyRunnable : public nsRunnable
+{
+public:
+  explicit NotifyRunnable(AudioChannelService* aService)
+    : mService(aService)
+  {}
+
+  NS_IMETHOD Run()
+  {
+    mService->Notify();
+    return NS_OK;
+  }
+
+private:
+  nsRefPtr<AudioChannelService> mService;
+};
+
+void
+AudioChannelService::SendNotification()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (mRunnable) {
+    return;
+  }
+
+  mRunnable = new NotifyRunnable(this);
+  NS_DispatchToCurrentThread(mRunnable);
+}
+
+void
+AudioChannelService::Notify()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mRunnable = nullptr;
+
+  // Notify any agent for the main process.
+  mAgents.EnumerateRead(NotifyEnumerator, nullptr);
+
+  // Notify for the child processes.
+  nsTArray<ContentParent*> children;
+  ContentParent::GetAll(children);
+  for (uint32_t i = 0; i < children.Length(); i++) {
+    unused << children[i]->SendAudioChannelNotify();
+  }
+}
+
+NS_IMETHODIMP
+AudioChannelService::Notify(nsITimer* aTimer)
+{
+  UnregisterTypeInternal(AudioChannel::Telephony, mTimerElementHidden,
+                         mTimerChildID, false);
+  mDeferTelChannelTimer = nullptr;
+  return NS_OK;
 }
 
 bool
 AudioChannelService::AnyAudioChannelIsActive()
 {
-  // TODO: no child process check.
-  bool active = false;
-  mWindows.Enumerate(AnyAudioChannelIsActiveEnumerator, &active);
-  return active;
+  for (int i = AUDIO_CHANNEL_INT_LAST - 1;
+       i >= AUDIO_CHANNEL_INT_NORMAL; --i) {
+    if (!mChannelCounters[i].IsEmpty()) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+bool
+AudioChannelService::ChannelsActiveWithHigherPriorityThan(
+  AudioChannelInternalType aType)
+{
+  for (int i = AUDIO_CHANNEL_INT_LAST - 1;
+       i != AUDIO_CHANNEL_INT_CONTENT_HIDDEN; --i) {
+    if (i == aType) {
+      return false;
+    }
+
+    if (!mChannelCounters[i].IsEmpty()) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+PLDHashOperator
+AudioChannelService::WindowDestroyedEnumerator(AudioChannelAgent* aAgent,
+                                               nsAutoPtr<AudioChannelAgentData>& aData,
+                                               void* aPtr)
+{
+  auto* data = static_cast<WindowDestroyedEnumeratorData*>(aPtr);
+  MOZ_ASSERT(data);
+
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aAgent->Window());
+  if (window && !window->IsInnerWindow()) {
+    window = window->GetCurrentInnerWindow();
+  }
+
+  if (!window || window->WindowID() != data->mInnerID) {
+    return PL_DHASH_NEXT;
+  }
+
+  AudioChannelService* service = AudioChannelService::GetAudioChannelService();
+  MOZ_ASSERT(service);
+
+  service->UnregisterType(aData->mChannel, aData->mElementHidden,
+                          CONTENT_PROCESS_ID_MAIN, aData->mWithVideo);
+  data->mAgents.AppendElement(aAgent);
+
+  return PL_DHASH_REMOVE;
 }
 
 NS_IMETHODIMP
-AudioChannelService::Observe(nsISupports* aSubject, const char* aTopic,
-                             const char16_t* aData)
+AudioChannelService::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
 {
   if (!strcmp(aTopic, "xpcom-shutdown")) {
     mDisabled = true;
-    mWindows.Clear();
+  }
+
+  if (!strcmp(aTopic, "ipc:content-shutdown")) {
+    nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aSubject);
+    if (!props) {
+      NS_WARNING("ipc:content-shutdown message without property bag as subject");
+      return NS_OK;
+    }
+
+    int32_t index;
+    uint64_t childID = 0;
+    nsresult rv = props->GetPropertyAsUint64(NS_LITERAL_STRING("childID"),
+                                             &childID);
+    if (NS_SUCCEEDED(rv)) {
+      for (int32_t type = AUDIO_CHANNEL_INT_NORMAL;
+           type < AUDIO_CHANNEL_INT_LAST;
+           ++type) {
+
+        while ((index = mChannelCounters[type].IndexOf(childID)) != -1) {
+          mChannelCounters[type].RemoveElementAt(index);
+        }
+      }
+
+      // No hidden content channel is playable if the original playable hidden
+      // process shuts down.
+      if (mPlayableHiddenContentChildID == childID) {
+        mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN;
+      }
+
+      while ((index = mWithVideoChildIDs.IndexOf(childID)) != -1) {
+        mWithVideoChildIDs.RemoveElementAt(index);
+      }
+
+      // We don't have to remove the agents from the mAgents hashtable because if
+      // that table contains only agents running on the same process.
+
+      SendAudioChannelChangedNotification(childID);
+      SendNotification();
+
+      if (mDefChannelChildID == childID) {
+        SetDefaultVolumeControlChannelInternal(-1, false, childID);
+        mDefChannelChildID = CONTENT_PROCESS_ID_UNKNOWN;
+      }
+    } else {
+      NS_WARNING("ipc:content-shutdown message without childID property");
+    }
   }
 
 #ifdef MOZ_WIDGET_GONK
   // To process the volume control on each audio channel according to
   // change of settings
   else if (!strcmp(aTopic, "mozsettings-changed")) {
     RootedDictionary<SettingChangeNotification> setting(nsContentUtils::RootingCxForThread());
     if (!WrappedJSToDictionary(aSubject, setting)) {
@@ -404,82 +915,153 @@ AudioChannelService::Observe(nsISupports
     NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
 
     uint64_t innerID;
     nsresult rv = wrapper->GetData(&innerID);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
-    nsAutoPtr<AudioChannelWindow> window;
-    mWindows.RemoveAndForget(innerID, window);
-    if (window) {
-      window->mAgents.EnumerateRead(NotifyEnumerator, nullptr);
+    WindowDestroyedEnumeratorData data(innerID);
+    mAgents.Enumerate(WindowDestroyedEnumerator, &data);
+    for (uint32_t i = 0, len = data.mAgents.Length(); i < len; ++i) {
+      data.mAgents[i]->NotifyAudioChannelStateChanged();
     }
 
 #ifdef MOZ_WIDGET_GONK
     bool active = AnyAudioChannelIsActive();
     for (uint32_t i = 0; i < mSpeakerManager.Length(); i++) {
       mSpeakerManager[i]->SetAudioChannelActive(active);
     }
 #endif
   }
 
-  else if (!strcmp(aTopic, "ipc:content-shutdown")) {
-    nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aSubject);
-    if (!props) {
-      NS_WARNING("ipc:content-shutdown message without property bag as subject");
-      return NS_OK;
-    }
+  return NS_OK;
+}
+
+AudioChannelService::AudioChannelInternalType
+AudioChannelService::GetInternalType(AudioChannel aChannel,
+                                     bool aElementHidden)
+{
+  switch (aChannel) {
+    case AudioChannel::Normal:
+      return aElementHidden
+               ? AUDIO_CHANNEL_INT_NORMAL_HIDDEN
+               : AUDIO_CHANNEL_INT_NORMAL;
+
+    case AudioChannel::Content:
+      return aElementHidden
+               ? AUDIO_CHANNEL_INT_CONTENT_HIDDEN
+               : AUDIO_CHANNEL_INT_CONTENT;
+
+    case AudioChannel::Notification:
+      return aElementHidden
+               ? AUDIO_CHANNEL_INT_NOTIFICATION_HIDDEN
+               : AUDIO_CHANNEL_INT_NOTIFICATION;
 
-    uint64_t childID = 0;
-    nsresult rv = props->GetPropertyAsUint64(NS_LITERAL_STRING("childID"),
-                                             &childID);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
+    case AudioChannel::Alarm:
+      return aElementHidden
+               ? AUDIO_CHANNEL_INT_ALARM_HIDDEN
+               : AUDIO_CHANNEL_INT_ALARM;
+
+    case AudioChannel::Telephony:
+      return aElementHidden
+               ? AUDIO_CHANNEL_INT_TELEPHONY_HIDDEN
+               : AUDIO_CHANNEL_INT_TELEPHONY;
 
-    if (mDefChannelChildID == childID) {
-      SetDefaultVolumeControlChannelInternal(-1, false, childID);
-      mDefChannelChildID = CONTENT_PROCESS_ID_UNKNOWN;
-    }
+    case AudioChannel::Ringer:
+      return aElementHidden
+               ? AUDIO_CHANNEL_INT_RINGER_HIDDEN
+               : AUDIO_CHANNEL_INT_RINGER;
+
+    case AudioChannel::Publicnotification:
+      return aElementHidden
+               ? AUDIO_CHANNEL_INT_PUBLICNOTIFICATION_HIDDEN
+               : AUDIO_CHANNEL_INT_PUBLICNOTIFICATION;
+
+    default:
+      break;
   }
 
-  return NS_OK;
+  MOZ_CRASH("unexpected audio channel");
 }
 
 struct RefreshAgentsVolumeData
 {
   explicit RefreshAgentsVolumeData(nsPIDOMWindow* aWindow)
     : mWindow(aWindow)
   {}
 
   nsPIDOMWindow* mWindow;
   nsTArray<nsRefPtr<AudioChannelAgent>> mAgents;
 };
 
 PLDHashOperator
-AudioChannelService::RefreshAgentsVolumeEnumerator(
-                                                 AudioChannelAgent* aAgent,
-                                                 AudioChannel* aUnused,
-                                                 void* aPtr)
+AudioChannelService::RefreshAgentsVolumeEnumerator(AudioChannelAgent* aAgent,
+                                                   AudioChannelAgentData* aUnused,
+                                                   void* aPtr)
 {
   MOZ_ASSERT(aAgent);
-  aAgent->WindowVolumeChanged();
+  RefreshAgentsVolumeData* data = static_cast<RefreshAgentsVolumeData*>(aPtr);
+  MOZ_ASSERT(data);
+
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aAgent->Window());
+  if (window && !window->IsInnerWindow()) {
+    window = window->GetCurrentInnerWindow();
+  }
+
+  if (window == data->mWindow) {
+    data->mAgents.AppendElement(aAgent);
+  }
+
   return PL_DHASH_NEXT;
 }
 void
 AudioChannelService::RefreshAgentsVolume(nsPIDOMWindow* aWindow)
 {
-  AudioChannelWindow* winData = mWindows.Get(aWindow->WindowID());
-  if (!winData) {
-    return;
+  RefreshAgentsVolumeData data(aWindow);
+  mAgents.EnumerateRead(RefreshAgentsVolumeEnumerator, &data);
+
+  for (uint32_t i = 0; i < data.mAgents.Length(); ++i) {
+    data.mAgents[i]->WindowVolumeChanged();
+  }
+}
+
+struct CountWindowData
+{
+  explicit CountWindowData(nsIDOMWindow* aWindow)
+    : mWindow(aWindow)
+    , mCount(0)
+  {}
+
+  nsIDOMWindow* mWindow;
+  uint32_t mCount;
+};
+
+PLDHashOperator
+AudioChannelService::CountWindowEnumerator(AudioChannelAgent* aAgent,
+                                           AudioChannelAgentData* aUnused,
+                                           void* aPtr)
+{
+  CountWindowData* data = static_cast<CountWindowData*>(aPtr);
+  MOZ_ASSERT(aAgent);
+
+  if (aAgent->Window() == data->mWindow) {
+    ++data->mCount;
   }
 
-  winData->mAgents.EnumerateRead(RefreshAgentsVolumeEnumerator, nullptr);
+  return PL_DHASH_NEXT;
+}
+
+uint32_t
+AudioChannelService::CountWindow(nsIDOMWindow* aWindow)
+{
+  CountWindowData data(aWindow);
+  mAgents.EnumerateRead(CountWindowEnumerator, &data);
+  return data.mCount;
 }
 
 /* static */ const nsAttrValue::EnumTable*
 AudioChannelService::GetAudioChannelTable()
 {
   return kMozAudioChannelAttributeTable;
 }
 
@@ -493,17 +1075,17 @@ AudioChannelService::GetAudioChannel(con
   }
 
   return AudioChannel::Normal;
 }
 
 /* static */ AudioChannel
 AudioChannelService::GetDefaultAudioChannel()
 {
-  nsAutoString audioChannel(Preferences::GetString("media.defaultAudioChannel"));
+  nsString audioChannel = Preferences::GetString("media.defaultAudioChannel");
   if (audioChannel.IsEmpty()) {
     return AudioChannel::Normal;
   }
 
   for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) {
     if (audioChannel.EqualsASCII(kMozAudioChannelAttributeTable[i].tag)) {
       return static_cast<AudioChannel>(kMozAudioChannelAttributeTable[i].value);
     }
@@ -527,212 +1109,54 @@ AudioChannelService::GetAudioChannelStri
   }
 }
 
 /* static */ void
 AudioChannelService::GetDefaultAudioChannelString(nsAString& aString)
 {
   aString.AssignASCII("normal");
 
-  nsAutoString audioChannel(Preferences::GetString("media.defaultAudioChannel"));
+  nsString audioChannel = Preferences::GetString("media.defaultAudioChannel");
   if (!audioChannel.IsEmpty()) {
     for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) {
       if (audioChannel.EqualsASCII(kMozAudioChannelAttributeTable[i].tag)) {
         aString = audioChannel;
         break;
       }
     }
   }
 }
 
-AudioChannelService::AudioChannelWindow&
-AudioChannelService::GetOrCreateWindowData(nsPIDOMWindow* aWindow)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aWindow);
-  MOZ_ASSERT(aWindow->IsOuterWindow());
-
-  AudioChannelWindow* winData = mWindows.LookupOrAdd(aWindow->WindowID());
-  return *winData;
-}
-
-float
-AudioChannelService::GetAudioChannelVolume(nsPIDOMWindow* aWindow,
-                                           AudioChannel aAudioChannel)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aWindow);
-  MOZ_ASSERT(aWindow->IsOuterWindow());
-
-  AudioChannelWindow& winData = GetOrCreateWindowData(aWindow);
-  return winData.mChannels[(uint32_t)aAudioChannel].mVolume;
-}
-
-NS_IMETHODIMP
-AudioChannelService::GetAudioChannelVolume(nsIDOMWindow* aWindow,
-                                           unsigned short aAudioChannel,
-                                           float* aVolume)
-{
-  nsCOMPtr<nsPIDOMWindow> window = GetTopWindow(aWindow);
-  *aVolume = GetAudioChannelVolume(window, (AudioChannel)aAudioChannel);
-  return NS_OK;
-}
-
 void
-AudioChannelService::SetAudioChannelVolume(nsPIDOMWindow* aWindow,
-                                           AudioChannel aAudioChannel,
-                                           float aVolume)
+AudioChannelService::RegisterTelephonyChild(uint64_t aChildID)
 {
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aWindow);
-  MOZ_ASSERT(aWindow->IsOuterWindow());
-
-  AudioChannelWindow& winData = GetOrCreateWindowData(aWindow);
-  winData.mChannels[(uint32_t)aAudioChannel].mVolume = aVolume;
-  RefreshAgentsVolume(aWindow);
-}
-
-NS_IMETHODIMP
-AudioChannelService::SetAudioChannelVolume(nsIDOMWindow* aWindow,
-                                           unsigned short aAudioChannel,
-                                           float aVolume)
-{
-  nsCOMPtr<nsPIDOMWindow> window = GetTopWindow(aWindow);
-  SetAudioChannelVolume(window, (AudioChannel)aAudioChannel, aVolume);
-  return NS_OK;
-}
+  for (uint32_t i = 0, len = mTelephonyChildren.Length(); i < len; ++i) {
+    if (mTelephonyChildren[i].mChildID == aChildID) {
+      ++mTelephonyChildren[i].mInstances;
 
-bool
-AudioChannelService::GetAudioChannelMuted(nsPIDOMWindow* aWindow,
-                                          AudioChannel aAudioChannel)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aWindow);
-  MOZ_ASSERT(aWindow->IsOuterWindow());
+      if (i != len - 1) {
+        TelephonyChild child = mTelephonyChildren[i];
+        mTelephonyChildren.RemoveElementAt(i);
+        mTelephonyChildren.AppendElement(child);
+      }
 
-  AudioChannelWindow& winData = GetOrCreateWindowData(aWindow);
-  return winData.mChannels[(uint32_t)aAudioChannel].mMuted;
-}
+      return;
+    }
+  }
 
-NS_IMETHODIMP
-AudioChannelService::GetAudioChannelMuted(nsIDOMWindow* aWindow,
-                                          unsigned short aAudioChannel,
-                                          bool* aMuted)
-{
-  nsCOMPtr<nsPIDOMWindow> window = GetTopWindow(aWindow);
-  *aMuted = GetAudioChannelMuted(window, (AudioChannel)aAudioChannel);
-  return NS_OK;
+  mTelephonyChildren.AppendElement(TelephonyChild(aChildID));
 }
 
 void
-AudioChannelService::SetAudioChannelMuted(nsPIDOMWindow* aWindow,
-                                          AudioChannel aAudioChannel,
-                                          bool aMuted)
+AudioChannelService::UnregisterTelephonyChild(uint64_t aChildID)
 {
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aWindow);
-  MOZ_ASSERT(aWindow->IsOuterWindow());
-
-  AudioChannelWindow& winData = GetOrCreateWindowData(aWindow);
-  winData.mChannels[(uint32_t)aAudioChannel].mMuted = aMuted;
-  RefreshAgentsVolume(aWindow);
-}
-
-NS_IMETHODIMP
-AudioChannelService::SetAudioChannelMuted(nsIDOMWindow* aWindow,
-                                          unsigned short aAudioChannel,
-                                          bool aMuted)
-{
-  nsCOMPtr<nsPIDOMWindow> window = GetTopWindow(aWindow);
-  SetAudioChannelMuted(window, (AudioChannel)aAudioChannel, aMuted);
-  return NS_OK;
-}
-
-bool
-AudioChannelService::IsAudioChannelActive(nsPIDOMWindow* aWindow,
-                                          AudioChannel aAudioChannel)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aWindow);
-  MOZ_ASSERT(aWindow->IsOuterWindow());
+  for (uint32_t i = 0, len = mTelephonyChildren.Length(); i < len; ++i) {
+    if (mTelephonyChildren[i].mChildID == aChildID) {
+      if (!--mTelephonyChildren[i].mInstances) {
+        mTelephonyChildren.RemoveElementAt(i);
+      }
 
-  AudioChannelWindow& winData = GetOrCreateWindowData(aWindow);
-  return !!winData.mChannels[(uint32_t)aAudioChannel].mNumberOfAgents;
-}
-
-NS_IMETHODIMP
-AudioChannelService::IsAudioChannelActive(nsIDOMWindow* aWindow,
-                                          unsigned short aAudioChannel,
-                                          bool* aActive)
-{
-  nsCOMPtr<nsPIDOMWindow> window = GetTopWindow(aWindow);
-  *aActive = IsAudioChannelActive(window, (AudioChannel)aAudioChannel);
-  return NS_OK;
-}
-void
-AudioChannelService::SetDefaultVolumeControlChannel(int32_t aChannel,
-                                                    bool aVisible)
-{
-  SetDefaultVolumeControlChannelInternal(aChannel, aVisible,
-                                         CONTENT_PROCESS_ID_MAIN);
-}
-
-void
-AudioChannelService::SetDefaultVolumeControlChannelInternal(int32_t aChannel,
-                                                            bool aVisible,
-                                                            uint64_t aChildID)
-{
-  if (XRE_GetProcessType() != GeckoProcessType_Default) {
-    ContentChild* cc = ContentChild::GetSingleton();
-    if (cc) {
-      cc->SendAudioChannelChangeDefVolChannel(aChannel, aVisible);
+      return;
     }
-
-    return;
   }
 
-  // If this child is in the background and mDefChannelChildID is set to
-  // others then it means other child in the foreground already set it's
-  // own default channel.
-  if (!aVisible && mDefChannelChildID != aChildID) {
-    return;
-  }
-
-  // Workaround for the call screen app. The call screen app is running on the
-  // main process, that will results in wrong visible state. Because we use the
-  // docshell's active state as visible state, the main process is always
-  // active. Therefore, we will see the strange situation that the visible
-  // state of the call screen is always true. If the mDefChannelChildID is set
-  // to others then it means other child in the foreground already set it's
-  // own default channel already.
-  // Summary :
-  //   Child process : foreground app always can set type.
-  //   Parent process : check the mDefChannelChildID.
-  else if (aChildID == CONTENT_PROCESS_ID_MAIN &&
-           mDefChannelChildID != CONTENT_PROCESS_ID_UNKNOWN) {
-    return;
-  }
-
-  mDefChannelChildID = aVisible ? aChildID : CONTENT_PROCESS_ID_UNKNOWN;
-  nsAutoString channelName;
-
-  if (aChannel == -1) {
-    channelName.AssignASCII("unknown");
-  } else {
-    GetAudioChannelString(static_cast<AudioChannel>(aChannel), channelName);
-  }
-
-  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
-  if (obs) {
-    obs->NotifyObservers(nullptr, "default-volume-channel-changed",
-                         channelName.get());
-  }
+  MOZ_ASSERT(false, "This should not happen.");
 }
-
-/* static */ PLDHashOperator
-AudioChannelService::NotifyEnumerator(AudioChannelAgent* aAgent,
-                                      AudioChannel* aAudioChannel,
-                                      void* aUnused)
-{
-  aAgent->WindowVolumeChanged();
-  return PL_DHASH_NEXT;
-}
-
--- a/dom/audiochannel/AudioChannelService.h
+++ b/dom/audiochannel/AudioChannelService.h
@@ -2,100 +2,100 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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 mozilla_dom_audiochannelservice_h__
 #define mozilla_dom_audiochannelservice_h__
 
-#include "nsIAudioChannelService.h"
 #include "nsAutoPtr.h"
 #include "nsIObserver.h"
 #include "nsTArray.h"
+#include "nsITimer.h"
 
+#include "AudioChannelCommon.h"
 #include "AudioChannelAgent.h"
 #include "nsAttrValue.h"
 #include "nsClassHashtable.h"
 #include "mozilla/dom/AudioChannelBinding.h"
 
 class nsIRunnable;
 class nsPIDOMWindow;
 
 namespace mozilla {
 namespace dom {
 #ifdef MOZ_WIDGET_GONK
 class SpeakerManagerService;
 #endif
-
-#define NUMBER_OF_AUDIO_CHANNELS (uint32_t)AudioChannel::Publicnotification + 1
-
-class AudioChannelService final : public nsIAudioChannelService
-                                , public nsIObserver
+class AudioChannelService
+: public nsIObserver
+, public nsITimerCallback
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
-  NS_DECL_NSIAUDIOCHANNELSERVICE
+  NS_DECL_NSITIMERCALLBACK
+
+  /**
+   * Returns the AudioChannelServce singleton or null if the process havn't create it before.
+   * Only to be called from main thread.
+   */
+  static AudioChannelService* GetAudioChannelService();
 
   /**
    * Returns the AudioChannelServce singleton.
    * If AudioChannelServce is not exist, create and return new one.
    * Only to be called from main thread.
    */
-  static already_AddRefed<AudioChannelService> GetOrCreate();
+  static AudioChannelService* GetOrCreateAudioChannelService();
 
   /**
    * Shutdown the singleton.
    */
   static void Shutdown();
 
   /**
    * Any audio channel agent that starts playing should register itself to
    * this service, sharing the AudioChannel.
    */
-  void RegisterAudioChannelAgent(AudioChannelAgent* aAgent, AudioChannel aChannel);
+  virtual void RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
+                                         AudioChannel aChannel,
+                                         bool aWithVideo);
 
   /**
    * Any audio channel agent that stops playing should unregister itself to
    * this service.
    */
-  void UnregisterAudioChannelAgent(AudioChannelAgent* aAgent);
+  virtual void UnregisterAudioChannelAgent(AudioChannelAgent* aAgent);
 
   /**
-   * Return the state to indicate this audioChannel for his window should keep
-   * playing/muted.
+   * Return the state to indicate this agent should keep playing/
+   * fading volume/muted.
    */
-  void GetState(nsPIDOMWindow* aWindow, uint32_t aChannel,
-                float* aVolume, bool* aMuted);
-
-  /* Methods for the BrowserElementAudioChannel */
-  float GetAudioChannelVolume(nsPIDOMWindow* aWindow, AudioChannel aChannel);
+  virtual AudioChannelState GetState(AudioChannelAgent* aAgent,
+                                     bool aElementHidden);
 
-  void SetAudioChannelVolume(nsPIDOMWindow* aWindow, AudioChannel aChannel,
-                             float aVolume);
-
-  bool GetAudioChannelMuted(nsPIDOMWindow* aWindow, AudioChannel aChannel);
-
-  void SetAudioChannelMuted(nsPIDOMWindow* aWindow, AudioChannel aChannel,
-                            bool aMuted);
-
-  bool IsAudioChannelActive(nsPIDOMWindow* aWindow, AudioChannel aChannel);
+  /**
+   * Return true if there is a content channel active in this process
+   * or one of its subprocesses.
+   */
+  virtual bool ContentOrNormalChannelIsActive();
 
   /**
    * Return true if there is a telephony channel active in this process
    * or one of its subprocesses.
    */
-  bool TelephonyChannelIsActive();
+  virtual bool TelephonyChannelIsActive();
 
   /**
    * Return true if a normal or content channel is active for the given
    * process ID.
    */
-  bool ProcessContentOrNormalChannelIsActive(uint64_t aChildID);
+  virtual bool ProcessContentOrNormalChannelIsActive(uint64_t aChildID);
 
   /***
    * AudioChannelManager calls this function to notify the default channel used
    * to adjust volume when there is no any active channel. if aChannel is -1,
    * the default audio channel will be used. Otherwise aChannel is casted to
    * AudioChannel enum.
    */
   virtual void SetDefaultVolumeControlChannel(int32_t aChannel,
@@ -120,79 +120,171 @@ public:
 #endif
 
   static const nsAttrValue::EnumTable* GetAudioChannelTable();
   static AudioChannel GetAudioChannel(const nsAString& aString);
   static AudioChannel GetDefaultAudioChannel();
   static void GetAudioChannelString(AudioChannel aChannel, nsAString& aString);
   static void GetDefaultAudioChannelString(nsAString& aString);
 
-  void Notify(uint64_t aWindowID);
+  void Notify();
+
+protected:
+  void SendNotification();
+
+  /**
+   * Send the audio-channel-changed notification for the given process ID if
+   * needed.
+   */
+  void SendAudioChannelChangedNotification(uint64_t aChildID);
 
-private:
-  AudioChannelService();
-  ~AudioChannelService();
+  /* Register/Unregister IPC types: */
+  void RegisterType(AudioChannel aChannel, uint64_t aChildID, bool aWithVideo);
+  void UnregisterType(AudioChannel aChannel, bool aElementHidden,
+                      uint64_t aChildID, bool aWithVideo);
+  void UnregisterTypeInternal(AudioChannel aChannel, bool aElementHidden,
+                              uint64_t aChildID, bool aWithVideo);
+
+  AudioChannelState GetStateInternal(AudioChannel aChannel, uint64_t aChildID,
+                                     bool aElementHidden,
+                                     bool aElementWasHidden);
+
+  /* Update the internal type value following the visibility changes */
+  void UpdateChannelType(AudioChannel aChannel, uint64_t aChildID,
+                         bool aElementHidden, bool aElementWasHidden);
 
   /* Send the default-volume-channel-changed notification */
   void SetDefaultVolumeControlChannelInternal(int32_t aChannel,
                                               bool aVisible, uint64_t aChildID);
 
-  struct AudioChannelConfig final
-  {
-    AudioChannelConfig()
-      : mVolume(1.0)
-      , mMuted(false)
-      , mNumberOfAgents(0)
-    {}
+  AudioChannelState CheckTelephonyPolicy(AudioChannel aChannel,
+                                         uint64_t aChildID);
+  void RegisterTelephonyChild(uint64_t aChildID);
+  void UnregisterTelephonyChild(uint64_t aChildID);
+
+  AudioChannelService();
+  virtual ~AudioChannelService();
 
-    float mVolume;
-    bool mMuted;
-
-    uint32_t mNumberOfAgents;
+  enum AudioChannelInternalType {
+    AUDIO_CHANNEL_INT_NORMAL = 0,
+    AUDIO_CHANNEL_INT_NORMAL_HIDDEN,
+    AUDIO_CHANNEL_INT_CONTENT,
+    AUDIO_CHANNEL_INT_CONTENT_HIDDEN,
+    AUDIO_CHANNEL_INT_NOTIFICATION,
+    AUDIO_CHANNEL_INT_NOTIFICATION_HIDDEN,
+    AUDIO_CHANNEL_INT_ALARM,
+    AUDIO_CHANNEL_INT_ALARM_HIDDEN,
+    AUDIO_CHANNEL_INT_TELEPHONY,
+    AUDIO_CHANNEL_INT_TELEPHONY_HIDDEN,
+    AUDIO_CHANNEL_INT_RINGER,
+    AUDIO_CHANNEL_INT_RINGER_HIDDEN,
+    AUDIO_CHANNEL_INT_PUBLICNOTIFICATION,
+    AUDIO_CHANNEL_INT_PUBLICNOTIFICATION_HIDDEN,
+    AUDIO_CHANNEL_INT_LAST
   };
 
-  struct AudioChannelWindow final
-  {
-    AudioChannelConfig mChannels[NUMBER_OF_AUDIO_CHANNELS];
-    nsClassHashtable<nsPtrHashKey<AudioChannelAgent>, AudioChannel> mAgents;
+  bool ChannelsActiveWithHigherPriorityThan(AudioChannelInternalType aType);
+
+  bool CheckVolumeFadedCondition(AudioChannelInternalType aType,
+                                 bool aElementHidden);
+
+  AudioChannelInternalType GetInternalType(AudioChannel aChannel,
+                                           bool aElementHidden);
+
+  class AudioChannelAgentData {
+  public:
+    AudioChannelAgentData(AudioChannel aChannel,
+                          bool aElementHidden,
+                          AudioChannelState aState,
+                          bool aWithVideo)
+    : mChannel(aChannel)
+    , mElementHidden(aElementHidden)
+    , mState(aState)
+    , mWithVideo(aWithVideo)
+    {}
+
+    AudioChannel mChannel;
+    bool mElementHidden;
+    AudioChannelState mState;
+    const bool mWithVideo;
   };
 
-  AudioChannelWindow&
-  GetOrCreateWindowData(nsPIDOMWindow* aWindow);
-
   static PLDHashOperator
-  TelephonyChannelIsActiveEnumerator(const uint64_t& aWindowID,
-                                     nsAutoPtr<AudioChannelWindow>& aWinData,
-                                     void *aPtr);
-
-  static PLDHashOperator
-  AnyAudioChannelIsActiveEnumerator(const uint64_t& aWindowID,
-                                    nsAutoPtr<AudioChannelWindow>& aWinData,
-                                    void *aPtr);
+  NotifyEnumerator(AudioChannelAgent* aAgent,
+                   AudioChannelAgentData* aData, void *aUnused);
 
   static PLDHashOperator
   RefreshAgentsVolumeEnumerator(AudioChannelAgent* aAgent,
-                                AudioChannel* aUnused,
+                                AudioChannelAgentData* aUnused,
                                 void *aPtr);
 
   static PLDHashOperator
-  NotifyEnumerator(AudioChannelAgent* aAgent,
-                   AudioChannel* aAudioChannel,
-                   void* aUnused);
+  CountWindowEnumerator(AudioChannelAgent* aAgent,
+                        AudioChannelAgentData* aUnused,
+                        void *aPtr);
 
-  nsClassHashtable<nsUint64HashKey, AudioChannelWindow> mWindows;
+  static PLDHashOperator
+  WindowDestroyedEnumerator(AudioChannelAgent* aAgent,
+                            nsAutoPtr<AudioChannelAgentData>& aData,
+                            void *aPtr);
 
+  // This returns the number of agents from this aWindow.
+  uint32_t CountWindow(nsIDOMWindow* aWindow);
+
+  nsClassHashtable< nsPtrHashKey<AudioChannelAgent>, AudioChannelAgentData > mAgents;
 #ifdef MOZ_WIDGET_GONK
   nsTArray<SpeakerManagerService*>  mSpeakerManager;
 #endif
+  nsTArray<uint64_t> mChannelCounters[AUDIO_CHANNEL_INT_LAST];
+
+  int32_t mCurrentHigherChannel;
+  int32_t mCurrentVisibleHigherChannel;
+
+  nsTArray<uint64_t> mWithVideoChildIDs;
+
+  // Telephony Channel policy is "LIFO", the last app to require the resource is
+  // allowed to play. The others are muted.
+  struct TelephonyChild {
+    uint64_t mChildID;
+    uint32_t mInstances;
+
+    explicit TelephonyChild(uint64_t aChildID)
+      : mChildID(aChildID)
+      , mInstances(1)
+    {}
+  };
+  nsTArray<TelephonyChild> mTelephonyChildren;
+
+  // mPlayableHiddenContentChildID stores the ChildID of the process which can
+  // play content channel(s) in the background.
+  // A background process contained content channel(s) will become playable:
+  //   1. When this background process registers its content channel(s) in
+  //   AudioChannelService and there is no foreground process with registered
+  //   content channel(s).
+  //   2. When this process goes from foreground into background and there is
+  //   no foreground process with registered content channel(s).
+  // A background process contained content channel(s) will become non-playable:
+  //   1. When there is a foreground process registering its content channel(s)
+  //   in AudioChannelService.
+  //   ps. Currently this condition is never satisfied because the default value
+  //   of visibility status of each channel during registering is hidden = true.
+  //   2. When there is a process with registered content channel(s) goes from
+  //   background into foreground.
+  //   3. When this process unregisters all hidden content channels.
+  //   4. When this process shuts down.
+  uint64_t mPlayableHiddenContentChildID;
 
   bool mDisabled;
 
   nsCOMPtr<nsIRunnable> mRunnable;
 
+  nsCOMPtr<nsITimer> mDeferTelChannelTimer;
+  bool mTimerElementHidden;
+  uint64_t mTimerChildID;
+
   uint64_t mDefChannelChildID;
 
   // This is needed for IPC comunication between
   // AudioChannelServiceChild and this class.
   friend class ContentParent;
   friend class ContentChild;
 };
 
new file mode 100644
--- /dev/null
+++ b/dom/audiochannel/AudioChannelServiceChild.cpp
@@ -0,0 +1,168 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "AudioChannelServiceChild.h"
+
+#include "base/basictypes.h"
+
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/unused.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "nsIObserverService.h"
+#include "nsThreadUtils.h"
+
+#ifdef MOZ_WIDGET_GONK
+#include "SpeakerManagerService.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::hal;
+
+StaticRefPtr<AudioChannelServiceChild> gAudioChannelServiceChild;
+
+// static
+AudioChannelService*
+AudioChannelServiceChild::GetAudioChannelService()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  return gAudioChannelServiceChild;
+
+}
+
+// static
+AudioChannelService*
+AudioChannelServiceChild::GetOrCreateAudioChannelService()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // If we already exist, exit early
+  if (gAudioChannelServiceChild) {
+    return gAudioChannelServiceChild;
+  }
+
+  // Create new instance, register, return
+  nsRefPtr<AudioChannelServiceChild> service = new AudioChannelServiceChild();
+  MOZ_ASSERT(service);
+
+  gAudioChannelServiceChild = service;
+  return gAudioChannelServiceChild;
+}
+
+void
+AudioChannelServiceChild::Shutdown()
+{
+  if (gAudioChannelServiceChild) {
+    gAudioChannelServiceChild = nullptr;
+  }
+}
+
+AudioChannelServiceChild::AudioChannelServiceChild()
+{
+}
+
+AudioChannelServiceChild::~AudioChannelServiceChild()
+{
+}
+
+AudioChannelState
+AudioChannelServiceChild::GetState(AudioChannelAgent* aAgent, bool aElementHidden)
+{
+  AudioChannelAgentData* data;
+  if (!mAgents.Get(aAgent, &data)) {
+    return AUDIO_CHANNEL_STATE_MUTED;
+  }
+
+  AudioChannelState state = AUDIO_CHANNEL_STATE_MUTED;
+  bool oldElementHidden = data->mElementHidden;
+
+  UpdateChannelType(data->mChannel, CONTENT_PROCESS_ID_MAIN, aElementHidden,
+                    oldElementHidden);
+
+  // Update visibility.
+  data->mElementHidden = aElementHidden;
+
+  ContentChild* cc = ContentChild::GetSingleton();
+  cc->SendAudioChannelGetState(data->mChannel, aElementHidden, oldElementHidden,
+                               &state);
+  data->mState = state;
+  cc->SendAudioChannelChangedNotification();
+
+  #ifdef MOZ_WIDGET_GONK
+  /** Only modify the speaker status when
+   *  (1) apps in the foreground.
+   *  (2) apps in the backgrund and inactive.
+   *  Notice : modify only when the visible status is stable, because there
+   *  has lantency in passing the visibility events.
+   **/
+  bool active = AnyAudioChannelIsActive();
+  if (aElementHidden == oldElementHidden &&
+      (!aElementHidden || (aElementHidden && !active))) {
+    for (uint32_t i = 0; i < mSpeakerManager.Length(); i++) {
+      mSpeakerManager[i]->SetAudioChannelActive(active);
+    }
+  }
+  #endif
+
+  return state;
+}
+
+void
+AudioChannelServiceChild::RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
+                                                    AudioChannel aChannel,
+                                                    bool aWithVideo)
+{
+  AudioChannelService::RegisterAudioChannelAgent(aAgent, aChannel, aWithVideo);
+
+  ContentChild::GetSingleton()->SendAudioChannelRegisterType(aChannel, aWithVideo);
+
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (obs) {
+    obs->NotifyObservers(nullptr, "audio-channel-agent-changed", nullptr);
+  }
+}
+
+void
+AudioChannelServiceChild::UnregisterAudioChannelAgent(AudioChannelAgent* aAgent)
+{
+  AudioChannelAgentData *pData;
+  if (!mAgents.Get(aAgent, &pData)) {
+    return;
+  }
+
+  // We need to keep a copy because unregister will remove the
+  // AudioChannelAgentData object from the hashtable.
+  AudioChannelAgentData data(*pData);
+
+  AudioChannelService::UnregisterAudioChannelAgent(aAgent);
+
+  ContentChild::GetSingleton()->SendAudioChannelUnregisterType(
+      data.mChannel, data.mElementHidden, data.mWithVideo);
+
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (obs) {
+    obs->NotifyObservers(nullptr, "audio-channel-agent-changed", nullptr);
+  }
+#ifdef MOZ_WIDGET_GONK
+  bool active = AnyAudioChannelIsActive();
+  for (uint32_t i = 0; i < mSpeakerManager.Length(); i++) {
+    mSpeakerManager[i]->SetAudioChannelActive(active);
+  }
+#endif
+}
+
+void
+AudioChannelServiceChild::SetDefaultVolumeControlChannel(int32_t aChannel,
+                                                         bool aHidden)
+{
+  ContentChild *cc = ContentChild::GetSingleton();
+  if (cc) {
+    cc->SendAudioChannelChangeDefVolChannel(aChannel, aHidden);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/dom/audiochannel/AudioChannelServiceChild.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 mozilla_dom_audiochannelservicechild_h__
+#define mozilla_dom_audiochannelservicechild_h__
+
+#include "nsAutoPtr.h"
+#include "nsISupports.h"
+
+#include "AudioChannelService.h"
+#include "AudioChannelCommon.h"
+
+namespace mozilla {
+namespace dom {
+
+class AudioChannelServiceChild : public AudioChannelService
+{
+public:
+
+  /**
+   * Returns the AudioChannelServce singleton or null if the process havn't create it before.
+   * Only to be called from main thread.
+   */
+  static AudioChannelService* GetAudioChannelService();
+
+  /**
+   * Returns the AudioChannelServce singleton.
+   * If AudioChannelServce is not exist, create and return new one.
+   * Only to be called from main thread.
+   */
+  static AudioChannelService* GetOrCreateAudioChannelService();
+
+  static void Shutdown();
+
+  virtual void RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
+                                         AudioChannel aChannel,
+                                         bool aWithVideo);
+  virtual void UnregisterAudioChannelAgent(AudioChannelAgent* aAgent);
+
+  /**
+   * Return the state to indicate this agent should keep playing/
+   * fading volume/muted.
+   */
+  virtual AudioChannelState GetState(AudioChannelAgent* aAgent,
+                                     bool aElementHidden);
+
+  virtual void SetDefaultVolumeControlChannel(int32_t aChannel,
+                                              bool aHidden);
+
+protected:
+  AudioChannelServiceChild();
+  virtual ~AudioChannelServiceChild();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
+
--- a/dom/audiochannel/moz.build
+++ b/dom/audiochannel/moz.build
@@ -1,28 +1,32 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
+TEST_DIRS += ['tests']
+
 XPIDL_SOURCES += [
     'nsIAudioChannelAgent.idl',
-    'nsIAudioChannelService.idl',
 ]
 
 XPIDL_MODULE = 'dom_audiochannel'
 
 EXPORTS += [
     'AudioChannelAgent.h',
+    'AudioChannelCommon.h',
     'AudioChannelService.h',
+    'AudioChannelServiceChild.h',
 ]
 
 UNIFIED_SOURCES += [
     'AudioChannelAgent.cpp',
     'AudioChannelService.cpp',
+    'AudioChannelServiceChild.cpp',
 ]
 
 FAIL_ON_WARNINGS = True
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
--- a/dom/audiochannel/nsIAudioChannelAgent.idl
+++ b/dom/audiochannel/nsIAudioChannelAgent.idl
@@ -1,40 +1,51 @@
 /* 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/. */
 
 #include "nsISupports.idl"
 
 interface nsIDOMWindow;
 
-[uuid(4f537c88-3722-4946-9a09-ce559fa0591d)]
+[uuid(194b55d9-39c0-45c6-b8ef-b8049f978ea5)]
 interface nsIAudioChannelAgentCallback : nsISupports
 {
   /**
+   * Notified when the playable status of channel is changed.
+   *
+   * @param canPlay
+   *        Callback from agent to notify component of the playable status
+   *        of the channel. If canPlay is muted state, component SHOULD stop
+   *        playing media associated with this channel as soon as possible. if
+   *        it is faded state then the volume of media should be reduced.
+   */
+  void canPlayChanged(in long canPlay);
+
+  /**
    * Notified when the window volume/mute is changed
    */
-  void windowVolumeChanged(in float aVolume, in bool aMuted);
+  void windowVolumeChanged();
 };
 
 /**
  * This interface provides an agent for gecko components to participate
  * in the audio channel service. Gecko components are responsible for
  *   1. Indicating what channel type they are using (via the init() member
  *      function).
  *   2. Before playing, checking the playable status of the channel.
  *   3. Notifying the agent when they start/stop using this channel.
  *   4. Notifying the agent of changes to the visibility of the component using
  *      this channel.
  *
  * The agent will invoke a callback to notify Gecko components of
  *   1. Changes to the playable status of this channel.
  */
 
-[uuid(363ff8d3-5bd2-485a-84ac-125062cbdc19)]
+[uuid(2b0222a5-8f7b-49d2-9ab8-cd01b744b23e)]
 interface nsIAudioChannelAgent : nsISupports
 {
   const long AUDIO_AGENT_CHANNEL_NORMAL             = 0;
   const long AUDIO_AGENT_CHANNEL_CONTENT            = 1;
   const long AUDIO_AGENT_CHANNEL_NOTIFICATION       = 2;
   const long AUDIO_AGENT_CHANNEL_ALARM              = 3;
   const long AUDIO_AGENT_CHANNEL_TELEPHONY          = 4;
   const long AUDIO_AGENT_CHANNEL_RINGER             = 5;
@@ -76,33 +87,56 @@ interface nsIAudioChannelAgent : nsISupp
    *
    * In order for this to work, |callback| must implement
    * nsISupportsWeakReference.
    */
   void initWithWeakCallback(in nsIDOMWindow window, in long channelType,
                             in nsIAudioChannelAgentCallback callback);
 
   /**
+   * This method is just like init(), and specify the channel is associated
+   * with video.
+   *
+   * @param weak
+   *    true if weak reference should be hold.
+   */
+  void initWithVideo(in nsIDOMWindow window, in long channelType,
+                     in nsIAudioChannelAgentCallback callback, in boolean weak);
+
+  /**
    * Notify the agent that we want to start playing.
    * Note: Gecko component SHOULD call this function first then start to
    *          play audio stream only when return value is true.
    *
    *
    * @return
    *    normal state: the agent has registered with audio channel service and
    *          the component should start playback.
    *    muted state: the agent has registered with audio channel service but
    *          the component should not start playback.
    *    faded state: the agent has registered with audio channel service the
    *          component should start playback as well as reducing the volume.
    */
-  void startPlaying(out float volume, out bool muted);
+  long startPlaying();
 
   /**
    * Notify the agent we no longer want to play.
    *
    * Note : even if startPlaying() returned false, the agent would still be
    *        registered with the audio channel service and receive callbacks for status changes.
    *        So stopPlaying must still eventually be called to unregister the agent with the
    *        channel service.
    */
   void stopPlaying();
+
+  /**
+   * Notify the agent of the visibility state of the window using this agent.
+   * @param visible
+   *    True if the window associated with the agent is visible.
+   */
+  void setVisibilityState(in boolean visible);
+
+  /**
+   * Retrieve the volume from the window.
+   */
+  readonly attribute float windowVolume;
 };
+
deleted file mode 100644
--- a/dom/audiochannel/nsIAudioChannelService.idl
+++ /dev/null
@@ -1,29 +0,0 @@
-/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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/. */
-
-#include "nsISupports.idl"
-
-interface nsIDOMWindow;
-
-[scriptable, builtinclass, uuid(323e5472-b8f4-4288-b1b9-53c7c54bbbe8)]
-interface nsIAudioChannelService : nsISupports
-{
-  float getAudioChannelVolume(in nsIDOMWindow window,
-                              in unsigned short audioChannel);
-
-  void setAudioChannelVolume(in nsIDOMWindow window,
-                             in unsigned short audioChannel,
-                             in float volume);
-
-  boolean getAudioChannelMuted(in nsIDOMWindow window,
-                               in unsigned short audioChannel);
-
-  void setAudioChannelMuted(in nsIDOMWindow window,
-                            in unsigned short audioChannel,
-                            in boolean muted);
-
-  boolean isAudioChannelActive(in nsIDOMWindow window,
-                               in unsigned short audioChannel);
-};
new file mode 100644
--- /dev/null
+++ b/dom/audiochannel/tests/AudioChannelChromeScript.js
@@ -0,0 +1,18 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+const { Services } = Cu.import('resource://gre/modules/Services.jsm');
+const { SystemAppProxy } = Cu.import('resource://gre/modules/SystemAppProxy.jsm');
+
+addMessageListener('init-chrome-event', function(message) {
+  // listen mozChromeEvent and forward to content process.
+  let type = message.type;
+
+  SystemAppProxy.addEventListener('mozChromeEvent', function(event) {
+    let details = event.detail;
+    if (details.type === type) {
+      sendAsyncMessage('chrome-event', details);
+    }
+  }, true);
+});
new file mode 100644
--- /dev/null
+++ b/dom/audiochannel/tests/TestAudioChannelService.cpp
@@ -0,0 +1,669 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+#ifdef XP_WIN
+#include <windows.h>
+#else
+#include <unistd.h>
+#endif
+
+#include "TestHarness.h"
+
+#include "nsWeakReference.h"
+#include "AudioChannelService.h"
+#include "AudioChannelAgent.h"
+
+#include "nsThreadUtils.h"
+
+#define TEST_ENSURE_BASE(_test, _msg)       \
+  PR_BEGIN_MACRO                            \
+    if (!(_test)) {                         \
+      fail(_msg);                           \
+      return NS_ERROR_FAILURE;              \
+    } else {                                \
+      passed(_msg);                         \
+    }                                       \
+  PR_END_MACRO
+
+using namespace mozilla::dom;
+
+void
+spin_events_loop_until_false(const bool* const aCondition)
+{
+  nsCOMPtr<nsIThread> thread(::do_GetCurrentThread());
+  nsresult rv = NS_OK;
+  bool processed = true;
+  while (*aCondition && NS_SUCCEEDED(rv)) {
+    rv = thread->ProcessNextEvent(true, &processed);
+  }
+}
+
+class Agent : public nsIAudioChannelAgentCallback,
+              public nsSupportsWeakReference
+{
+protected:
+  virtual ~Agent()
+  {
+    if (mRegistered) {
+      StopPlaying();
+    }
+  }
+
+public:
+  NS_DECL_ISUPPORTS
+
+  explicit Agent(AudioChannel aChannel)
+  : mChannel(aChannel)
+  , mWaitCallback(false)
+  , mRegistered(false)
+  , mCanPlay(AUDIO_CHANNEL_STATE_MUTED)
+  {
+    mAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1");
+  }
+
+  nsresult Init(bool video=false)
+  {
+    nsresult rv = NS_OK;
+    if (video) {
+      rv = mAgent->InitWithVideo(nullptr, static_cast<int32_t>(mChannel),
+                                 this, true);
+    }
+    else {
+      rv = mAgent->InitWithWeakCallback(nullptr, static_cast<int32_t>(mChannel),
+                                        this);
+    }
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    return mAgent->SetVisibilityState(false);
+  }
+
+  nsresult StartPlaying(AudioChannelState *_ret)
+  {
+    if (mRegistered) {
+      StopPlaying();
+    }
+
+    nsresult rv = mAgent->StartPlaying((int32_t *)_ret);
+    mRegistered = true;
+    return rv;
+  }
+
+  nsresult StopPlaying()
+  {
+    mRegistered = false;
+    spin_events_loop_until_false(&mWaitCallback);
+    return mAgent->StopPlaying();
+  }
+
+  nsresult SetVisibilityState(bool visible)
+  {
+    if (mRegistered) {
+      mWaitCallback = true;
+    }
+    return mAgent->SetVisibilityState(visible);
+  }
+
+  NS_IMETHODIMP CanPlayChanged(int32_t canPlay) override
+  {
+    mCanPlay = static_cast<AudioChannelState>(canPlay);
+    mWaitCallback = false;
+    return NS_OK;
+  }
+
+  NS_IMETHODIMP WindowVolumeChanged() override
+  {
+    return NS_OK;
+  }
+
+  nsresult GetCanPlay(AudioChannelState *_ret, bool aWaitCallback = false)
+  {
+    if (aWaitCallback) {
+      mWaitCallback = true;
+    }
+
+    spin_events_loop_until_false(&mWaitCallback);
+    *_ret = mCanPlay;
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIAudioChannelAgent> mAgent;
+  AudioChannel mChannel;
+  bool mWaitCallback;
+  bool mRegistered;
+  AudioChannelState mCanPlay;
+};
+
+NS_IMPL_ISUPPORTS(Agent, nsIAudioChannelAgentCallback,
+                  nsISupportsWeakReference)
+
+nsresult
+TestDoubleStartPlaying()
+{
+  nsRefPtr<Agent> agent = new Agent(AudioChannel::Normal);
+
+  nsresult rv = agent->Init();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  AudioChannelState playable;
+  rv = agent->mAgent->StartPlaying((int32_t *)&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = agent->mAgent->StartPlaying((int32_t *)&playable);
+  TEST_ENSURE_BASE(NS_FAILED(rv),
+    "Test0: StartPlaying calling twice must return error");
+
+  return NS_OK;
+}
+
+nsresult
+TestOneNormalChannel()
+{
+  nsRefPtr<Agent> agent = new Agent(AudioChannel::Normal);
+  nsresult rv = agent->Init();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  AudioChannelState playable;
+  rv = agent->StartPlaying(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_MUTED,
+    "Test1: A normal channel unvisible agent must be muted");
+
+  rv = agent->SetVisibilityState(true);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = agent->GetCanPlay(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test1: A normal channel visible agent must be playable");
+
+  return rv;
+}
+
+nsresult
+TestTwoNormalChannels()
+{
+  nsRefPtr<Agent> agent1 = new Agent(AudioChannel::Normal);
+  nsresult rv = agent1->Init();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsRefPtr<Agent> agent2 = new Agent(AudioChannel::Normal);
+  rv = agent2->Init();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  AudioChannelState playable;
+  rv = agent1->StartPlaying(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_MUTED,
+    "Test2: A normal channel unvisible agent1 must be muted");
+
+  rv = agent2->StartPlaying(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_MUTED,
+    "Test2: A normal channel unvisible agent2 must be muted");
+
+  rv = agent1->SetVisibilityState(true);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = agent2->SetVisibilityState(true);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = agent1->GetCanPlay(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test2: A normal channel visible agent1 must be playable");
+
+  rv = agent2->GetCanPlay(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test2: A normal channel visible agent2 must be playable");
+
+  return rv;
+}
+
+nsresult
+TestContentChannels()
+{
+  nsRefPtr<Agent> agent1 = new Agent(AudioChannel::Content);
+  nsresult rv = agent1->Init();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsRefPtr<Agent> agent2 = new Agent(AudioChannel::Content);
+  rv = agent2->Init();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // All content channels in the foreground can be allowed to play
+  rv = agent1->SetVisibilityState(true);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = agent2->SetVisibilityState(true);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  AudioChannelState playable;
+  rv = agent1->StartPlaying(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test3: A content channel visible agent1 must be playable");
+
+  rv = agent2->StartPlaying(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test3: A content channel visible agent2 must be playable");
+
+  // Test the transition state of one content channel tried to set non-visible
+  // state first when app is going to background.
+  rv = agent1->SetVisibilityState(false);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = agent1->GetCanPlay(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test3: A content channel unvisible agent1 must be playable from "
+    "foreground to background");
+
+  // Test all content channels set non-visible already
+  rv = agent2->SetVisibilityState(false);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = agent2->GetCanPlay(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test3: A content channel unvisible agent2 must be playable from "
+    "foreground to background");
+
+  // Clear the content channels & mActiveContentChildIDs in AudioChannelService.
+  // If agent stop playable in the background, we will reserve it's childID in
+  // mActiveContentChildIDs, then it can allow to play next song. So we set agents
+  // to foreground first then stopping to play
+  rv = agent1->SetVisibilityState(true);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = agent2->SetVisibilityState(true);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = agent1->StopPlaying();
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = agent2->StopPlaying();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Test that content channels can be allow to play when they starts from
+  // the background state
+  rv = agent1->SetVisibilityState(false);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = agent2->SetVisibilityState(false);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = agent1->StartPlaying(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test3: A content channel unvisible agent1 must be playable "
+    "from background state");
+
+  rv = agent2->StartPlaying(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test3: A content channel unvisible agent2 must be playable "
+    "from background state");
+
+  return rv;
+}
+
+nsresult
+TestFadedState()
+{
+  nsRefPtr<Agent> normalAgent = new Agent(AudioChannel::Normal);
+  nsresult rv = normalAgent->Init();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsRefPtr<Agent> contentAgent = new Agent(AudioChannel::Content);
+  rv = contentAgent->Init();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsRefPtr<Agent> notificationAgent = new Agent(AudioChannel::Notification);
+  rv = notificationAgent->Init();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = normalAgent->SetVisibilityState(true);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = contentAgent->SetVisibilityState(true);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = notificationAgent->SetVisibilityState(true);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  AudioChannelState playable;
+  rv = normalAgent->StartPlaying(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test4: A normal channel visible agent must be playable");
+
+  rv = contentAgent->StartPlaying(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test4: A content channel visible agent must be playable");
+
+  rv = notificationAgent->StartPlaying(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test4: A notification channel visible agent must be playable");
+
+  rv = contentAgent->GetCanPlay(&playable, true);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_FADED,
+    "Test4: A content channel unvisible agent must be faded because of "
+    "notification channel is playing");
+
+  rv = contentAgent->SetVisibilityState(false);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = contentAgent->GetCanPlay(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_FADED,
+    "Test4: A content channel unvisible agent must be faded because of "
+    "notification channel is playing");
+
+  rv = notificationAgent->SetVisibilityState(false);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = notificationAgent->GetCanPlay(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test4: A notification channel unvisible agent must be playable from "
+    "foreground to background");
+
+  rv = notificationAgent->StopPlaying();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = contentAgent->GetCanPlay(&playable, true);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test4: A content channel unvisible agent must be playable "
+    "because of notification channel is stopped");
+
+  rv = contentAgent->SetVisibilityState(true);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return rv;
+}
+
+nsresult
+TestPriorities()
+{
+  nsRefPtr<Agent> normalAgent = new Agent(AudioChannel::Normal);
+  nsresult rv = normalAgent->Init();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsRefPtr<Agent> contentAgent = new Agent(AudioChannel::Content);
+  rv = contentAgent->Init();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsRefPtr<Agent> notificationAgent = new Agent(AudioChannel::Notification);
+  rv = notificationAgent->Init();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsRefPtr<Agent> alarmAgent = new Agent(AudioChannel::Alarm);
+  rv = alarmAgent->Init();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsRefPtr<Agent> telephonyAgent = new Agent(AudioChannel::Telephony);
+  rv = telephonyAgent->Init();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsRefPtr<Agent> ringerAgent = new Agent(AudioChannel::Ringer);
+  rv = ringerAgent->Init();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsRefPtr<Agent> pNotificationAgent =
+    new Agent(AudioChannel::Publicnotification);
+  rv = pNotificationAgent->Init();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  AudioChannelState playable;
+
+  rv = normalAgent->StartPlaying(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_MUTED,
+    "Test5: A normal channel unvisible agent must be muted");
+
+  rv = contentAgent->StartPlaying(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test5: A content channel unvisible agent must be playable while "
+    "playing from background state");
+
+  rv = notificationAgent->StartPlaying(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test5: A notification channel unvisible agent must be playable");
+
+  rv = alarmAgent->StartPlaying(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test5: An alarm channel unvisible agent must be playable");
+
+  rv = notificationAgent->StartPlaying(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_MUTED,
+    "Test5: A notification channel unvisible agent must be muted when an "
+    "alarm is playing");
+
+  rv = telephonyAgent->StartPlaying(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test5: A telephony channel unvisible agent must be playable");
+
+  rv = alarmAgent->StartPlaying(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_MUTED,
+    "Test5: An alarm channel unvisible agent must be muted when a telephony "
+    "is playing");
+
+  rv = ringerAgent->StartPlaying(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test5: A ringer channel unvisible agent must be playable");
+
+  rv = telephonyAgent->StartPlaying(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_MUTED,
+    "Test5: A telephony channel unvisible agent must be muted when a ringer "
+    "is playing");
+
+  rv = pNotificationAgent->StartPlaying(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test5: A pNotification channel unvisible agent must be playable");
+
+  rv = ringerAgent->StartPlaying(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_MUTED,
+    "Test5: A ringer channel unvisible agent must be muted when a public "
+    "notification is playing");
+
+  // Stop to play notification channel or normal/content will be faded.
+  // Which already be tested on Test 4.
+  rv = notificationAgent->StopPlaying();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Settings visible the normal channel.
+  rv = normalAgent->SetVisibilityState(true);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = normalAgent->GetCanPlay(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test5: A normal channel visible agent must be playable");
+
+  // Set the content channel as visible .
+  rv = contentAgent->SetVisibilityState(true);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Content must be playable because visible.
+  rv = contentAgent->GetCanPlay(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test5: A content channel visible agent must be playable");
+
+  // Set the alarm channel as visible.
+  rv = alarmAgent->SetVisibilityState(true);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = alarmAgent->GetCanPlay(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test5: An alarm channel visible agent must be playable");
+
+  // Set the telephony channel as visible.
+  rv = telephonyAgent->SetVisibilityState(true);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = telephonyAgent->GetCanPlay(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test5: A telephony channel visible agent must be playable");
+
+  // Set the ringer channel as visible.
+  rv = ringerAgent->SetVisibilityState(true);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = ringerAgent->GetCanPlay(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test5: A ringer channel visible agent must be playable");
+
+  // Set the public notification channel as visible.
+  rv = pNotificationAgent->SetVisibilityState(true);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = pNotificationAgent->GetCanPlay(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test5: A pNotification channel visible agent must be playable");
+
+  return rv;
+}
+
+nsresult
+TestOneVideoNormalChannel()
+{
+  nsRefPtr<Agent> agent1 = new Agent(AudioChannel::Normal);
+  nsresult rv = agent1->Init(true);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsRefPtr<Agent> agent2 = new Agent(AudioChannel::Content);
+  rv = agent2->Init(false);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  AudioChannelState playable;
+  rv = agent1->StartPlaying(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_MUTED,
+    "Test6: A video normal channel invisible agent1 must be muted");
+
+  rv = agent2->StartPlaying(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test6: A content channel invisible agent2 must be playable");
+
+  // one video normal channel in foreground and one content channel in background
+  rv = agent1->SetVisibilityState(true);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = agent1->GetCanPlay(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test6: A video normal channel visible agent1 must be playable");
+
+  rv = agent2->GetCanPlay(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_MUTED,
+    "Test6: A content channel invisible agent2 must be muted");
+
+  // both one video normal channel and one content channel in foreground
+  rv = agent2->SetVisibilityState(true);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = agent1->GetCanPlay(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test6: A video normal channel visible agent1 must be playable");
+
+  rv = agent2->GetCanPlay(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test6: A content channel visible agent2 must be playable");
+
+  // one video normal channel in background and one content channel in foreground
+  rv = agent1->SetVisibilityState(false);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = agent1->GetCanPlay(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_MUTED,
+    "Test6: A video normal channel invisible agent1 must be muted");
+
+  rv = agent2->GetCanPlay(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test6: A content channel visible agent2 must be playable");
+
+  // both one video normal channel and one content channel in background
+  rv = agent2->SetVisibilityState(false);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = agent1->GetCanPlay(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_MUTED,
+    "Test6: A video normal channel invisible agent1 must be muted");
+
+  rv = agent2->GetCanPlay(&playable);
+  NS_ENSURE_SUCCESS(rv, rv);
+  TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
+    "Test6: A content channel invisible agent2 must be playable");
+
+  return rv;
+}
+
+int main(int argc, char** argv)
+{
+  ScopedXPCOM xpcom("AudioChannelService");
+  if (xpcom.failed()) {
+    return 1;
+  }
+
+  if (NS_FAILED(TestDoubleStartPlaying())) {
+    return 1;
+  }
+
+  if (NS_FAILED(TestOneNormalChannel())) {
+    return 1;
+  }
+
+  if (NS_FAILED(TestTwoNormalChannels())) {
+    return 1;
+  }
+
+  if (NS_FAILED(TestContentChannels())) {
+    return 1;
+  }
+
+  if (NS_FAILED(TestFadedState())) {
+    return 1;
+  }
+
+  // Channel type with AudioChannel::Telephony cannot be unregistered until the
+  // main thread has chances to process 1500 millisecond timer. In order to
+  // skip ambiguous return value of ChannelsActiveWithHigherPriorityThan(), new
+  // test cases are added before any test case that registers the channel type
+  // with AudioChannel::Telephony channel.
+  if (NS_FAILED(TestOneVideoNormalChannel())) {
+    return 1;
+  }
+
+  if (NS_FAILED(TestPriorities())) {
+    return 1;
+  }
+
+  return 0;
+}
+
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..d7f6a0ccf47fbc30e936b47e3f4d2cf8f4a90a16
GIT binary patch
literal 16521
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
DFy$Y2
new file mode 100644
--- /dev/null
+++ b/dom/audiochannel/tests/file_audio.html
@@ -0,0 +1,99 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test audio-channel-changed & visible-audio-channel-changed mozChromeEvent</title>
+</head>
+<body>
+  <div id="content"></div>
+  <script>
+  var normalAudio;
+  var contentAudio;
+  var notificationAudio;
+  var alarmAudio;
+  var telephonyAudio;
+  var ringerAudio;
+  var publicnotificationAudio;
+
+  function playWithAudioType(audio, type) {
+    audio.mozAudioChannelType = type;
+    audio.src = "test.ogg";
+    audio.loop = true;
+
+    audio.play();
+  }
+
+  function runTest() {
+    // normal channel.
+    normalAudio = new Audio();
+    playWithAudioType(normalAudio, 'normal');
+
+    // content channel.
+    contentAudio = new Audio();
+    playWithAudioType(contentAudio, 'content');
+
+    // notification channel.
+    notificationAudio = new Audio();
+    playWithAudioType(notificationAudio, 'notification');
+
+    // alarm channel.
+    alarmAudio = new Audio();
+    playWithAudioType(alarmAudio, 'alarm');
+
+    // telephony channel.
+    telephonyAudio = new Audio();
+    playWithAudioType(telephonyAudio, 'telephony');
+
+    // ringer channel.
+    ringerAudio = new Audio();
+    playWithAudioType(ringerAudio, 'ringer');
+
+    // publicnotification channel.
+    publicnotificationAudio = new Audio();
+    playWithAudioType(publicnotificationAudio, 'publicnotification');
+
+    window.addEventListener('hashchange', function(event) {
+      if (location.hash == "#pauseAudio") {
+        publicnotificationAudio.pause();
+        ringerAudio.pause();
+        telephonyAudio.pause();
+      }
+
+      if (location.hash == "#pauseAudioFollowing") {
+        alarmAudio.pause();
+        notificationAudio.pause();
+        contentAudio.pause();
+        normalAudio.pause();
+      }
+    }, false);
+  }
+
+  function checkBackgroundStatus() {
+    if (location.hash == "#fg") {
+      runTest();
+      return;
+    }
+
+    if (document.hidden) {
+      runTest();
+      return;
+    }
+
+    document.addEventListener('visibilitychange', function visibilityChange() {
+      if (document.hidden) {
+        runTest();
+      }
+    });
+  }
+
+  SpecialPowers.pushPermissions(
+    [{ "type": "audio-channel-content", "allow": 1, "context": document },
+     { "type": "audio-channel-notification", "allow": 1, "context": document },
+     { "type": "audio-channel-alarm", "allow": 1, "context": document },
+     { "type": "audio-channel-telephony", "allow": 1, "context": document },
+     { "type": "audio-channel-ringer", "allow": 1, "context": document },
+     { "type": "audio-channel-publicnotification", "allow": 1, "context": document }],
+    checkBackgroundStatus);
+
+  </script>
+</body>
+</html>
rename from dom/browser-element/mochitest/test_browserElement_oop_AudioChannel.html
rename to dom/audiochannel/tests/file_telephonyPolicy.html
--- a/dom/browser-element/mochitest/test_browserElement_oop_AudioChannel.html
+++ b/dom/audiochannel/tests/file_telephonyPolicy.html
@@ -1,13 +1,18 @@
 <!DOCTYPE HTML>
 <html>
 <head>
-  <title>Test of browser element audioChannel.</title>
-  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="application/javascript" src="browserElementTestHelpers.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <meta charset="utf-8">
+  <title>Test Telephony Channel Policy</title>
 </head>
 <body>
-<script type="application/javascript;version=1.7" src="browserElement_AudioChannel.js">
-</script>
+<div id="container"></div>
+  <script type="application/javascript;version=1.7">
+
+  var audio = new Audio();
+  audio.mozAudioChannelType = 'telephony';
+  audio.src = "audio.ogg";
+  audio.play();
+
+  </script>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/dom/audiochannel/tests/mochitest.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+support-files =
+  audio.ogg
+  file_audio.html
+  file_telephonyPolicy.html
+  AudioChannelChromeScript.js
+
+[test_telephonyPolicy.html]
+skip-if = buildapp == 'mulet' || (toolkit == 'gonk' || e10s) || os == "android"
+[test_audioChannelChange.html]
+skip-if = (toolkit != 'gonk')
new file mode 100644
--- /dev/null
+++ b/dom/audiochannel/tests/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+GeckoCppUnitTests([
+    'TestAudioChannelService',
+])
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+    DEFINES['NOMINMAX'] = True
+
+MOCHITEST_MANIFESTS += ['mochitest.ini']
+
+FAIL_ON_WARNINGS = True
new file mode 100644
--- /dev/null
+++ b/dom/audiochannel/tests/test_audioChannelChange.html
@@ -0,0 +1,209 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test audio-channel-changed & visible-audio-channel-changed mozChromeEvent</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+  <div id="content"></div>
+  <script type="application/javascript;version=1.7">
+  var expectedAudioTypes;
+  var expectedVisibleAudioTypes;
+  var expectedVisibleAudioType;
+  var index;
+  var visibleIndex;
+  var iframe1;
+  var normalAudio;
+
+  function playWithAudioType(audio, type) {
+    audio.mozAudioChannelType = type;
+    audio.src = "test.ogg";
+    audio.loop = true;
+
+    audio.play();
+  }
+
+  function fgBgTestListener(message) {
+    var type = message.type;
+    var channel = message.channel;
+
+    if (type == 'audio-channel-changed') {
+      is(channel, expectedAudioTypes[index], channel + " is received and expected " + expectedAudioTypes[index]);
+      index++;
+    }
+
+    if (type == 'visible-audio-channel-changed') {
+      is(channel, expectedVisibleAudioType, channel + " is received and expected " + expectedVisibleAudioType);
+    }
+
+    // All audio types are playing now so ask to pause them.
+    // This call will stop audio from highest to telephony.
+    if ('cmd-pause' == expectedAudioTypes[index]) {
+      iframe1.src = 'file_audio.html#pauseAudio';
+      index++;
+    }
+
+    // According to there is a 1.5 second delay of releasing telephony,
+    // we need to wait for it then continue to pause others.
+    if ('cmd-secondPause' == expectedAudioTypes[index]) {
+      iframe1.src = 'file_audio.html#pauseAudioFollowing';
+      index++;
+    }
+
+    if (index == expectedAudioTypes.length) {
+      document.body.removeChild(iframe1);
+      script.removeMessageListener('chrome-event', fgBgTestListener);
+      normalAudio.pause();
+      SimpleTest.finish();
+    }
+  }
+
+  // Channel of visible-audio-channel-changed event should be always normal.
+  // Audios in background should not effect visible-audio-channel-changed.
+  function runFgBgTest() {
+    expectedAudioTypes = ["normal", "content", "notification",
+                          "alarm", "telephony", "ringer", "publicnotification", "cmd-pause",
+                          "ringer", "telephony", "alarm", "cmd-secondPause", "notification",
+                          "content", "normal"];
+    expectedVisibleAudioType = "normal";
+    index = 0;
+
+    script.addMessageListener('chrome-event', fgBgTestListener);
+
+    // To play a audio with normal channel in the foreground.
+    normalAudio = new Audio();
+    playWithAudioType(normalAudio, 'normal');
+
+    iframe1.src = 'file_audio.html#bg';
+    document.body.appendChild(iframe1);
+    iframe1.setVisible(false);
+  }
+
+  function bgTestListener(message) {
+    var type = message.type;
+    var channel = message.channel;
+
+    if (type == 'audio-channel-changed') {
+      is(channel, expectedAudioTypes[index], channel + " is received and expected " + expectedAudioTypes[index]);
+      index++;
+    }
+
+    if (type == 'visible-audio-channel-changed') {
+      is(channel, expectedVisibleAudioType, channel + " is received and expected " + expectedVisibleAudioType);
+    }
+
+    // All audio types are playing now so ask to pause them.
+    if ('cmd-pause' == expectedAudioTypes[index]) {
+      iframe1.src = 'file_audio.html#pauseAudio';
+      index++;
+    }
+
+    if ('cmd-secondPause' == expectedAudioTypes[index]) {
+      iframe1.src = 'file_audio.html#pauseAudioFollowing';
+      index++;
+    }
+
+    if (index == expectedAudioTypes.length) {
+      document.body.removeChild(iframe1);
+      script.removeMessageListener('chrome-event', bgTestListener);
+      runFgBgTest();
+    }
+  }
+
+  // 1. Channel of visible-audio-channel-changed event should be always none.
+  // 2. normal is not allowed to be played in the background.
+  function runBgTest() {
+    expectedAudioTypes = ["content", "notification",
+                          "alarm", "telephony", "ringer", "publicnotification", "cmd-pause",
+                         "ringer", "telephony", "alarm", "cmd-secondPause", "notification",
+                          "content", "none"];
+    expectedVisibleAudioType = "none";
+    index = 0;
+
+    script.addMessageListener('chrome-event', bgTestListener);
+
+    iframe1.src = 'file_audio.html#bg';
+    document.body.appendChild(iframe1);
+    iframe1.setVisible(false);
+  }
+
+  function fgTestListener(message) {
+    var type = message.type;
+    var channel = message.channel;
+
+    if (type == 'audio-channel-changed') {
+      is(channel, expectedAudioTypes[index], channel + " is received and expected " + expectedAudioTypes[index]);
+      index++;
+    }
+
+    if (type == 'visible-audio-channel-changed') {
+      is(channel, expectedAudioTypes[visibleIndex], channel + " is received and expected " + expectedAudioTypes[visibleIndex]);
+      visibleIndex++;
+    }
+
+    // All audio types are playing now so ask to pause them.
+    if ('cmd-pause' == expectedAudioTypes[visibleIndex] &&
+        'cmd-pause' == expectedAudioTypes[index]) {
+      iframe1.src = 'file_audio.html#pauseAudio';
+      visibleIndex++;
+      index++;
+    }
+
+    if ('cmd-secondPause' == expectedAudioTypes[visibleIndex] &&
+        'cmd-secondPause' == expectedAudioTypes[index]) {
+      iframe1.src = 'file_audio.html#pauseAudioFollowing';
+      visibleIndex++;
+      index++;
+    }
+
+    if (index == expectedAudioTypes.length && visibleIndex == expectedAudioTypes.length) {
+      document.body.removeChild(iframe1);
+      script.removeMessageListener('chrome-event', fgTestListener);
+      runBgTest();
+    }
+  }
+
+  // The foreground audio will effect both of audio-channel-changed and
+  // visible-audio-channel-changed.
+  function runFgTest() {
+    expectedAudioTypes = ["normal", "content", "notification",
+                          "alarm", "telephony", "ringer", "publicnotification",
+                          "cmd-pause", "ringer", "telephony", "alarm",
+                          "cmd-secondPause", "notification", "content",
+                          "normal", "none"];
+
+    index = 0;
+    visibleIndex = 0;
+
+    script.addMessageListener('chrome-event', fgTestListener);
+
+    iframe1 = document.createElement('iframe');
+    iframe1.setAttribute('mozbrowser', true);
+    iframe1.src = 'file_audio.html#fg';
+    document.body.appendChild(iframe1);
+  }
+
+  var url = SimpleTest.getTestFileURL("AudioChannelChromeScript.js")
+  var script = SpecialPowers.loadChromeScript(url);
+  script.sendAsyncMessage("init-chrome-event", {
+    type: 'audio-channel-changed'
+  });
+  script.sendAsyncMessage("init-chrome-event", {
+    type: 'visible-audio-channel-changed'
+  });
+
+  SimpleTest.waitForExplicitFinish();
+
+  SpecialPowers.pushPermissions(
+    [{ "type": "browser", "allow": 1, "context": document },
+     { "type": "embed-apps", "allow": 1, "context": document },
+     { "type": "webapps-manage", "allow": 1, "context": document }], function() {
+    SpecialPowers.pushPrefEnv({"set": [["dom.ipc.browser_frames.oop_by_default", true],
+                                       ["media.useAudioChannelService", true],
+                                       ["dom.mozBrowserFramesEnabled", true]]}, runFgTest);
+  });
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/audiochannel/tests/test_telephonyPolicy.html
@@ -0,0 +1,87 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test the Telephony Channel Policy</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<div id="container"></div>
+  <script type="application/javascript;version=1.7">
+
+function mainApp() {
+  var audio = new Audio();
+  audio.mozAudioChannelType = 'telephony';
+  audio.src = "audio.ogg";
+  audio.loop = true;
+  audio.play();
+
+  audio.addEventListener('mozinterruptbegin', function() {
+    ok(true, "This element has been muted!");
+  }, false);
+
+  audio.addEventListener('mozinterruptend', function() {
+    ok(true, "This element has been unmuted!");
+    audio.pause();
+    runTest();
+  }, false);
+
+  setTimeout(runTest, 600);
+}
+
+function newApp() {
+  var iframe = document.createElement('iframe');
+  iframe.setAttribute('mozbrowser', true);
+  // That needs to be an app.
+  iframe.setAttribute('mozapp', 'https://acertified.com/manifest.webapp');
+  iframe.src = "file_telephonyPolicy.html";
+  document.body.appendChild(iframe);
+}
+
+var tests = [
+  // Permissions
+  function() {
+    SpecialPowers.pushPermissions(
+      [{ "type": "browser", "allow": 1, "context": document },
+       { "type": "embed-apps", "allow": 1, "context": document },
+       { "type": "webapps-manage", "allow": 1, "context": document },
+       { "type": "audio-channel-telephony", "allow": 1, "context": document }], runTest);
+  },
+
+  // Preferences
+  function() {
+    SpecialPowers.pushPrefEnv({"set": [["dom.ipc.browser_frames.oop_by_default", true],
+                                       ["media.useAudioChannelAPI", true],
+                                       ["media.useAudioChannelService", true],
+                                       ["media.defaultAudioChannel", "telephony"],
+                                       ["dom.mozBrowserFramesEnabled", true],
+                                       ["network.disable.ipc.security", true]]}, runTest);
+  },
+
+  // Run 2 apps
+  mainApp,
+  newApp,
+];
+
+function runTest() {
+  if (!tests.length) {
+    finish();
+    return;
+  }
+
+  var test = tests.shift();
+  test();
+}
+
+function finish() {
+  SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+runTest();
+
+  </script>
+</body>
+</html>
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -2050,17 +2050,16 @@ GK_ATOM(canplaythrough, "canplaythrough"
 GK_ATOM(ratechange, "ratechange")
 GK_ATOM(durationchange, "durationchange")
 GK_ATOM(volumechange, "volumechange")
 GK_ATOM(ondataavailable, "ondataavailable")
 GK_ATOM(onwarning, "onwarning")
 GK_ATOM(onstart, "onstart")
 GK_ATOM(onstop, "onstop")
 GK_ATOM(onphoto, "onphoto")
-GK_ATOM(onactivestatechanged, "onactivestatechanged")
 #ifdef MOZ_GAMEPAD
 GK_ATOM(ongamepadbuttondown, "ongamepadbuttondown")
 GK_ATOM(ongamepadbuttonup, "ongamepadbuttonup")
 GK_ATOM(ongamepadaxismove, "ongamepadaxismove")
 GK_ATOM(ongamepadconnected, "ongamepadconnected")
 GK_ATOM(ongamepaddisconnected, "ongamepaddisconnected")
 #endif
 
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -3730,20 +3730,49 @@ nsPIDOMWindow::SetAudioVolume(float aVol
     return NS_OK;
   }
 
   mAudioVolume = aVolume;
   RefreshMediaElements();
   return NS_OK;
 }
 
+float
+nsPIDOMWindow::GetAudioGlobalVolume()
+{
+  float globalVolume = 1.0;
+  nsCOMPtr<nsPIDOMWindow> window = this;
+
+  do {
+    if (window->GetAudioMuted()) {
+      return 0;
+    }
+
+    globalVolume *= window->GetAudioVolume();
+
+    nsCOMPtr<nsIDOMWindow> win;
+    window->GetParent(getter_AddRefs(win));
+    if (window == win) {
+      break;
+    }
+
+    window = do_QueryInterface(win);
+
+    // If there is not parent, or we are the toplevel or the volume is
+    // already 0.0, we don't continue.
+  } while (window && window != this && globalVolume);
+
+  return globalVolume;
+}
+
 void
 nsPIDOMWindow::RefreshMediaElements()
 {
-  nsRefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
+  nsRefPtr<AudioChannelService> service =
+    AudioChannelService::GetOrCreateAudioChannelService();
   service->RefreshAgentsVolume(GetCurrentInnerWindow());
 }
 
 // nsISpeechSynthesisGetter
 
 #ifdef MOZ_WEBSPEECH
 SpeechSynthesis*
 nsGlobalWindow::GetSpeechSynthesis(ErrorResult& aError)
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -180,16 +180,18 @@ public:
 
   // Audio API
   bool GetAudioMuted() const;
   void SetAudioMuted(bool aMuted);
 
   float GetAudioVolume() const;
   nsresult SetAudioVolume(float aVolume);
 
+  float GetAudioGlobalVolume();
+
   virtual void SetServiceWorkersTestingEnabled(bool aEnabled)
   {
     MOZ_ASSERT(IsOuterWindow());
     mServiceWorkersTestingEnabled = aEnabled;
   }
 
   bool GetServiceWorkersTestingEnabled()
   {
deleted file mode 100644
--- a/dom/browser-element/BrowserElementAudioChannel.cpp
+++ /dev/null
@@ -1,489 +0,0 @@
-/* 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/. */
-
-#include "BrowserElementAudioChannel.h"
-
-#include "mozilla/Services.h"
-#include "mozilla/dom/BrowserElementAudioChannelBinding.h"
-#include "mozilla/dom/DOMRequest.h"
-#include "mozilla/dom/ToJSValue.h"
-#include "AudioChannelService.h"
-#include "nsIBrowserElementAPI.h"
-#include "nsIDocShell.h"
-#include "nsIDOMDOMRequest.h"
-#include "nsIObserverService.h"
-#include "nsISupportsPrimitives.h"
-#include "nsITabParent.h"
-#include "nsPIDOMWindow.h"
-
-namespace {
-
-void
-AssertIsInMainProcess()
-{
-  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
-}
-
-} // anonymous namespace
-
-namespace mozilla {
-namespace dom {
-
-NS_IMPL_ADDREF_INHERITED(BrowserElementAudioChannel, DOMEventTargetHelper)
-NS_IMPL_RELEASE_INHERITED(BrowserElementAudioChannel, DOMEventTargetHelper)
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(BrowserElementAudioChannel)
-  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
-  NS_INTERFACE_MAP_ENTRY(nsIObserver)
-NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
-
-NS_IMPL_CYCLE_COLLECTION_INHERITED(BrowserElementAudioChannel,
-                                   DOMEventTargetHelper,
-                                   mFrameLoader,
-                                   mFrameWindow,
-                                   mTabParent,
-                                   mBrowserElementAPI)
-
-BrowserElementAudioChannel::BrowserElementAudioChannel(
-                                                   nsPIDOMWindow* aWindow,
-                                                   nsIFrameLoader* aFrameLoader,
-                                                   nsIBrowserElementAPI* aAPI,
-                                                   AudioChannel aAudioChannel)
-  : DOMEventTargetHelper(aWindow)
-  , mFrameLoader(aFrameLoader)
-  , mBrowserElementAPI(aAPI)
-  , mAudioChannel(aAudioChannel)
-  , mState(eStateUnknown)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  AssertIsInMainProcess();
-  MOZ_ASSERT(mFrameLoader);
-
-  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
-  if (obs) {
-    nsAutoString name;
-    AudioChannelService::GetAudioChannelString(aAudioChannel, name);
-
-    nsAutoCString topic;
-    topic.Assign("audiochannel-activity-");
-    topic.Append(NS_ConvertUTF16toUTF8(name));
-
-    obs->AddObserver(this, topic.get(), true);
-  }
-}
-
-BrowserElementAudioChannel::~BrowserElementAudioChannel()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  AssertIsInMainProcess();
-
-  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
-  if (obs) {
-    nsAutoString name;
-    AudioChannelService::GetAudioChannelString(mAudioChannel, name);
-
-    nsAutoCString topic;
-    topic.Assign("audiochannel-activity-");
-    topic.Append(NS_ConvertUTF16toUTF8(name));
-
-    obs->RemoveObserver(this, topic.get());
-  }
-}
-
-nsresult
-BrowserElementAudioChannel::Initialize()
-{
-  nsCOMPtr<nsIDocShell> docShell;
-  nsresult rv = mFrameLoader->GetDocShell(getter_AddRefs(docShell));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  if (docShell) {
-    nsCOMPtr<nsPIDOMWindow> window = docShell->GetWindow();
-    if (!window) {
-      return NS_ERROR_FAILURE;
-    }
-
-    nsCOMPtr<nsIDOMWindow> topWindow;
-    window->GetScriptableTop(getter_AddRefs(topWindow));
-
-    mFrameWindow = do_QueryInterface(topWindow);
-    mFrameWindow = mFrameWindow->GetOuterWindow();
-    return NS_OK;
-  }
-
-  rv = mFrameLoader->GetTabParent(getter_AddRefs(mTabParent));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  MOZ_ASSERT(mTabParent);
-  return NS_OK;
-}
-
-JSObject*
-BrowserElementAudioChannel::WrapObject(JSContext *aCx,
-                                       JS::Handle<JSObject*> aGivenProto)
-{
-  return BrowserElementAudioChannelBinding::Wrap(aCx, this, aGivenProto);
-}
-
-AudioChannel
-BrowserElementAudioChannel::Name() const
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  AssertIsInMainProcess();
-
-  return mAudioChannel;
-}
-
-namespace {
-
-class BaseRunnable : public nsRunnable
-{
-protected:
-  nsCOMPtr<nsPIDOMWindow> mParentWindow;
-  nsCOMPtr<nsPIDOMWindow> mFrameWindow;
-  nsRefPtr<DOMRequest> mRequest;
-  AudioChannel mAudioChannel;
-
-  virtual void DoWork(AudioChannelService* aService,
-                      JSContext* aCx) = 0;
-
-public:
-  BaseRunnable(nsPIDOMWindow* aParentWindow, nsPIDOMWindow* aFrameWindow,
-               DOMRequest* aRequest, AudioChannel aAudioChannel)
-    : mParentWindow(aParentWindow)
-    , mFrameWindow(aFrameWindow)
-    , mRequest(aRequest)
-    , mAudioChannel(aAudioChannel)
-  {}
-
-  NS_IMETHODIMP Run() override
-  {
-    nsRefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
-    MOZ_ASSERT(service);
-
-    AutoJSAPI jsapi;
-    if (!jsapi.Init(mParentWindow)) {
-      mRequest->FireError(NS_ERROR_FAILURE);
-      return NS_OK;
-    }
-
-    DoWork(service, jsapi.cx());
-    return NS_OK;
-  }
-};
-
-class GetVolumeRunnable final : public BaseRunnable
-{
-public:
-  GetVolumeRunnable(nsPIDOMWindow* aParentWindow, nsPIDOMWindow* aFrameWindow,
-                    DOMRequest* aRequest, AudioChannel aAudioChannel)
-    : BaseRunnable(aParentWindow, aFrameWindow, aRequest, aAudioChannel)
-  {}
-
-protected:
-  virtual void DoWork(AudioChannelService* aService, JSContext* aCx) override
-  {
-    float volume = aService->GetAudioChannelVolume(mFrameWindow, mAudioChannel);
-
-    JS::Rooted<JS::Value> value(aCx);
-    if (!ToJSValue(aCx, volume, &value)) {
-      mRequest->FireError(NS_ERROR_FAILURE);
-      return;
-    }
-
-    mRequest->FireSuccess(value);
-  }
-};
-
-class GetMutedRunnable final : public BaseRunnable
-{
-public:
-  GetMutedRunnable(nsPIDOMWindow* aParentWindow, nsPIDOMWindow* aFrameWindow,
-                   DOMRequest* aRequest, AudioChannel aAudioChannel)
-    : BaseRunnable(aParentWindow, aFrameWindow, aRequest, aAudioChannel)
-  {}
-
-protected:
-  virtual void DoWork(AudioChannelService* aService, JSContext* aCx) override
-  {
-    bool muted = aService->GetAudioChannelMuted(mFrameWindow, mAudioChannel);
-
-    JS::Rooted<JS::Value> value(aCx);
-    if (!ToJSValue(aCx, muted, &value)) {
-      mRequest->FireError(NS_ERROR_FAILURE);
-      return;
-    }
-
-    mRequest->FireSuccess(value);
-  }
-};
-
-class IsActiveRunnable final : public BaseRunnable
-{
-  bool mActive;
-  bool mValueKnown;
-
-public:
-  IsActiveRunnable(nsPIDOMWindow* aParentWindow, nsPIDOMWindow* aFrameWindow,
-                   DOMRequest* aRequest, AudioChannel aAudioChannel,
-                   bool aActive)
-    : BaseRunnable(aParentWindow, aFrameWindow, aRequest, aAudioChannel)
-    , mActive(aActive)
-    , mValueKnown(true)
-  {}
-
-  IsActiveRunnable(nsPIDOMWindow* aParentWindow, nsPIDOMWindow* aFrameWindow,
-                   DOMRequest* aRequest, AudioChannel aAudioChannel)
-    : BaseRunnable(aParentWindow, aFrameWindow, aRequest, aAudioChannel)
-    , mActive(true)
-    , mValueKnown(false)
-  {}
-
-protected:
-  virtual void DoWork(AudioChannelService* aService, JSContext* aCx) override
-  {
-    if (!mValueKnown) {
-      mActive = aService->IsAudioChannelActive(mFrameWindow, mAudioChannel);
-    }
-
-    JS::Rooted<JS::Value> value(aCx);
-    if (!ToJSValue(aCx, mActive, &value)) {
-      mRequest->FireError(NS_ERROR_FAILURE);
-      return;
-    }
-
-    mRequest->FireSuccess(value);
-  }
-};
-
-class FireSuccessRunnable final : public BaseRunnable
-{
-public:
-  FireSuccessRunnable(nsPIDOMWindow* aParentWindow, nsPIDOMWindow* aFrameWindow,
-                      DOMRequest* aRequest, AudioChannel aAudioChannel)
-    : BaseRunnable(aParentWindow, aFrameWindow, aRequest, aAudioChannel)
-  {}
-
-protected:
-  virtual void DoWork(AudioChannelService* aService, JSContext* aCx) override
-  {
-    JS::Rooted<JS::Value> value(aCx);
-    mRequest->FireSuccess(value);
-  }
-};
-
-} // anonymous namespace
-
-already_AddRefed<dom::DOMRequest>
-BrowserElementAudioChannel::GetVolume(ErrorResult& aRv)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  AssertIsInMainProcess();
-
-  if (!mFrameWindow) {
-    nsCOMPtr<nsIDOMDOMRequest> request;
-    aRv = mBrowserElementAPI->GetAudioChannelVolume((uint32_t)mAudioChannel,
-                                                    getter_AddRefs(request));
-    if (NS_WARN_IF(aRv.Failed())) {
-      return nullptr;
-    }
-
-    return request.forget().downcast<DOMRequest>();
-  }
-
-  nsRefPtr<DOMRequest> domRequest = new DOMRequest(GetOwner());
-
-  nsCOMPtr<nsIRunnable> runnable =
-    new GetVolumeRunnable(GetOwner(), mFrameWindow, domRequest, mAudioChannel);
-  NS_DispatchToMainThread(runnable);
-
-  return domRequest.forget();
-}
-
-already_AddRefed<dom::DOMRequest>
-BrowserElementAudioChannel::SetVolume(float aVolume, ErrorResult& aRv)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  AssertIsInMainProcess();
-
-  if (!mFrameWindow) {
-    nsCOMPtr<nsIDOMDOMRequest> request;
-    aRv = mBrowserElementAPI->SetAudioChannelVolume((uint32_t)mAudioChannel,
-                                                    aVolume,
-                                                    getter_AddRefs(request));
-    if (NS_WARN_IF(aRv.Failed())) {
-      return nullptr;
-    }
-
-    return request.forget().downcast<DOMRequest>();
-  }
-
-  nsRefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
-  MOZ_ASSERT(service);
-
-  service->SetAudioChannelVolume(mFrameWindow, mAudioChannel, aVolume);
-
-  nsRefPtr<DOMRequest> domRequest = new DOMRequest(GetOwner());
-  nsCOMPtr<nsIRunnable> runnable = new FireSuccessRunnable(GetOwner(),
-                                                           mFrameWindow,
-                                                           domRequest,
-                                                           mAudioChannel);
-  NS_DispatchToMainThread(runnable);
-
-  return domRequest.forget();
-}
-
-already_AddRefed<dom::DOMRequest>
-BrowserElementAudioChannel::GetMuted(ErrorResult& aRv)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  AssertIsInMainProcess();
-
-  if (!mFrameWindow) {
-    nsCOMPtr<nsIDOMDOMRequest> request;
-    aRv = mBrowserElementAPI->GetAudioChannelMuted((uint32_t)mAudioChannel,
-                                                   getter_AddRefs(request));
-    if (NS_WARN_IF(aRv.Failed())) {
-      return nullptr;
-    }
-
-    return request.forget().downcast<DOMRequest>();
-  }
-
-  nsRefPtr<DOMRequest> domRequest = new DOMRequest(GetOwner());
-
-  nsCOMPtr<nsIRunnable> runnable =
-    new GetMutedRunnable(GetOwner(), mFrameWindow, domRequest, mAudioChannel);
-  NS_DispatchToMainThread(runnable);
-
-  return domRequest.forget();
-}
-
-already_AddRefed<dom::DOMRequest>
-BrowserElementAudioChannel::SetMuted(bool aMuted, ErrorResult& aRv)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  AssertIsInMainProcess();
-
-  if (!mFrameWindow) {
-    nsCOMPtr<nsIDOMDOMRequest> request;
-    aRv = mBrowserElementAPI->SetAudioChannelMuted((uint32_t)mAudioChannel,
-                                                   aMuted,
-                                                   getter_AddRefs(request));
-    if (NS_WARN_IF(aRv.Failed())) {
-      return nullptr;
-    }
-
-    return request.forget().downcast<DOMRequest>();
-  }
-
-  nsRefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
-  MOZ_ASSERT(service);
-
-  service->SetAudioChannelMuted(mFrameWindow, mAudioChannel, aMuted);
-
-  nsRefPtr<DOMRequest> domRequest = new DOMRequest(GetOwner());
-  nsCOMPtr<nsIRunnable> runnable = new FireSuccessRunnable(GetOwner(),
-                                                           mFrameWindow,
-                                                           domRequest,
-                                                           mAudioChannel);
-  NS_DispatchToMainThread(runnable);
-
-  return domRequest.forget();
-}
-
-already_AddRefed<dom::DOMRequest>
-BrowserElementAudioChannel::IsActive(ErrorResult& aRv)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  AssertIsInMainProcess();
-
-  if (mState != eStateUnknown) {
-    nsRefPtr<DOMRequest> domRequest = new DOMRequest(GetOwner());
-
-    nsCOMPtr<nsIRunnable> runnable =
-      new IsActiveRunnable(GetOwner(), mFrameWindow, domRequest, mAudioChannel,
-                           mState == eStateActive);
-    NS_DispatchToMainThread(runnable);
-
-    return domRequest.forget();
-  }
-
-  if (!mFrameWindow) {
-    nsCOMPtr<nsIDOMDOMRequest> request;
-    aRv = mBrowserElementAPI->IsAudioChannelActive((uint32_t)mAudioChannel,
-                                                   getter_AddRefs(request));
-    if (NS_WARN_IF(aRv.Failed())) {
-      return nullptr;
-    }
-
-    return request.forget().downcast<DOMRequest>();
-  }
-
-  nsRefPtr<DOMRequest> domRequest = new DOMRequest(GetOwner());
-
-  nsCOMPtr<nsIRunnable> runnable =
-    new IsActiveRunnable(GetOwner(), mFrameWindow, domRequest, mAudioChannel);
-  NS_DispatchToMainThread(runnable);
-
-  return domRequest.forget();
-}
-
-NS_IMETHODIMP
-BrowserElementAudioChannel::Observe(nsISupports* aSubject, const char* aTopic,
-                                    const char16_t* aData)
-{
-  nsAutoString name;
-  AudioChannelService::GetAudioChannelString(mAudioChannel, name);
-
-  nsAutoCString topic;
-  topic.Assign("audiochannel-activity-");
-  topic.Append(NS_ConvertUTF16toUTF8(name));
-
-  if (strcmp(topic.get(), aTopic)) {
-    return NS_OK;
-  }
-
-  // Message received from the child.
-  if (!mFrameWindow) {
-    if (mTabParent == aSubject) {
-      ProcessStateChanged(aData);
-    }
-
-    return NS_OK;
-  }
-
-  nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
-  if (NS_WARN_IF(!wrapper)) {
-    return NS_ERROR_FAILURE;
-  }
-
-  uint64_t windowID;
-  nsresult rv = wrapper->GetData(&windowID);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  if (windowID != mFrameWindow->WindowID()) {
-    return NS_OK;
-  }
-
-  ProcessStateChanged(aData);
-  return NS_OK;
-}
-
-void
-BrowserElementAudioChannel::ProcessStateChanged(const char16_t* aData)
-{
-  nsAutoString value(aData);
-  mState = value.EqualsASCII("active") ? eStateActive : eStateInactive;
-  DispatchTrustedEvent(NS_LITERAL_STRING("activestatechanged"));
-}
-
-} // dom namespace
-} // mozilla namespace
deleted file mode 100644
--- a/dom/browser-element/BrowserElementAudioChannel.h
+++ /dev/null
@@ -1,83 +0,0 @@
-/* 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 mozilla_dom_BrowserElementAudioChannels_h
-#define mozilla_dom_BrowserElementAudioChannels_h
-
-#include "mozilla/dom/AudioChannelBinding.h"
-#include "mozilla/dom/BindingDeclarations.h"
-#include "mozilla/DOMEventTargetHelper.h"
-#include "mozilla/ErrorResult.h"
-#include "nsCycleCollectionParticipant.h"
-#include "nsIObserver.h"
-#include "nsIFrameLoader.h"
-#include "nsWeakReference.h"
-#include "nsWrapperCache.h"
-
-class nsIBrowserElementAPI;
-class nsITabParent;
-class nsPIDOMWindow;
-
-namespace mozilla {
-namespace dom {
-
-class DOMRequest;
-
-class BrowserElementAudioChannel final : public DOMEventTargetHelper
-                                       , public nsSupportsWeakReference
-                                       , public nsIObserver
-{
-public:
-  NS_DECL_ISUPPORTS_INHERITED
-  NS_DECL_NSIOBSERVER
-
-  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(BrowserElementAudioChannel,
-                                           DOMEventTargetHelper)
-
-  BrowserElementAudioChannel(nsPIDOMWindow* aWindow,
-                             nsIFrameLoader* aFrameLoader,
-                             nsIBrowserElementAPI* aAPI,
-                             AudioChannel aAudioChannel);
-
-  nsresult Initialize();
-
-  // WebIDL methods
-
-  virtual JSObject* WrapObject(JSContext *aCx,
-                               JS::Handle<JSObject*> aGivenProto) override;
-
-  AudioChannel Name() const;
-
-  already_AddRefed<dom::DOMRequest> GetVolume(ErrorResult& aRv);
-  already_AddRefed<dom::DOMRequest> SetVolume(float aVolume, ErrorResult& aRv);
-
-  already_AddRefed<dom::DOMRequest> GetMuted(ErrorResult& aRv);
-  already_AddRefed<dom::DOMRequest> SetMuted(bool aMuted, ErrorResult& aRv);
-
-  already_AddRefed<dom::DOMRequest> IsActive(ErrorResult& aRv);
-
-  IMPL_EVENT_HANDLER(activestatechanged);
-
-private:
-  ~BrowserElementAudioChannel();
-
-  void ProcessStateChanged(const char16_t* aData);
-
-  nsCOMPtr<nsIFrameLoader> mFrameLoader;
-  nsCOMPtr<nsIBrowserElementAPI> mBrowserElementAPI;
-  nsCOMPtr<nsITabParent> mTabParent;
-  nsCOMPtr<nsPIDOMWindow> mFrameWindow;
-  AudioChannel mAudioChannel;
-
-  enum {
-    eStateActive,
-    eStateInactive,
-    eStateUnknown
-  } mState;
-};
-
-} // dom namespace
-} // mozilla namespace
-
-#endif // mozilla_dom_BrowserElementAudioChannels_h
--- a/dom/browser-element/BrowserElementChildPreload.js
+++ b/dom/browser-element/BrowserElementChildPreload.js
@@ -8,20 +8,16 @@ dump("######################## BrowserEl
 
 var BrowserElementIsReady = false;
 
 let { classes: Cc, interfaces: Ci, results: Cr, utils: Cu }  = Components;
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/BrowserElementPromptService.jsm");
 
-XPCOMUtils.defineLazyServiceGetter(this, "acs",
-                                   "@mozilla.org/audiochannel/service;1",
-                                   "nsIAudioChannelService");
-
 let kLongestReturnedString = 128;
 
 function debug(msg) {
   //dump("BrowserElementChildPreload - " + msg + "\n");
 }
 
 function sendAsyncMsg(msg, data) {
   // Ensure that we don't send any messages before BrowserElementChild.js
@@ -233,21 +229,16 @@ BrowserElementChild.prototype = {
       "exit-fullscreen": this._recvExitFullscreen.bind(this),
       "activate-next-paint-listener": this._activateNextPaintListener.bind(this),
       "set-input-method-active": this._recvSetInputMethodActive.bind(this),
       "deactivate-next-paint-listener": this._deactivateNextPaintListener.bind(this),
       "do-command": this._recvDoCommand,
       "find-all": this._recvFindAll.bind(this),
       "find-next": this._recvFindNext.bind(this),
       "clear-match": this._recvClearMatch.bind(this),
-      "get-audio-channel-volume": this._recvGetAudioChannelVolume,
-      "set-audio-channel-volume": this._recvSetAudioChannelVolume,
-      "get-audio-channel-muted": this._recvGetAudioChannelMuted,
-      "set-audio-channel-muted": this._recvSetAudioChannelMuted,
-      "get-is-audio-channel-active": this._recvIsAudioChannelActive
     }
 
     addMessageListener("browser-element-api:call", function(aMessage) {
       if (aMessage.data.msg_name in mmCalls) {
         return mmCalls[aMessage.data.msg_name].apply(self, arguments);
       }
     });
 
@@ -1250,65 +1241,16 @@ BrowserElementChild.prototype = {
 
   _recvDoCommand: function(data) {
     if (this._isCommandEnabled(data.json.command)) {
       this._selectionStateChangedTarget = null;
       docShell.doCommand(COMMAND_MAP[data.json.command]);
     }
   },
 
-  _recvGetAudioChannelVolume: function(data) {
-    debug("Received getAudioChannelVolume message: (" + data.json.id + ")");
-
-    let volume = acs.getAudioChannelVolume(content,
-                                           data.json.args.audioChannel);
-    sendAsyncMsg('got-audio-channel-volume', {
-      id: data.json.id, successRv: volume
-    });
-  },
-
-  _recvSetAudioChannelVolume: function(data) {
-    debug("Received setAudioChannelVolume message: (" + data.json.id + ")");
-
-    acs.setAudioChannelVolume(content,
-                              data.json.args.audioChannel,
-                              data.json.args.volume);
-    sendAsyncMsg('got-set-audio-channel-volume', {
-      id: data.json.id, successRv: true
-    });
-  },
-
-  _recvGetAudioChannelMuted: function(data) {
-    debug("Received getAudioChannelMuted message: (" + data.json.id + ")");
-
-    let muted = acs.getAudioChannelMuted(content, data.json.args.audioChannel);
-    sendAsyncMsg('got-audio-channel-muted', {
-      id: data.json.id, successRv: muted
-    });
-  },
-
-  _recvSetAudioChannelMuted: function(data) {
-    debug("Received setAudioChannelMuted message: (" + data.json.id + ")");
-
-    acs.setAudioChannelMuted(content, data.json.args.audioChannel,
-                             data.json.args.muted);
-    sendAsyncMsg('got-set-audio-channel-muted', {
-      id: data.json.id, successRv: true
-    });
-  },
-
-  _recvIsAudioChannelActive: function(data) {
-    debug("Received isAudioChannelActive message: (" + data.json.id + ")");
-
-    let active = acs.isAudioChannelActive(content, data.json.args.audioChannel);
-    sendAsyncMsg('got-is-audio-channel-active', {
-      id: data.json.id, successRv: active
-    });
-  },
-
   _initFinder: function() {
     if (!this._finder) {
       try {
         this._findLimit = Services.prefs.getIntPref("accessibility.typeaheadfind.matchesCountLimit");
       } catch (e) {
         // Pref not available, assume 0, no match counting.
         this._findLimit = 0;
       }
--- a/dom/browser-element/BrowserElementParent.cpp
+++ b/dom/browser-element/BrowserElementParent.cpp
@@ -10,17 +10,16 @@
 //   #define CreateEvent CreateEventW
 // That messes up our call to EventDispatcher::CreateEvent below.
 
 #ifdef CreateEvent
 #undef CreateEvent
 #endif
 
 #include "BrowserElementParent.h"
-#include "BrowserElementAudioChannel.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/dom/HTMLIFrameElement.h"
 #include "mozilla/dom/ToJSValue.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsVariant.h"
 #include "mozilla/dom/BrowserElementDictionariesBinding.h"
 #include "mozilla/dom/CustomEvent.h"
 
--- a/dom/browser-element/BrowserElementParent.js
+++ b/dom/browser-element/BrowserElementParent.js
@@ -201,22 +201,17 @@ BrowserElementParent.prototype = {
       "fullscreen-origin-change": this._fullscreenOriginChange,
       "exit-dom-fullscreen": this._exitDomFullscreen,
       "got-visible": this._gotDOMRequestResult,
       "visibilitychange": this._childVisibilityChange,
       "got-set-input-method-active": this._gotDOMRequestResult,
       "selectionstatechanged": this._handleSelectionStateChanged,
       "scrollviewchange": this._handleScrollViewChange,
       "caretstatechanged": this._handleCaretStateChanged,
-      "findchange": this._handleFindChange,
-      "got-audio-channel-volume": this._gotDOMRequestResult,
-      "got-set-audio-channel-volume": this._gotDOMRequestResult,
-      "got-audio-channel-muted": this._gotDOMRequestResult,
-      "got-set-audio-channel-muted": this._gotDOMRequestResult,
-      "got-is-audio-channel-active": this._gotDOMRequestResult
+      "findchange": this._handleFindChange
     };
 
     let mmSecuritySensitiveCalls = {
       "mediaplaybackchange": this._fireEventFromMsg,
       "showmodalprompt": this._handleShowModalPrompt,
       "contextmenu": this._fireCtxMenuEvent,
       "securitychange": this._fireEventFromMsg,
       "locationchange": this._fireEventFromMsg,
@@ -973,43 +968,16 @@ BrowserElementParent.prototype = {
       let nfcContentHelper =
         Cc["@mozilla.org/nfc/content-helper;1"].getService(Ci.nsINfcBrowserAPI);
       nfcContentHelper.setFocusApp(tabId, isFocus);
     } catch(e) {
       // Not all platforms support NFC
     }
   },
 
-  getAudioChannelVolume: function(aAudioChannel) {
-    return this._sendDOMRequest('get-audio-channel-volume',
-                                {audioChannel: aAudioChannel});
-  },
-
-  setAudioChannelVolume: function(aAudioChannel, aVolume) {
-    return this._sendDOMRequest('set-audio-channel-volume',
-                                {audioChannel: aAudioChannel,
-                                 volume: aVolume});
-  },
-
-  getAudioChannelMuted: function(aAudioChannel) {
-    return this._sendDOMRequest('get-audio-channel-muted',
-                                {audioChannel: aAudioChannel});
-  },
-
-  setAudioChannelMuted: function(aAudioChannel, aMuted) {
-    return this._sendDOMRequest('set-audio-channel-muted',
-                                {audioChannel: aAudioChannel,
-                                 muted: aMuted});
-  },
-
-  isAudioChannelActive: function(aAudioChannel) {
-    return this._sendDOMRequest('get-is-audio-channel-active',
-                                {audioChannel: aAudioChannel});
-  },
-
   /**
    * Called when the visibility of the window which owns this iframe changes.
    */
   _ownerVisibilityChange: function() {
     this._sendAsyncMsg('owner-visibility-change',
                        {visible: !this._window.document.hidden});
   },
 
deleted file mode 100644
--- a/dom/browser-element/mochitest/browserElement_AudioChannel.js
+++ /dev/null
@@ -1,153 +0,0 @@
-/* Any copyright is dedicated to the public domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-// Bug 1113086 - tests for AudioChannel API into BrowserElement
-
-"use strict";
-
-SimpleTest.waitForExplicitFinish();
-browserElementTestHelpers.setEnabledPref(true);
-browserElementTestHelpers.addPermission();
-
-SpecialPowers.setBoolPref("dom.testing.browserElementAudioChannel.noapp", true);
-SpecialPowers.setBoolPref("media.useAudioChannelService", true);
-
-function noaudio() {
-  var iframe = document.createElement('iframe');
-  iframe.setAttribute('mozbrowser', 'true');
-  iframe.setAttribute('mozapp', 'http://example.org/manifest.webapp');
-  iframe.src = 'http://example.org/tests/dom/browser-element/mochitest/file_empty.html';
-
-  function noaudio_loadend() {
-    ok("allowedAudioChannels" in iframe, "allowedAudioChannels exist");
-    var channels = iframe.allowedAudioChannels;
-    is(channels.length, 1, "1 audio channel by default");
-
-    var ac = channels[0];
-
-    ok(ac instanceof BrowserElementAudioChannel, "Correct class");
-    ok("getVolume" in ac, "ac.getVolume exists");
-    ok("setVolume" in ac, "ac.setVolume exists");
-    ok("getMuted" in ac, "ac.getMuted exists");
-    ok("setMuted" in ac, "ac.setMuted exists");
-    ok("isActive" in ac, "ac.isActive exists");
-
-    new Promise(function(r, rr) {
-      var req = ac.getVolume();
-      ok(req instanceof DOMRequest, "This is a domRequest.");
-      req.onsuccess = function(e) {
-        is(e.target.result, 1.0, "The default volume should be 1.0");
-        r();
-      }
-    })
-
-    .then(function() {
-      return new Promise(function(r, rr) {
-        ac.getMuted().onsuccess = function(e) {
-          is(e.target.result, false, "The default muted value should be false");
-          r();
-        }
-      });
-    })
-
-    .then(function() {
-      return new Promise(function(r, rr) {
-        ac.setVolume(0.8).onsuccess = function() { r(); }
-      });
-    })
-
-    .then(function() {
-      return new Promise(function(r, rr) {
-        ac.getVolume().onsuccess = function(e) {
-          // the actual value is 0.800000011920929..
-          ok(Math.abs(0.8 - e.target.result) < 0.01, "The new volume should be 0.8: " + e.target.result);
-          r();
-        }
-      });
-    })
-
-    .then(function() {
-      return new Promise(function(r, rr) {
-        ac.setVolume(1.0).onsuccess = function() { r(); }
-      });
-    })
-
-    .then(function() {
-      return new Promise(function(r, rr) {
-        ac.setMuted(true).onsuccess = function() { r(); }
-      });
-    })
-
-    .then(function() {
-      return new Promise(function(r, rr) {
-        ac.getMuted().onsuccess = function(e) {
-          is(e.target.result, true, "The new muted value should be true");
-          r();
-        }
-      });
-    })
-
-    .then(function() {
-      return new Promise(function(r, rr) {
-        ac.isActive().onsuccess = function(e) {
-          is(e.target.result, false, "ac.isActive is false: no audio element active.");
-          r();
-        }
-      });
-    })
-
-    .then(runTests);
-  }
-
-  iframe.addEventListener('mozbrowserloadend', noaudio_loadend);
-  document.body.appendChild(iframe);
-}
-
-function audio() {
-  var iframe = document.createElement('iframe');
-  iframe.setAttribute('mozbrowser', 'true');
-  iframe.setAttribute('mozapp', 'http://example.org/manifest.webapp');
-  iframe.src = 'http://example.org/tests/dom/browser-element/mochitest/iframe_file_audio.html';
-
-  function audio_loadend() {
-    ok("allowedAudioChannels" in iframe, "allowedAudioChannels exist");
-    var channels = iframe.allowedAudioChannels;
-    is(channels.length, 1, "1 audio channel by default");
-
-    var ac = channels[0];
-
-    ok(ac instanceof BrowserElementAudioChannel, "Correct class");
-    ok("getVolume" in ac, "ac.getVolume exists");
-    ok("setVolume" in ac, "ac.setVolume exists");
-    ok("getMuted" in ac, "ac.getMuted exists");
-    ok("setMuted" in ac, "ac.setMuted exists");
-    ok("isActive" in ac, "ac.isActive exists");
-
-    ac.onactivestatechanged = function() {
-      ok("activestatechanged event received.");
-      ac.onactivestatechanged = null;
-      runTests();
-    }
-  }
-
-  iframe.addEventListener('mozbrowserloadend', audio_loadend);
-  document.body.appendChild(iframe);
-}
-
-var tests = [ noaudio, audio ];
-
-function runTests() {
-  if (tests.length == 0) {
-    SimpleTest.finish();
-    return;
-  }
-
-  var test = tests.shift();
-  test();
-}
-
-
-addEventListener('load', function() {
-  SimpleTest.executeSoon(runTests);
-});
-
deleted file mode 100644
--- a/dom/browser-element/mochitest/file_audio.html
+++ /dev/null
@@ -1,15 +0,0 @@
-<html>
-<body>
-<audio src="http://mochi.test:8888/tests/dom/browser-element/mochitest/audio.ogg" id="audio" />
-<script>
-var audio = document.getElementById('audio');
-audio.play();
-audio.onended = function() {
-  setTimeout(function() {
-    audio.play();
-  }, 0);
-}
-</script>
-
-</body>
-</html>
deleted file mode 100644
--- a/dom/browser-element/mochitest/iframe_file_audio.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<html>
-<body>
-<iframe src="file_audio.html"></iframe>
-</body>
-</html>
--- a/dom/browser-element/mochitest/mochitest-oop.ini
+++ b/dom/browser-element/mochitest/mochitest-oop.ini
@@ -104,9 +104,8 @@ disabled = bug 930449
 # Disabled until bug 924771 makes them stop timing out
 [test_browserElement_oop_CloseFromOpener.html]
 disabled = bug 924771
 [test_browserElement_oop_CloseApp.html]
 disabled = bug 924771
 [test_browserElement_oop_ExposableURI.html]
 disabled = bug 924771
 [test_browserElement_oop_GetContentDimensions.html]
-[test_browserElement_oop_AudioChannel.html]
--- a/dom/browser-element/mochitest/mochitest.ini
+++ b/dom/browser-element/mochitest/mochitest.ini
@@ -70,17 +70,16 @@ support-files =
   browserElement_TopBarrier.js
   browserElement_VisibilityChange.js
   browserElement_XFrameOptions.js
   browserElement_XFrameOptionsAllowFrom.js
   browserElement_XFrameOptionsDeny.js
   browserElement_XFrameOptionsSameOrigin.js
   browserElement_XFrameOptionsSameOrigin.js
   browserElement_GetContentDimensions.js
-  browserElement_AudioChannel.js
   file_browserElement_AlertInFrame.html
   file_browserElement_AlertInFrame_Inner.html
   file_browserElement_AllowEmbedAppsInNestedOOIframe.html
   file_browserElement_AppFramePermission.html
   file_browserElement_AppWindowNamespace.html
   file_browserElement_Viewmode.html
   file_browserElement_ThemeColor.html
   file_browserElement_BrowserWindowNamespace.html
@@ -118,18 +117,16 @@ support-files =
   file_empty.html
   file_empty_script.js
   file_focus.html
   file_http_401_response.sjs
   file_http_407_response.sjs
   file_inputmethod.html
   file_post_request.html
   file_wyciwyg.html
-  file_audio.html
-  iframe_file_audio.html
 
 # Note: browserElementTestHelpers.js looks at the test's filename to determine
 # whether the test should be OOP.  "_oop_" signals OOP, "_inproc_" signals in
 # process.  Default is OOP.
 [test_browserElement_NoAttr.html]
 [test_browserElement_NoPref.html]
 [test_browserElement_NoPermission.html]
 [test_browserElement_inproc_Alert.html]
@@ -221,9 +218,8 @@ skip-if = (toolkit == 'gonk' && !debug)
 [test_browserElement_inproc_XFrameOptionsDeny.html]
 [test_browserElement_inproc_XFrameOptionsSameOrigin.html]
 [test_browserElement_oop_NextPaint.html]
 skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 936226
 # Disabled due to https://bugzilla.mozilla.org/show_bug.cgi?id=774100
 [test_browserElement_inproc_Reload.html]
 disabled = bug 774100
 [test_browserElement_inproc_GetContentDimensions.html]
-[test_browserElement_inproc_AudioChannel.html]
deleted file mode 100644
--- a/dom/browser-element/mochitest/test_browserElement_inproc_AudioChannel.html
+++ /dev/null
@@ -1,13 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Test of browser element audioChannel.</title>
-  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="application/javascript" src="browserElementTestHelpers.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-<body>
-<script type="application/javascript;version=1.7" src="browserElement_AudioChannel.js">
-</script>
-</body>
-</html>
--- a/dom/browser-element/moz.build
+++ b/dom/browser-element/moz.build
@@ -3,22 +3,17 @@
 # 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/.
 
 EXPORTS.mozilla += [
     'BrowserElementParent.h',
 ]
 
-EXPORTS.mozilla.dom += [
-    'BrowserElementAudioChannel.h',
-]
-
 SOURCES += [
-    'BrowserElementAudioChannel.cpp',
     'BrowserElementParent.cpp',
 ]
 
 XPIDL_SOURCES += [
     'nsIBrowserElementAPI.idl',
 ]
 
 XPIDL_MODULE = 'browser-element'
--- a/dom/browser-element/nsIBrowserElementAPI.idl
+++ b/dom/browser-element/nsIBrowserElementAPI.idl
@@ -21,17 +21,17 @@ interface nsIBrowserElementNextPaintList
     { 0x651db7e3, 0x1734, 0x4536,                               \
       { 0xb1, 0x5a, 0x5b, 0x3a, 0xe6, 0x44, 0x13, 0x4c } }
 %}
 
 /**
  * Interface to the BrowserElementParent implementation. All methods
  * but setFrameLoader throw when the remote process is dead.
  */
-[scriptable, uuid(daa264b2-54df-4fc7-89b7-c9d02167c5d4)]
+[scriptable, uuid(8ecb598c-f886-11e4-9915-778f934fbf93)]
 interface nsIBrowserElementAPI : nsISupports
 {
   const long FIND_CASE_SENSITIVE = 0;
   const long FIND_CASE_INSENSITIVE = 1;
 
   const long FIND_FORWARD = 0;
   const long FIND_BACKWARD = 1;
 
@@ -77,18 +77,10 @@ interface nsIBrowserElementAPI : nsISupp
   void findNext(in long direction);
   void clearMatch();
 
   void addNextPaintListener(in nsIBrowserElementNextPaintListener listener);
   void removeNextPaintListener(in nsIBrowserElementNextPaintListener listener);
 
   nsIDOMDOMRequest setInputMethodActive(in boolean isActive);
 
-  nsIDOMDOMRequest getAudioChannelVolume(in uint32_t audioChannel);
-  nsIDOMDOMRequest setAudioChannelVolume(in uint32_t audioChannel, in float volume);
-
-  nsIDOMDOMRequest getAudioChannelMuted(in uint32_t audioChannel);
-  nsIDOMDOMRequest setAudioChannelMuted(in uint32_t audioChannel, in bool muted);
-
-  nsIDOMDOMRequest isAudioChannelActive(in uint32_t audioChannel);
-
   void setNFCFocus(in boolean isFocus);
 };
--- a/dom/camera/DOMCameraControl.cpp
+++ b/dom/camera/DOMCameraControl.cpp
@@ -1130,22 +1130,18 @@ nsDOMCameraControl::NotifyRecordingStatu
     mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1");
     if (!mAudioChannelAgent) {
       return NS_ERROR_UNEXPECTED;
     }
 
     // Camera app will stop recording when it falls to the background, so no callback is necessary.
     mAudioChannelAgent->Init(mWindow, (int32_t)AudioChannel::Content, nullptr);
     // Video recording doesn't output any sound, so it's not necessary to check canPlay.
-    float volume = 0.0;
-    bool muted = true;
-    rv = mAudioChannelAgent->StartPlaying(&volume, &muted);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
+    int32_t canPlay;
+    mAudioChannelAgent->StartPlaying(&canPlay);
   }
 #endif
   return rv;
 }
 
 already_AddRefed<Promise>
 nsDOMCameraControl::CreatePromise(ErrorResult& aRv)
 {
--- a/dom/fmradio/FMRadio.cpp
+++ b/dom/fmradio/FMRadio.cpp
@@ -130,43 +130,62 @@ FMRadio::Init(nsPIDOMWindow *aWindow)
                                              /* default = */ false);
   if (mHasInternalAntenna) {
     LOG("We have an internal antenna.");
   } else {
     mHeadphoneState = GetCurrentSwitchState(SWITCH_HEADPHONES);
     RegisterSwitchObserver(SWITCH_HEADPHONES, this);
   }
 
+  nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(GetOwner());
+  NS_ENSURE_TRUE_VOID(target);
+  target->AddSystemEventListener(NS_LITERAL_STRING("visibilitychange"), this,
+                                 /* useCapture = */ true,
+                                 /* wantsUntrusted = */ false);
+
+
   // All of the codes below are for AudioChannel. We can directly return here
   // if preferences doesn't enable AudioChannelService.
   NS_ENSURE_TRUE_VOID(Preferences::GetBool("media.useAudioChannelService"));
 
   nsCOMPtr<nsIAudioChannelAgent> audioChannelAgent =
     do_CreateInstance("@mozilla.org/audiochannelagent;1");
   NS_ENSURE_TRUE_VOID(audioChannelAgent);
 
   audioChannelAgent->InitWithWeakCallback(
     GetOwner(),
     nsIAudioChannelAgent::AUDIO_AGENT_CHANNEL_CONTENT,
     this);
 
+  nsCOMPtr<nsIDocShell> docshell = do_GetInterface(GetOwner());
+  NS_ENSURE_TRUE_VOID(docshell);
+
+  bool isActive = false;
+  docshell->GetIsActive(&isActive);
+  audioChannelAgent->SetVisibilityState(isActive);
+
   // Once all necessary resources are got successfully, we just enabled
   // mAudioChannelAgent.
   mAudioChannelAgent = audioChannelAgent;
 }
 
 void
 FMRadio::Shutdown()
 {
   IFMRadioService::Singleton()->RemoveObserver(this);
 
   if (!mHasInternalAntenna) {
     UnregisterSwitchObserver(SWITCH_HEADPHONES, this);
   }
 
+  nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(GetOwner());
+  NS_ENSURE_TRUE_VOID(target);
+  target->RemoveSystemEventListener(NS_LITERAL_STRING("visibilitychange"), this,
+                                    /* useCapture = */ true);
+
   mIsShutdown = true;
 }
 
 JSObject*
 FMRadio::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return FMRadioBinding::Wrap(aCx, this, aGivenProto);
 }
@@ -445,39 +464,70 @@ FMRadio::DisableRDS()
     return nullptr;
   }
 
   nsRefPtr<FMRadioRequest> r = new FMRadioRequest(win, this);
   FMRadioService::Singleton()->DisableRDS(r);
   return r.forget();
 }
 
+NS_IMETHODIMP
+FMRadio::HandleEvent(nsIDOMEvent* aEvent)
+{
+  nsAutoString type;
+  aEvent->GetType(type);
+
+  if (!type.EqualsLiteral("visibilitychange")) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsCOMPtr<nsIDocShell> docshell = do_GetInterface(GetOwner());
+  NS_ENSURE_TRUE(docshell, NS_ERROR_FAILURE);
+
+  bool isActive = false;
+  docshell->GetIsActive(&isActive);
+
+  mAudioChannelAgent->SetVisibilityState(isActive);
+  return NS_OK;
+}
+
 void
 FMRadio::EnableAudioChannelAgent()
 {
   NS_ENSURE_TRUE_VOID(mAudioChannelAgent);
 
-  float volume = 0.0;
-  bool muted = true;
-  mAudioChannelAgent->StartPlaying(&volume, &muted);
-  WindowVolumeChanged(volume, muted);
+  int32_t playingState = 0;
+  mAudioChannelAgent->StartPlaying(&playingState);
+  SetCanPlay(playingState == AudioChannelState::AUDIO_CHANNEL_STATE_NORMAL);
 
   mAudioChannelAgentEnabled = true;
 }
 
 NS_IMETHODIMP
-FMRadio::WindowVolumeChanged(float aVolume, bool aMuted)
+FMRadio::CanPlayChanged(int32_t aCanPlay)
 {
-  IFMRadioService::Singleton()->EnableAudio(!aMuted);
-  // TODO: what about the volume?
+  SetCanPlay(!(aCanPlay == AudioChannelState::AUDIO_CHANNEL_STATE_MUTED));
   return NS_OK;
 }
 
+NS_IMETHODIMP
+FMRadio::WindowVolumeChanged()
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void
+FMRadio::SetCanPlay(bool aCanPlay)
+{
+  IFMRadioService::Singleton()->EnableAudio(aCanPlay);
+}
+
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(FMRadio)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
   NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(FMRadio, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(FMRadio, DOMEventTargetHelper)
 
 END_FMRADIO_NAMESPACE
 
--- a/dom/fmradio/FMRadio.h
+++ b/dom/fmradio/FMRadio.h
@@ -20,16 +20,17 @@ BEGIN_FMRADIO_NAMESPACE
 
 class DOMRequest;
 
 class FMRadio final : public DOMEventTargetHelper
                     , public hal::SwitchObserver
                     , public FMRadioEventObserver
                     , public nsSupportsWeakReference
                     , public nsIAudioChannelAgentCallback
+                    , public nsIDOMEventListener
 
 {
   friend class FMRadioRequest;
 
 public:
   FMRadio();
 
   NS_DECL_ISUPPORTS_INHERITED
@@ -103,19 +104,23 @@ public:
   IMPL_EVENT_HANDLER(antennaavailablechange);
   IMPL_EVENT_HANDLER(frequencychange);
   IMPL_EVENT_HANDLER(pichange);
   IMPL_EVENT_HANDLER(ptychange);
   IMPL_EVENT_HANDLER(pschange);
   IMPL_EVENT_HANDLER(rtchange);
   IMPL_EVENT_HANDLER(newrdsgroup);
 
+  // nsIDOMEventListener
+  NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent);
+
 private:
   ~FMRadio();
 
+  void SetCanPlay(bool aCanPlay);
   void EnableAudioChannelAgent();
 
   hal::SwitchState mHeadphoneState;
   uint32_t mRdsGroupMask;
   bool mAudioChannelAgentEnabled;
   bool mHasInternalAntenna;
   bool mIsShutdown;
 
--- a/dom/html/HTMLAudioElement.cpp
+++ b/dom/html/HTMLAudioElement.cpp
@@ -9,16 +9,17 @@
 #include "nsError.h"
 #include "nsGenericHTMLElement.h"
 #include "nsGkAtoms.h"
 #include "nsIDocument.h"
 #include "jsfriendapi.h"
 #include "nsContentUtils.h"
 #include "nsJSUtils.h"
 #include "AudioSampleFormat.h"
+#include "AudioChannelCommon.h"
 #include <algorithm>
 #include "nsComponentManagerUtils.h"
 #include "nsIHttpChannel.h"
 #include "mozilla/dom/TimeRanges.h"
 #include "AudioStream.h"
 
 NS_IMPL_NS_NEW_HTML_ELEMENT(Audio)
 
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -1826,17 +1826,26 @@ void HTMLMediaElement::SetMutedInternal(
     return;
   }
 
   SetVolumeInternal();
 }
 
 void HTMLMediaElement::SetVolumeInternal()
 {
-  float effectiveVolume = mMuted ? 0.0f : float(mVolume * mAudioChannelVolume);
+  float effectiveVolume = mMuted ? 0.0f :
+    mAudioChannelFaded ? float(mVolume) * FADED_VOLUME_RATIO : float(mVolume);
+
+  if (mAudioChannelAgent) {
+    float volume;
+    nsresult rv = mAudioChannelAgent->GetWindowVolume(&volume);
+    if (NS_SUCCEEDED(rv)) {
+      effectiveVolume *= volume;
+    }
+  }
 
   if (mDecoder) {
     mDecoder->SetVolume(effectiveVolume);
   } else if (mSrcStream) {
     GetSrcMediaStream()->SetAudioOutputVolume(this, effectiveVolume);
   }
 }
 
@@ -2078,17 +2087,17 @@ HTMLMediaElement::HTMLMediaElement(alrea
     mHasPlayedOrSeeked(false),
     mHasSelfReference(false),
     mShuttingDown(false),
     mSuspendedForPreloadNone(false),
     mMediaSecurityVerified(false),
     mCORSMode(CORS_NONE),
     mIsEncrypted(false),
     mDownloadSuspendedByCache(false, "HTMLMediaElement::mDownloadSuspendedByCache"),
-    mAudioChannelVolume(1.0),
+    mAudioChannelFaded(false),
     mPlayingThroughTheAudioChannel(false),
     mDisableVideo(false),
     mPlayBlockedBecauseHidden(false),
     mElementInTreeState(ELEMENT_NOT_INTREE)
 {
   if (!gMediaElementLog) {
     gMediaElementLog = PR_NewLogModule("nsMediaElement");
   }
@@ -4029,16 +4038,23 @@ bool HTMLMediaElement::IsBeingDestroyed(
 void HTMLMediaElement::NotifyOwnerDocumentActivityChanged()
 {
   nsIDocument* ownerDoc = OwnerDoc();
   if (mDecoder && !IsBeingDestroyed()) {
     mDecoder->SetElementVisibility(!ownerDoc->Hidden());
     mDecoder->NotifyOwnerActivityChanged();
   }
 
+  // SetVisibilityState will update mMuted with MUTED_BY_AUDIO_CHANNEL via the
+  // CanPlayChanged callback.
+  if (UseAudioChannelService() && mPlayingThroughTheAudioChannel &&
+      mAudioChannelAgent) {
+    AutoNoJSAPI nojsapi;
+    mAudioChannelAgent->SetVisibilityState(!ownerDoc->Hidden());
+  }
   bool pauseElement = !IsActive() || (mMuted & MUTED_BY_AUDIO_CHANNEL);
 
   SuspendOrResumeElement(pauseElement, !IsActive());
 
   if (!mPausedForInactiveDocumentOrChannel &&
       mPlayBlockedBecauseHidden &&
       !OwnerDoc()->Hidden()) {
     LOG(LogLevel::Debug, ("%p Resuming playback now that owner doc is visble.", this));
@@ -4445,34 +4461,36 @@ NS_IMETHODIMP HTMLMediaElement::SetMozPr
 }
 
 ImageContainer* HTMLMediaElement::GetImageContainer()
 {
   VideoFrameContainer* container = GetVideoFrameContainer();
   return container ? container->GetImageContainer() : nullptr;
 }
 
-nsresult HTMLMediaElement::UpdateChannelMuteState(float aVolume, bool aMuted)
+nsresult HTMLMediaElement::UpdateChannelMuteState(AudioChannelState aCanPlay)
 {
   if (!UseAudioChannelService()) {
     return NS_OK;
   }
 
-  if (mAudioChannelVolume != aVolume) {
-    mAudioChannelVolume = aVolume;
+  if ((aCanPlay == AUDIO_CHANNEL_STATE_FADED && !mAudioChannelFaded) ||
+      (aCanPlay != AUDIO_CHANNEL_STATE_FADED && mAudioChannelFaded)) {
+    mAudioChannelFaded = !mAudioChannelFaded;
     SetVolumeInternal();
   }
 
   // We have to mute this channel.
-  if (aMuted && !(mMuted & MUTED_BY_AUDIO_CHANNEL)) {
+  if (aCanPlay == AUDIO_CHANNEL_STATE_MUTED && !(mMuted & MUTED_BY_AUDIO_CHANNEL)) {
     SetMutedInternal(mMuted | MUTED_BY_AUDIO_CHANNEL);
     if (UseAudioChannelAPI()) {
       DispatchAsyncEvent(NS_LITERAL_STRING("mozinterruptbegin"));
     }
-  } else if (!aMuted && (mMuted & MUTED_BY_AUDIO_CHANNEL)) {
+  } else if (aCanPlay != AUDIO_CHANNEL_STATE_MUTED &&
+             (mMuted & MUTED_BY_AUDIO_CHANNEL)) {
     SetMutedInternal(mMuted & ~MUTED_BY_AUDIO_CHANNEL);
     if (UseAudioChannelAPI()) {
       DispatchAsyncEvent(NS_LITERAL_STRING("mozinterruptend"));
     }
   }
 
   SuspendOrResumeElement(mMuted & MUTED_BY_AUDIO_CHANNEL, false);
   return NS_OK;
@@ -4500,44 +4518,64 @@ void HTMLMediaElement::UpdateAudioChanne
     }
 
     if (!mAudioChannelAgent) {
       nsresult rv;
       mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1", &rv);
       if (!mAudioChannelAgent) {
         return;
       }
-      mAudioChannelAgent->InitWithWeakCallback(OwnerDoc()->GetWindow(),
-                                               static_cast<int32_t>(mAudioChannel),
-                                               this);
+      // Use a weak ref so the audio channel agent can't leak |this|.
+      if (AudioChannel::Normal == mAudioChannel && IsVideo()) {
+        mAudioChannelAgent->InitWithVideo(OwnerDoc()->GetWindow(),
+                                          static_cast<int32_t>(mAudioChannel),
+                                          this, true);
+      } else {
+        mAudioChannelAgent->InitWithWeakCallback(OwnerDoc()->GetWindow(),
+                                                 static_cast<int32_t>(mAudioChannel),
+                                                 this);
+      }
+      mAudioChannelAgent->SetVisibilityState(!OwnerDoc()->Hidden());
     }
 
     // This is needed to pass nsContentUtils::IsCallerChrome().
     // AudioChannel API should not called from content but it can happen that
     // this method has some content JS in its stack.
     AutoNoJSAPI nojsapi;
 
     if (mPlayingThroughTheAudioChannel) {
-      float volume = 0.0;
-      bool muted = true;
-      mAudioChannelAgent->StartPlaying(&volume, &muted);
-      WindowVolumeChanged(volume, muted);
+      int32_t canPlay;
+      mAudioChannelAgent->StartPlaying(&canPlay);
+      CanPlayChanged(canPlay);
     } else {
       mAudioChannelAgent->StopPlaying();
       mAudioChannelAgent = nullptr;
     }
   }
 }
 
-NS_IMETHODIMP HTMLMediaElement::WindowVolumeChanged(float aVolume, bool aMuted)
-{
+/* void canPlayChanged (in boolean canPlay); */
+NS_IMETHODIMP HTMLMediaElement::CanPlayChanged(int32_t canPlay)
+{
+  static_assert(static_cast<AudioChannelState>(
+                nsIAudioChannelAgent::AUDIO_AGENT_STATE_NORMAL) ==
+                AUDIO_CHANNEL_STATE_NORMAL &&
+                static_cast<AudioChannelState>(
+                nsIAudioChannelAgent::AUDIO_AGENT_STATE_MUTED) ==
+                AUDIO_CHANNEL_STATE_MUTED &&
+                static_cast<AudioChannelState>(
+                nsIAudioChannelAgent::AUDIO_AGENT_STATE_FADED) ==
+                AUDIO_CHANNEL_STATE_FADED,
+                "Enum of channel state on nsIAudioChannelAgent.idl should be "
+                "the same with AudioChannelCommon.h");
+
   NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE);
 
-  UpdateChannelMuteState(aVolume, aMuted);
-  mPaused.SetCanPlay(!aMuted);
+  UpdateChannelMuteState(static_cast<AudioChannelState>(canPlay));
+  mPaused.SetCanPlay(canPlay != AUDIO_CHANNEL_STATE_MUTED);
   return NS_OK;
 }
 
 #ifdef MOZ_EME
 MediaKeys*
 HTMLMediaElement::GetMediaKeys() const
 {
   return mMediaKeys;
@@ -4672,16 +4710,22 @@ HTMLMediaElement::GetTopLevelPrincipal()
   if (!doc) {
     return nullptr;
   }
   principal = doc->NodePrincipal();
   return principal.forget();
 }
 #endif // MOZ_EME
 
+NS_IMETHODIMP HTMLMediaElement::WindowVolumeChanged()
+{
+  SetVolumeInternal();
+  return NS_OK;
+}
+
 AudioTrackList*
 HTMLMediaElement::AudioTracks()
 {
   if (!mAudioTrackList) {
     nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(OwnerDoc()->GetParentObject());
     mAudioTrackList = new AudioTrackList(window, this);
   }
   return mAudioTrackList;
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -8,16 +8,17 @@
 
 #include "nsIDOMHTMLMediaElement.h"
 #include "nsGenericHTMLElement.h"
 #include "MediaDecoderOwner.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIObserver.h"
 #include "mozilla/CORSMode.h"
 #include "DOMMediaStream.h"
+#include "AudioChannelCommon.h"
 #include "DecoderTraits.h"
 #include "nsIAudioChannelAgent.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/TextTrackManager.h"
 #include "MediaDecoder.h"
 #ifdef MOZ_EME
 #include "mozilla/dom/MediaKeys.h"
@@ -1002,18 +1003,18 @@ protected:
 #ifdef MOZ_EME
   void ReportEMETelemetry();
 #endif
   void ReportMSETelemetry();
 
   // Check the permissions for audiochannel.
   bool CheckAudioChannelPermissions(const nsAString& aType);
 
-  // This method does the check for muting/nmuting the audio channel.
-  nsresult UpdateChannelMuteState(float aVolume, bool aMuted);
+  // This method does the check for muting/fading/unmuting the audio channel.
+  nsresult UpdateChannelMuteState(mozilla::dom::AudioChannelState aCanPlay);
 
   // Seeks to aTime seconds. aSeekType can be Exact to seek to exactly the
   // seek target, or PrevSyncPoint if a quicker but less precise seek is
   // desired, and we'll seek to the sync point (keyframe and/or start of the
   // next block of audio samples) preceeding seek target.
   void Seek(double aTime, SeekTarget::Type aSeekType, ErrorResult& aRv);
 
   // Update the audio channel playing state
@@ -1357,18 +1358,18 @@ protected:
 #endif // MOZ_EME
 
   // True if the media's channel's download has been suspended.
   Watchable<bool> mDownloadSuspendedByCache;
 
   // Audio Channel.
   AudioChannel mAudioChannel;
 
-  // The audio channel volume
-  float mAudioChannelVolume;
+  // The audio channel has been faded.
+  bool mAudioChannelFaded;
 
   // Is this media element playing?
   bool mPlayingThroughTheAudioChannel;
 
   // Disable the video playback by track selection. This flag might not be
   // enough if we ever expand the ability of supporting multi-tracks video
   // playback.
   bool mDisableVideo;
--- a/dom/html/nsBrowserElement.cpp
+++ b/dom/html/nsBrowserElement.cpp
@@ -4,31 +4,25 @@
  * 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 "nsBrowserElement.h"
 
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/dom/BrowserElementBinding.h"
-#include "mozilla/dom/BrowserElementAudioChannel.h"
 #include "mozilla/dom/DOMRequest.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/ToJSValue.h"
 
-#include "AudioChannelService.h"
-
-#include "mozIApplication.h"
 #include "nsComponentManagerUtils.h"
 #include "nsContentUtils.h"
 #include "nsFrameLoader.h"
-#include "nsIAppsService.h"
 #include "nsIDOMDOMRequest.h"
 #include "nsIDOMElement.h"
-#include "nsIMozBrowserFrame.h"
 #include "nsINode.h"
 #include "nsIPrincipal.h"
 
 using namespace mozilla::dom;
 
 namespace mozilla {
 
 bool
@@ -509,151 +503,16 @@ nsBrowserElement::SetInputMethodActive(b
     }
     return nullptr;
   }
 
   return req.forget().downcast<DOMRequest>();
 }
 
 void
-nsBrowserElement::GetAllowedAudioChannels(
-                 nsTArray<nsRefPtr<BrowserElementAudioChannel>>& aAudioChannels,
-                 ErrorResult& aRv)
-{
-  aAudioChannels.Clear();
-
-  NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
-  NS_ENSURE_TRUE_VOID(IsNotWidgetOrThrow(aRv));
-
-  // If empty, it means that this is the first call of this method.
-  if (mBrowserElementAudioChannels.IsEmpty()) {
-    nsCOMPtr<nsIFrameLoader> frameLoader = GetFrameLoader();
-    if (!frameLoader) {
-      aRv.Throw(NS_ERROR_FAILURE);
-      return;
-    }
-
-    nsCOMPtr<nsIDOMElement> frameElement;
-    aRv = frameLoader->GetOwnerElement(getter_AddRefs(frameElement));
-    if (NS_WARN_IF(aRv.Failed())) {
-      return;
-    }
-
-    MOZ_ASSERT(frameElement);
-
-    nsCOMPtr<nsIDOMDocument> doc;
-    aRv = frameElement->GetOwnerDocument(getter_AddRefs(doc));
-    if (NS_WARN_IF(aRv.Failed())) {
-      return;
-    }
-
-    MOZ_ASSERT(doc);
-
-    nsCOMPtr<nsIDOMWindow> win;
-    aRv = doc->GetDefaultView(getter_AddRefs(win));
-    if (NS_WARN_IF(aRv.Failed())) {
-      return;
-    }
-
-    MOZ_ASSERT(win);
-
-    nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(win);
-
-    if (!window->IsInnerWindow()) {
-      window = window->GetCurrentInnerWindow();
-    }
-
-    nsCOMPtr<nsIMozBrowserFrame> mozBrowserFrame =
-      do_QueryInterface(frameElement);
-    if (NS_WARN_IF(!mozBrowserFrame)) {
-      aRv.Throw(NS_ERROR_FAILURE);
-      return;
-    }
-
-    nsAutoString manifestURL;
-    aRv = mozBrowserFrame->GetAppManifestURL(manifestURL);
-    if (NS_WARN_IF(aRv.Failed())) {
-      return;
-    }
-
-    nsCOMPtr<nsIAppsService> appsService =
-      do_GetService("@mozilla.org/AppsService;1");
-    if (NS_WARN_IF(!appsService)) {
-      aRv.Throw(NS_ERROR_FAILURE);
-      return;
-    }
-
-    nsCOMPtr<mozIApplication> app;
-    aRv = appsService->GetAppByManifestURL(manifestURL, getter_AddRefs(app));
-    if (NS_WARN_IF(aRv.Failed())) {
-      return;
-    }
-
-    bool noapp = false;
-    Preferences::GetBool("dom.testing.browserElementAudioChannel.noapp", &noapp);
-
-    if (!noapp && !app) {
-      aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
-      return;
-    }
-
-    // Normal is always allowed.
-    nsTArray<nsRefPtr<BrowserElementAudioChannel>> channels;
-
-    nsRefPtr<BrowserElementAudioChannel> ac =
-      new BrowserElementAudioChannel(window, frameLoader, mBrowserElementAPI,
-                                     AudioChannel::Normal);
-
-    aRv = ac->Initialize();
-    if (NS_WARN_IF(aRv.Failed())) {
-      return;
-    }
-
-    channels.AppendElement(ac);
-
-    // app can be null in case we are in a test.
-    if (app) {
-      const nsAttrValue::EnumTable* audioChannelTable =
-        AudioChannelService::GetAudioChannelTable();
-
-      bool allowed;
-      nsAutoCString permissionName;
-
-      for (uint32_t i = 0; audioChannelTable && audioChannelTable[i].tag; ++i) {
-        permissionName.AssignASCII("audio-channel-");
-        permissionName.AppendASCII(audioChannelTable[i].tag);
-
-        aRv = app->HasPermission(permissionName.get(), &allowed);
-        if (NS_WARN_IF(aRv.Failed())) {
-          return;
-        }
-
-        if (allowed) {
-          nsRefPtr<BrowserElementAudioChannel> ac =
-            new BrowserElementAudioChannel(window, frameLoader,
-                                           mBrowserElementAPI,
-                                           (AudioChannel)audioChannelTable[i].value);
-
-          aRv = ac->Initialize();
-          if (NS_WARN_IF(aRv.Failed())) {
-            return;
-          }
-
-          channels.AppendElement(ac);
-        }
-      }
-    }
-
-    mBrowserElementAudioChannels.AppendElements(channels);
-  }
-
-  aAudioChannels.AppendElements(mBrowserElementAudioChannels);
-}
-
-void
 nsBrowserElement::SetNFCFocus(bool aIsFocus,
                               ErrorResult& aRv)
 {
   NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
 
   nsRefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
   if (!frameLoader) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
--- a/dom/html/nsBrowserElement.h
+++ b/dom/html/nsBrowserElement.h
@@ -3,17 +3,16 @@
 /* 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 nsBrowserElement_h
 #define nsBrowserElement_h
 
 #include "mozilla/dom/BindingDeclarations.h"
-#include "mozilla/dom/BrowserElementAudioChannel.h"
 
 #include "nsCOMPtr.h"
 #include "nsIBrowserElementAPI.h"
 
 class nsFrameLoader;
 
 namespace mozilla {
 
@@ -66,20 +65,16 @@ public:
 
   already_AddRefed<dom::DOMRequest>
   Download(const nsAString& aUrl,
            const dom::BrowserElementDownloadOptions& options,
            ErrorResult& aRv);
 
   already_AddRefed<dom::DOMRequest> PurgeHistory(ErrorResult& aRv);
 
-  void GetAllowedAudioChannels(
-            nsTArray<nsRefPtr<dom::BrowserElementAudioChannel>>& aAudioChannels,
-            ErrorResult& aRv);
-
   already_AddRefed<dom::DOMRequest>
   GetScreenshot(uint32_t aWidth,
                 uint32_t aHeight,
                 const nsAString& aMimeType,
                 ErrorResult& aRv);
 
   void Zoom(float aZoom, ErrorResult& aRv);
 
@@ -102,17 +97,16 @@ public:
 
   void SetNFCFocus(bool isFocus,
                    ErrorResult& aRv);
 
 protected:
   NS_IMETHOD_(already_AddRefed<nsFrameLoader>) GetFrameLoader() = 0;
   void InitBrowserElementAPI();
   nsCOMPtr<nsIBrowserElementAPI> mBrowserElementAPI;
-  nsTArray<nsRefPtr<dom::BrowserElementAudioChannel>> mBrowserElementAudioChannels;
 
 private:
   bool IsBrowserElementOrThrow(ErrorResult& aRv);
   bool IsNotWidgetOrThrow(ErrorResult& aRv);
   bool mOwnerIsWidget;
 };
 
 } // namespace mozilla
--- a/dom/html/nsGenericHTMLFrameElement.cpp
+++ b/dom/html/nsGenericHTMLFrameElement.cpp
@@ -1,17 +1,16 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
 #include "nsGenericHTMLFrameElement.h"
 
-#include "mozilla/dom/BrowserElementAudioChannel.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ErrorResult.h"
 #include "GeckoProfiler.h"
 #include "mozIApplication.h"
 #include "nsAttrValueInlines.h"
 #include "nsContentUtils.h"
 #include "nsIAppsService.h"
@@ -30,30 +29,18 @@ using namespace mozilla;
 using namespace mozilla::dom;
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsGenericHTMLFrameElement)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsGenericHTMLFrameElement,
                                                   nsGenericHTMLElement)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameLoader)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowserElementAPI)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowserElementAudioChannels)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
-NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsGenericHTMLFrameElement,
-                                                nsGenericHTMLElement)
-  if (tmp->mFrameLoader) {
-    tmp->mFrameLoader->Destroy();
-  }
-
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameLoader)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowserElementAPI)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowserElementAudioChannels)
-NS_IMPL_CYCLE_COLLECTION_UNLINK_END
-
 NS_IMPL_ADDREF_INHERITED(nsGenericHTMLFrameElement, nsGenericHTMLElement)
 NS_IMPL_RELEASE_INHERITED(nsGenericHTMLFrameElement, nsGenericHTMLElement)
 
 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsGenericHTMLFrameElement)
   NS_INTERFACE_TABLE_INHERITED(nsGenericHTMLFrameElement,
                                nsIFrameLoaderOwner,
                                nsIDOMMozBrowserFrame,
                                nsIMozBrowserFrame)
--- a/dom/html/nsGenericHTMLFrameElement.h
+++ b/dom/html/nsGenericHTMLFrameElement.h
@@ -66,18 +66,18 @@ public:
                                 const nsAttrValue* aValue,
                                 bool aNotify) override;
   virtual void DestroyContent() override;
 
   nsresult CopyInnerTo(mozilla::dom::Element* aDest);
 
   virtual int32_t TabIndexDefault() override;
 
-  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsGenericHTMLFrameElement,
-                                           nsGenericHTMLElement)
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_NO_UNLINK(nsGenericHTMLFrameElement,
+                                                     nsGenericHTMLElement)
 
   void SwapFrameLoaders(nsXULElement& aOtherOwner, mozilla::ErrorResult& aError);
 
   static bool BrowserFramesEnabled();
 
   /**
    * Helper method to map a HTML 'scrolling' attribute value to a nsIScrollable
    * enum value.  scrolling="no" (and its synonyms) maps to
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -177,16 +177,17 @@
 #endif
 
 #include "ProcessUtils.h"
 #include "StructuredCloneUtils.h"
 #include "URIUtils.h"
 #include "nsContentUtils.h"
 #include "nsIPrincipal.h"
 #include "nsDeviceStorage.h"
+#include "AudioChannelService.h"
 #include "DomainPolicy.h"
 #include "mozilla/dom/DataStoreService.h"
 #include "mozilla/dom/telephony/PTelephonyChild.h"
 #include "mozilla/dom/time/DateCacheCleaner.h"
 #include "mozilla/dom/voicemail/VoicemailIPCService.h"
 #include "mozilla/net/NeckoMessageUtils.h"
 #include "mozilla/widget/PuppetBidiKeyboard.h"
 #include "mozilla/RemoteSpellCheckEngineChild.h"
@@ -938,16 +939,27 @@ NS_IMETHODIMP MemoryReportRequestChild::
     mgr->GetReportsForThisProcessExtended(cb, nullptr, mAnonymize,
                                           FileDescriptorToFILE(mDMDFile, "wb"));
 
     bool sent = Send__delete__(this);
     return sent ? NS_OK : NS_ERROR_FAILURE;
 }
 
 bool
+ContentChild::RecvAudioChannelNotify()
+{
+    nsRefPtr<AudioChannelService> service =
+        AudioChannelService::GetAudioChannelService();
+    if (service) {
+        service->Notify();
+    }
+    return true;
+}
+
+bool
 ContentChild::RecvDataStoreNotify(const uint32_t& aAppId,
                                   const nsString& aName,
                                   const nsString& aManifestURL)
 {
   nsRefPtr<DataStoreService> service = DataStoreService::GetOrCreate();
   if (NS_WARN_IF(!service)) {
     return false;
   }
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -196,16 +196,19 @@ public:
     DeallocPCycleCollectWithLogsChild(PCycleCollectWithLogsChild* aActor) override;
     virtual bool
     RecvPCycleCollectWithLogsConstructor(PCycleCollectWithLogsChild* aChild,
                                          const bool& aDumpAllTraces,
                                          const FileDescriptor& aGCLog,
                                          const FileDescriptor& aCCLog) override;
 
     virtual bool
+    RecvAudioChannelNotify() override;
+
+    virtual bool
     RecvDataStoreNotify(const uint32_t& aAppId, const nsString& aName,
                         const nsString& aManifestURL) override;
 
     virtual PTestShellChild* AllocPTestShellChild() override;
     virtual bool DeallocPTestShellChild(PTestShellChild*) override;
     virtual bool RecvPTestShellConstructor(PTestShellChild*) override;
     jsipc::CPOWManager* GetCPOWManager() override;
 
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -2787,20 +2787,73 @@ ContentParent::RecvFirstIdle()
     // which we use as a good time to prelaunch another process. If we
     // prelaunch any sooner than this, then we'll be competing with the
     // child process and slowing it down.
     PreallocatedProcessManager::AllocateAfterDelay();
     return true;
 }
 
 bool
+ContentParent::RecvAudioChannelGetState(const AudioChannel& aChannel,
+                                        const bool& aElementHidden,
+                                        const bool& aElementWasHidden,
+                                        AudioChannelState* aState)
+{
+    nsRefPtr<AudioChannelService> service =
+        AudioChannelService::GetOrCreateAudioChannelService();
+    *aState = AUDIO_CHANNEL_STATE_NORMAL;
+    MOZ_ASSERT(service);
+    *aState = service->GetStateInternal(aChannel, mChildID,
+                                        aElementHidden, aElementWasHidden);
+
+    return true;
+}
+
+bool
+ContentParent::RecvAudioChannelRegisterType(const AudioChannel& aChannel,
+                                            const bool& aWithVideo)
+{
+    nsRefPtr<AudioChannelService> service =
+        AudioChannelService::GetOrCreateAudioChannelService();
+    MOZ_ASSERT(service);
+    service->RegisterType(aChannel, mChildID, aWithVideo);
+
+    return true;
+}
+
+bool
+ContentParent::RecvAudioChannelUnregisterType(const AudioChannel& aChannel,
+                                              const bool& aElementHidden,
+                                              const bool& aWithVideo)
+{
+    nsRefPtr<AudioChannelService> service =
+        AudioChannelService::GetOrCreateAudioChannelService();
+    MOZ_ASSERT(service);
+    service->UnregisterType(aChannel, aElementHidden, mChildID, aWithVideo);
+
+    return true;
+}
+
+bool
+ContentParent::RecvAudioChannelChangedNotification()
+{
+    nsRefPtr<AudioChannelService> service =
+        AudioChannelService::GetOrCreateAudioChannelService();
+    MOZ_ASSERT(service);
+    service->SendAudioChannelChangedNotification(ChildID());
+
+    return true;
+}
+
+bool
 ContentParent::RecvAudioChannelChangeDefVolChannel(const int32_t& aChannel,
                                                    const bool& aHidden)
 {
-    nsRefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
+    nsRefPtr<AudioChannelService> service =
+        AudioChannelService::GetOrCreateAudioChannelService();
     MOZ_ASSERT(service);
     service->SetDefaultVolumeControlChannelInternal(aChannel,
                                                     aHidden, mChildID);
     return true;
 }
 
 bool
 ContentParent::RecvDataStoreGetStores(
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -747,16 +747,29 @@ private:
                                  const uint32_t& aColNumber,
                                  const uint32_t& aFlags,
                                  const nsCString& aCategory) override;
 
     virtual bool RecvPrivateDocShellsExist(const bool& aExist) override;
 
     virtual bool RecvFirstIdle() override;
 
+    virtual bool RecvAudioChannelGetState(const AudioChannel& aChannel,
+                                          const bool& aElementHidden,
+                                          const bool& aElementWasHidden,
+                                          AudioChannelState* aValue) override;
+
+    virtual bool RecvAudioChannelRegisterType(const AudioChannel& aChannel,
+                                              const bool& aWithVideo) override;
+    virtual bool RecvAudioChannelUnregisterType(const AudioChannel& aChannel,
+                                                const bool& aElementHidden,
+                                                const bool& aWithVideo) override;
+
+    virtual bool RecvAudioChannelChangedNotification() override;
+
     virtual bool RecvAudioChannelChangeDefVolChannel(const int32_t& aChannel,
                                                      const bool& aHidden) override;
     virtual bool RecvGetSystemMemory(const uint64_t& getterId) override;
 
     virtual bool RecvGetLookAndFeelCache(nsTArray<LookAndFeelInt>* aLookAndFeelIntCache) override;
 
     virtual bool RecvDataStoreGetStores(
                        const nsString& aName,
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -543,20 +543,16 @@ parent:
 
     prio(high) sync DispatchWheelEvent(WidgetWheelEvent event);
     prio(high) sync DispatchMouseEvent(WidgetMouseEvent event);
     prio(high) sync DispatchKeyboardEvent(WidgetKeyboardEvent event);
 
     InvokeDragSession(IPCDataTransfer[] transfers, uint32_t action,
                       nsCString visualData, uint32_t width, uint32_t height,
                       uint32_t stride, uint8_t format, int32_t dragAreaX, int32_t dragAreaY);
-
-    async AudioChannelActivityNotification(uint32_t aAudioChannel,
-                                           bool aActive);
-
 child:
     /**
      * Notify the remote browser that it has been Show()n on this
      * side, with the given |visibleRect|.  This message is expected
      * to trigger creation of the remote browser's "widget".
      *
      * |Show()| and |Move()| take IntSizes rather than Rects because
      * content processes always render to a virtual <0, 0> top-left
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -76,16 +76,18 @@ using struct OverrideMapping from "mozil
 using base::ChildPrivileges from "base/process_util.h";
 using base::ProcessId from "base/process.h";
 using struct IPC::Permission from "mozilla/net/NeckoMessageUtils.h";
 using class IPC::Principal from "mozilla/dom/PermissionMessageUtils.h";
 using struct mozilla::null_t from "ipc/IPCMessageUtils.h";
 using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
 using mozilla::dom::asmjscache::OpenMode from "mozilla/dom/asmjscache/AsmJSCache.h";
 using mozilla::dom::asmjscache::WriteParams from "mozilla/dom/asmjscache/AsmJSCache.h";
+using mozilla::dom::AudioChannel from "mozilla/dom/AudioChannelBinding.h";
+using mozilla::dom::AudioChannelState from "AudioChannelCommon.h";
 using mozilla::dom::NativeThreadId from "mozilla/dom/TabMessageUtils.h";
 using mozilla::dom::quota::PersistenceType from "mozilla/dom/quota/PersistenceType.h";
 using mozilla::hal::ProcessPriority from "mozilla/HalTypes.h";
 using gfxIntSize from "nsSize.h";
 using mozilla::dom::TabId from "mozilla/dom/ipc/IdType.h";
 using mozilla::dom::ContentParentId from "mozilla/dom/ipc/IdType.h";
 using struct LookAndFeelInt from "mozilla/widget/WidgetMessageUtils.h";
 using struct mozilla::OwningSerializedStructuredCloneBuffer from "ipc/IPCMessageUtils.h";
@@ -491,16 +493,21 @@ child:
      * usually only be performed zero or one times.  The child may
      * abnormally exit if this fails; the details are OS-specific.
      */
     async SetProcessSandbox();
 
     PMemoryReportRequest(uint32_t generation, bool anonymize,
                          bool minimizeMemoryUsage, MaybeFileDesc DMDFile);
 
+    /**
+     * Notify the AudioChannelService in the child processes.
+     */
+    async AudioChannelNotify();
+
     async SpeakerManagerNotify();
 
     /**
      * Communication between the PuppetBidiKeyboard and the actual
      * BidiKeyboard hosted by the parent
      */
     async BidiKeyboardNotify(bool isLangRTL);
 
@@ -859,16 +866,27 @@ parent:
         returns (bool showPassword);
 
     // Notify the parent of the presence or absence of private docshells
     PrivateDocShellsExist(bool aExist);
 
     // Tell the parent that the child has gone idle for the first time
     async FirstIdle();
 
+    // Get Muted from the main AudioChannelService.
+    sync AudioChannelGetState(AudioChannel aChannel, bool aElementHidden,
+                              bool aElementWasHidden)
+        returns (AudioChannelState value);
+
+    sync AudioChannelRegisterType(AudioChannel aChannel, bool aWithVideo);
+    sync AudioChannelUnregisterType(AudioChannel aChannel,
+                                    bool aElementHidden,
+                                    bool aWithVideo);
+
+    async AudioChannelChangedNotification();
     async AudioChannelChangeDefVolChannel(int32_t aChannel, bool aHidden);
 
     sync DataStoreGetStores(nsString aName, nsString aOwner, Principal aPrincipal)
         returns (DataStoreSetting[] dataStores);
 
     async FilePathUpdateNotify(nsString aType,
                                nsString aStorageName,
                                nsString aFilepath,
--- a/dom/ipc/ProcessPriorityManager.cpp
+++ b/dom/ipc/ProcessPriorityManager.cpp
@@ -1036,17 +1036,17 @@ ParticularProcessPriorityManager::Comput
       PROCESS_PRIORITY_FOREGROUND;
   }
 
   if ((mHoldsCPUWakeLock || mHoldsHighPriorityWakeLock) &&
       IsExpectingSystemMessage()) {
     return PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE;
   }
 
-  nsRefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
+  AudioChannelService* service = AudioChannelService::GetOrCreateAudioChannelService();
   if (service->ProcessContentOrNormalChannelIsActive(ChildID())) {
     return PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE;
   }
 
   return mIsActivityOpener ? PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE
                            : PROCESS_PRIORITY_BACKGROUND;
 }
 
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -885,32 +885,16 @@ TabChild::TabChild(nsIContentChild* aMan
   // check the other conditions necessary for enabling APZ.
   mAsyncPanZoomEnabled = gfxPlatform::AsyncPanZoomEnabled();
 
   // preloaded TabChild should not be added to child map
   if (mUniqueId) {
     MOZ_ASSERT(NestedTabChildMap().find(mUniqueId) == NestedTabChildMap().end());
     NestedTabChildMap()[mUniqueId] = this;
   }
-
-  nsCOMPtr<nsIObserverService> observerService =
-    mozilla::services::GetObserverService();
-
-  if (observerService) {
-    const nsAttrValue::EnumTable* table =
-      AudioChannelService::GetAudioChannelTable();
-
-    nsAutoCString topic;
-    for (uint32_t i = 0; table[i].tag; ++i) {
-      topic.Assign("audiochannel-activity-");
-      topic.Append(table[i].tag);
-
-      observerService->AddObserver(this, topic.get(), false);
-    }
-  }
 }
 
 NS_IMETHODIMP
 TabChild::HandleEvent(nsIDOMEvent* aEvent)
 {
   nsAutoString eventType;
   aEvent->GetType(eventType);
   if (eventType.EqualsLiteral("DOMMetaAdded")) {
@@ -968,37 +952,16 @@ TabChild::Observe(nsISupports *aSubject,
             nsLayoutUtils::SetResolutionAndScaleTo(shell, mLastRootMetrics.GetPresShellResolution());
           }
           HandlePossibleViewportChange(GetInnerSize());
         }
       }
     }
   }
 
-  const nsAttrValue::EnumTable* table =
-    AudioChannelService::GetAudioChannelTable();
-
-  nsAutoCString topic;
-  int16_t audioChannel = -1;
-  for (uint32_t i = 0; table[i].tag; ++i) {
-    topic.Assign("audiochannel-activity-");
-    topic.Append(table[i].tag);
-
-    if (topic.Equals(aTopic)) {
-      audioChannel = table[i].value;
-      break;
-    }
-  }
-
-  if (audioChannel != -1) {
-    nsAutoString active(aData);
-    unused << SendAudioChannelActivityNotification(audioChannel,
-                                                   active.Equals(NS_LITERAL_STRING("active")));
-  }
-
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TabChild::OnStateChange(nsIWebProgress* aWebProgress,
                         nsIRequest* aRequest,
                         uint32_t aStateFlags,
                         nsresult aStatus)
@@ -2851,27 +2814,16 @@ TabChild::RecvDestroy()
   }
 
   nsCOMPtr<nsIObserverService> observerService =
     mozilla::services::GetObserverService();
 
   observerService->RemoveObserver(this, BROWSER_ZOOM_TO_RECT);
   observerService->RemoveObserver(this, BEFORE_FIRST_PAINT);
 
-  const nsAttrValue::EnumTable* table =
-    AudioChannelService::GetAudioChannelTable();
-
-  nsAutoCString topic;
-  for (uint32_t i = 0; table[i].tag; ++i) {
-    topic.Assign("audiochannel-activity-");
-    topic.Append(table[i].tag);
-
-    observerService->RemoveObserver(this, topic.get());
-  }
-
   // XXX what other code in ~TabChild() should we be running here?
   DestroyWindow();
 
   // Bounce through the event loop once to allow any delayed teardown runnables
   // that were just generated to have a chance to run.
   nsCOMPtr<nsIRunnable> deleteRunnable = new DelayedDeleteRunnable(this);
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToCurrentThread(deleteRunnable)));
 
--- a/dom/ipc/TabMessageUtils.h
+++ b/dom/ipc/TabMessageUtils.h
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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 TABMESSAGE_UTILS_H
 #define TABMESSAGE_UTILS_H
 
+#include "AudioChannelCommon.h"
 #include "ipc/IPCMessageUtils.h"
 #include "mozilla/dom/AudioChannelBinding.h"
 #include "nsIDOMEvent.h"
 #include "nsCOMPtr.h"
 
 #ifdef MOZ_CRASHREPORTER
 #include "nsExceptionHandler.h"
 #endif
@@ -84,16 +85,24 @@ struct ParamTraits<mozilla::dom::AudioCh
   }
 
   static void Log(const paramType& aParam, std::wstring* aLog)
   {
   }
 };
 
 template <>
+struct ParamTraits<mozilla::dom::AudioChannelState>
+  : public ContiguousEnumSerializer<mozilla::dom::AudioChannelState,
+                                    mozilla::dom::AUDIO_CHANNEL_STATE_NORMAL,
+                                    mozilla::dom::AUDIO_CHANNEL_STATE_LAST>
+{ };
+
+
+template <>
 struct ParamTraits<nsEventStatus>
   : public ContiguousEnumSerializer<nsEventStatus,
                                     nsEventStatus_eIgnore,
                                     nsEventStatus_eSentinel>
 { };
 
 }
 
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -3,17 +3,16 @@
 /* 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/. */
 
 #include "base/basictypes.h"
 
 #include "TabParent.h"
 
-#include "AudioChannelService.h"
 #include "AppProcessChecker.h"
 #include "mozIApplication.h"
 #ifdef ACCESSIBILITY
 #include "mozilla/a11y/DocAccessibleParent.h"
 #include "nsAccessibilityService.h"
 #endif
 #include "mozilla/BrowserElementParent.h"
 #include "mozilla/dom/ContentParent.h"
@@ -2642,39 +2641,16 @@ TabParent::RecvGetRenderFrameInfo(PRende
   if (mNeedLayerTreeReadyNotification) {
     RequestNotifyLayerTreeReady();
     mNeedLayerTreeReadyNotification = false;
   }
 
   return true;
 }
 
-bool
-TabParent::RecvAudioChannelActivityNotification(const uint32_t& aAudioChannel,
-                                                const bool& aActive)
-{
-  if (aAudioChannel >= NUMBER_OF_AUDIO_CHANNELS) {
-    return false;
-  }
-
-  nsCOMPtr<nsIObserverService> os = services::GetObserverService();
-  if (os) {
-    nsRefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
-    nsAutoCString topic;
-    topic.Assign("audiochannel-activity-");
-    topic.Append(AudioChannelService::GetAudioChannelTable()[aAudioChannel].tag);
-
-    os->NotifyObservers(NS_ISUPPORTS_CAST(nsITabParent*, this),
-                        topic.get(),
-                        aActive ? MOZ_UTF16("active") : MOZ_UTF16("inactive"));
-  }
-
-  return true;
-}
-
 already_AddRefed<nsFrameLoader>
 TabParent::GetFrameLoader(bool aUseCachedFrameLoaderAfterDestroy) const
 {
   if (mIsDestroyed && !aUseCachedFrameLoaderAfterDestroy) {
     return nullptr;
   }
 
   if (mFrameLoader) {
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -457,19 +457,16 @@ protected:
     virtual bool RecvGetRenderFrameInfo(PRenderFrameParent* aRenderFrame,
                                         TextureFactoryIdentifier* aTextureFactoryIdentifier,
                                         uint64_t* aLayersId) override;
 
     virtual bool RecvSetDimensions(const uint32_t& aFlags,
                                    const int32_t& aX, const int32_t& aY,
                                    const int32_t& aCx, const int32_t& aCy) override;
 
-    virtual bool RecvAudioChannelActivityNotification(const uint32_t& aAudioChannel,
-                                                      const bool& aActive) override;
-
     bool InitBrowserConfiguration(const nsCString& aURI,
                                   BrowserConfiguration& aConfiguration);
 
     void SetHasContentOpener(bool aHasContentOpener);
 
     ContentCacheInParent mContentCache;
 
     nsIntRect mRect;
--- a/dom/media/webaudio/AudioDestinationNode.cpp
+++ b/dom/media/webaudio/AudioDestinationNode.cpp
@@ -293,21 +293,54 @@ static bool UseAudioChannelService()
   return Preferences::GetBool("media.useAudioChannelService");
 }
 
 static bool UseAudioChannelAPI()
 {
   return Preferences::GetBool("media.useAudioChannelAPI");
 }
 
+class EventProxyHandler final : public nsIDOMEventListener
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  explicit EventProxyHandler(nsIDOMEventListener* aNode)
+  {
+    MOZ_ASSERT(aNode);
+    mWeakNode = do_GetWeakReference(aNode);
+  }
+
+  // nsIDOMEventListener
+  NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) override
+  {
+    nsCOMPtr<nsIDOMEventListener> listener = do_QueryReferent(mWeakNode);
+    if (!listener) {
+      return NS_OK;
+    }
+
+    auto node = static_cast<AudioDestinationNode*>(listener.get());
+    return node->HandleEvent(aEvent);
+  }
+
+private:
+  ~EventProxyHandler()
+  { }
+
+  nsWeakPtr mWeakNode;
+};
+
+NS_IMPL_ISUPPORTS(EventProxyHandler, nsIDOMEventListener)
+
 NS_IMPL_CYCLE_COLLECTION_INHERITED(AudioDestinationNode, AudioNode,
-                                   mAudioChannelAgent,
+                                   mAudioChannelAgent, mEventProxyHelper,
                                    mOfflineRenderingPromise)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(AudioDestinationNode)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
   NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback)
 NS_INTERFACE_MAP_END_INHERITING(AudioNode)
 
 NS_IMPL_ADDREF_INHERITED(AudioDestinationNode, AudioNode)
 NS_IMPL_RELEASE_INHERITED(AudioDestinationNode, AudioNode)
 
 AudioDestinationNode::AudioDestinationNode(AudioContext* aContext,
                                            bool aIsOffline,
@@ -370,16 +403,23 @@ AudioDestinationNode::SizeOfIncludingThi
 }
 
 void
 AudioDestinationNode::DestroyAudioChannelAgent()
 {
   if (mAudioChannelAgent && !Context()->IsOffline()) {
     mAudioChannelAgent->StopPlaying();
     mAudioChannelAgent = nullptr;
+
+    nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(GetOwner());
+    NS_ENSURE_TRUE_VOID(target);
+
+    target->RemoveSystemEventListener(NS_LITERAL_STRING("visibilitychange"),
+                                      mEventProxyHelper,
+                                      /* useCapture = */ true);
   }
 }
 
 void
 AudioDestinationNode::DestroyMediaStream()
 {
   DestroyAudioChannelAgent();
 
@@ -473,40 +513,75 @@ void
 AudioDestinationNode::StartRendering(Promise* aPromise)
 {
   mOfflineRenderingPromise = aPromise;
   mOfflineRenderingRef.Take(this);
   mStream->Graph()->StartNonRealtimeProcessing(mFramesToProduce);
 }
 
 void
-AudioDestinationNode::SetCanPlay(float aVolume, bool aMuted)
+AudioDestinationNode::SetCanPlay(bool aCanPlay)
 {
-  if (!mStream) {
-    return;
+  mStream->SetTrackEnabled(AudioNodeStream::AUDIO_TRACK, aCanPlay);
+}
+
+NS_IMETHODIMP
+AudioDestinationNode::HandleEvent(nsIDOMEvent* aEvent)
+{
+  nsAutoString type;
+  aEvent->GetType(type);
+
+  if (!type.EqualsLiteral("visibilitychange")) {
+    return NS_ERROR_FAILURE;
   }
 
-  mStream->SetTrackEnabled(AudioNodeStream::AUDIO_TRACK, !aMuted);
-  mStream->SetAudioOutputVolume(&gWebAudioOutputKey, aVolume);
+  nsCOMPtr<nsIDocShell> docshell = do_GetInterface(GetOwner());
+  NS_ENSURE_TRUE(docshell, NS_ERROR_FAILURE);
+
+  bool isActive = false;
+  docshell->GetIsActive(&isActive);
+
+  mAudioChannelAgent->SetVisibilityState(isActive);
+  return NS_OK;
 }
 
 NS_IMETHODIMP
-AudioDestinationNode::WindowVolumeChanged(float aVolume, bool aMuted)
+AudioDestinationNode::CanPlayChanged(int32_t aCanPlay)
 {
-  if (aMuted != mAudioChannelAgentPlaying) {
-    mAudioChannelAgentPlaying = aMuted;
+  bool playing = aCanPlay == AudioChannelState::AUDIO_CHANNEL_STATE_NORMAL;
+  if (playing == mAudioChannelAgentPlaying) {
+    return NS_OK;
+  }
 
-    if (UseAudioChannelAPI()) {
-      Context()->DispatchTrustedEvent(
-        !aMuted ? NS_LITERAL_STRING("mozinterruptend")
-                : NS_LITERAL_STRING("mozinterruptbegin"));
-    }
+  mAudioChannelAgentPlaying = playing;
+  SetCanPlay(playing);
+
+  if (UseAudioChannelAPI()) {
+    Context()->DispatchTrustedEvent(
+      playing ? NS_LITERAL_STRING("mozinterruptend")
+              : NS_LITERAL_STRING("mozinterruptbegin"));
   }
 
-  SetCanPlay(aVolume, aMuted);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+AudioDestinationNode::WindowVolumeChanged()
+{
+  MOZ_ASSERT(mAudioChannelAgent);
+
+  if (!mStream) {
+    return NS_OK;
+  }
+
+  float volume;
+  nsresult rv = mAudioChannelAgent->GetWindowVolume(&volume);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mStream->SetAudioOutputVolume(&gWebAudioOutputKey, volume);
   return NS_OK;
 }
 
 AudioChannel
 AudioDestinationNode::MozAudioChannelType() const
 {
   return mAudioChannel;
 }
@@ -574,28 +649,49 @@ AudioDestinationNode::CheckAudioChannelP
 
 void
 AudioDestinationNode::CreateAudioChannelAgent()
 {
   if (mIsOffline || !UseAudioChannelService()) {
     return;
   }
 
+  if (!mEventProxyHelper) {
+    nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(GetOwner());
+    if (target) {
+      // We use a proxy because otherwise the event listerner would hold a
+      // reference of the destination node, and by extension, everything
+      // connected to it.
+      mEventProxyHelper = new EventProxyHandler(this);
+      target->AddSystemEventListener(NS_LITERAL_STRING("visibilitychange"),
+                                     mEventProxyHelper,
+                                     /* useCapture = */ true,
+                                     /* wantsUntrusted = */ false);
+    }
+  }
+
   if (mAudioChannelAgent) {
     mAudioChannelAgent->StopPlaying();
   }
 
   mAudioChannelAgent = new AudioChannelAgent();
   mAudioChannelAgent->InitWithWeakCallback(GetOwner(),
                                            static_cast<int32_t>(mAudioChannel),
                                            this);
 
-  // The AudioChannelAgent must start playing immediately in order to avoid
-  // race conditions with mozinterruptbegin/end events.
-  InputMuted(false);
+  nsCOMPtr<nsIDocShell> docshell = do_GetInterface(GetOwner());
+  if (docshell) {
+    bool isActive = false;
+    docshell->GetIsActive(&isActive);
+    mAudioChannelAgent->SetVisibilityState(isActive);
+
+    // The AudioChannelAgent must start playing immediately in order to avoid
+    // race conditions with mozinterruptbegin/end events.
+    InputMuted(false);
+  }
 }
 
 void
 AudioDestinationNode::NotifyStableState()
 {
   mExtraCurrentTimeUpdatedSinceLastStableState = false;
 }
 
@@ -670,20 +766,19 @@ AudioDestinationNode::InputMuted(bool aM
     return;
   }
 
   if (aMuted) {
     mAudioChannelAgent->StopPlaying();
     return;
   }
 
-  float volume = 0.0;
-  bool muted = true;
-  nsresult rv = mAudioChannelAgent->StartPlaying(&volume, &muted);
+  int32_t state = 0;
+  nsresult rv = mAudioChannelAgent->StartPlaying(&state);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
 
-  WindowVolumeChanged(volume, muted);
+  CanPlayChanged(state);
 }
 
 } // dom namespace
 } // mozilla namespace
--- a/dom/media/webaudio/AudioDestinationNode.h
+++ b/dom/media/webaudio/AudioDestinationNode.h
@@ -4,24 +4,28 @@
  * 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/AudioChannelBinding.h"
 #include "AudioNode.h"
+#include "nsIDOMEventListener.h"
 #include "nsIAudioChannelAgent.h"
+#include "AudioChannelCommon.h"
 
 namespace mozilla {
 namespace dom {
 
 class AudioContext;
+class EventProxyHandler;
 
 class AudioDestinationNode final : public AudioNode
+                                 , public nsIDOMEventListener
                                  , public nsIAudioChannelAgentCallback
                                  , public MainThreadMediaStreamListener
 {
 public:
   // This node type knows what MediaStreamGraph to use based on
   // whether it's in offline mode.
   AudioDestinationNode(AudioContext* aContext,
                        bool aIsOffline,
@@ -52,16 +56,19 @@ public:
 
   void Mute();
   void Unmute();
 
   void StartRendering(Promise* aPromise);
 
   void OfflineShutdown();
 
+  // nsIDOMEventListener - by proxy
+  NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) override;
+
   AudioChannel MozAudioChannelType() const;
   void SetMozAudioChannelType(AudioChannel aValue, ErrorResult& aRv);
 
   virtual void NotifyMainThreadStreamFinished() override;
   void FireOfflineCompletionEvent();
 
   // An amount that should be added to the MediaStream's current time to
   // get the AudioContext.currentTime.
@@ -85,26 +92,27 @@ public:
   void ResolvePromise(AudioBuffer* aRenderedBuffer);
 
 protected:
   virtual ~AudioDestinationNode();
 
 private:
   bool CheckAudioChannelPermissions(AudioChannel aValue);
 
-  void SetCanPlay(float aVolume, bool aMuted);
+  void SetCanPlay(bool aCanPlay);
 
   void NotifyStableState();
   void ScheduleStableStateNotification();
 
   SelfReference<AudioDestinationNode> mOfflineRenderingRef;
   uint32_t mFramesToProduce;
 
   nsCOMPtr<nsIAudioChannelAgent> mAudioChannelAgent;
 
+  nsRefPtr<EventProxyHandler> mEventProxyHelper;
   nsRefPtr<Promise> mOfflineRenderingPromise;
 
   // Audio Channel Type.
   AudioChannel mAudioChannel;
   bool mIsOffline;
   bool mAudioChannelAgentPlaying;
 
   TimeStamp mStartedBlockingDueToBeingOnlyNode;
--- a/dom/media/webaudio/moz.build
+++ b/dom/media/webaudio/moz.build
@@ -13,16 +13,18 @@ TEST_DIRS += ['compiledtest']
 
 MOCHITEST_MANIFESTS += [
     'test/blink/mochitest.ini',
     'test/mochitest.ini',
 ]
 
 MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini']
 
+BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
+
 EXPORTS += [
     'AlignedTArray.h',
     'AudioContext.h',
     'AudioEventTimeline.h',
     'AudioNodeEngine.h',
     'AudioNodeExternalInputStream.h',
     'AudioNodeStream.h',
     'AudioParamTimeline.h',
new file mode 100644
--- /dev/null
+++ b/dom/media/webaudio/test/browser.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+support-files =
+  browser_mozAudioChannel.html
+  browser_mozAudioChannel_muted.html
+
+[browser_mozAudioChannel.js]
+[browser_mozAudioChannel_muted.js]
+skip-if = e10s
new file mode 100644
--- /dev/null
+++ b/dom/media/webaudio/test/browser_mozAudioChannel.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
+<meta charset="utf-8">
+<title>Test for mozinterruptbegin/end in AudioContext</title>
+
+<script type="application/javascript">
+
+  var ac = new AudioContext();
+
+  function createEvent(msg) {
+    var event = document.createEvent('CustomEvent');
+    event.initCustomEvent('testmozchannel', true, true, { msg: msg });
+    dispatchEvent(event);
+  }
+
+  ac.onmozinterruptbegin = function(evt) {
+    createEvent('mozinterruptbegin');
+  }
+
+  ac.addEventListener('mozinterruptend', function() {
+    createEvent('mozinterruptend');
+  }, false);
+
+  var buffer = ac.createBuffer(1, 2048, ac.sampleRate);
+  for (var i = 0; i < 2048; ++i) {
+    buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / ac.sampleRate);
+  }
+
+  var source = ac.createBufferSource();
+  source.buffer = buffer;
+  source.connect(ac.destination);
+  source.loop = true;
+  source.start(0);
+</script>
new file mode 100644
--- /dev/null
+++ b/dom/media/webaudio/test/browser_mozAudioChannel.js
@@ -0,0 +1,80 @@
+/* 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/. */
+
+function whenBrowserLoaded(aBrowser, aCallback) {
+  aBrowser.addEventListener("load", function onLoad(event) {
+    if (event.target == aBrowser.contentDocument) {
+      aBrowser.removeEventListener("load", onLoad, true);
+      executeSoon(aCallback);
+    }
+  }, true);
+}
+
+function whenBrowserUnloaded(aBrowser, aCallback) {
+  aBrowser.addEventListener("unload", function onUnload() {
+    aBrowser.removeEventListener("unload", onUnload, true);
+    executeSoon(aCallback);
+  }, true);
+}
+
+var event;
+var next = function() {}
+
+function eventListener(evt) {
+  info("Event has been received!");
+  is(evt.detail.msg, event, "AudioContext has been received the right event: " + event);
+  next();
+}
+
+function test() {
+
+  waitForExplicitFinish();
+
+  let testURL = "http://mochi.test:8888/browser/" +
+    "dom/media/webaudio/test/browser_mozAudioChannel.html";
+
+  SpecialPowers.pushPrefEnv({"set": [["media.defaultAudioChannel", "content" ],
+                                     ["media.useAudioChannelAPI", true ],
+                                     ["media.useAudioChannelService", true ]]},
+    function() {
+      let tab1 = gBrowser.addTab(testURL);
+      gBrowser.selectedTab = tab1;
+
+      whenBrowserLoaded(tab1.linkedBrowser, function() {
+        let doc = tab1.linkedBrowser.contentDocument;
+        tab1.linkedBrowser.contentWindow.addEventListener('testmozchannel', eventListener, false);
+
+        SpecialPowers.pushPrefEnv({"set": [["media.defaultAudioChannel", "telephony" ]]},
+          function() {
+            event = 'mozinterruptbegin';
+            next = function() {
+              info("Next is called.");
+              event = 'mozinterruptend';
+              next =  function() {
+                info("Next is called again.");
+                tab1.linkedBrowser.contentWindow.removeEventListener('testmozchannel', eventListener);
+                gBrowser.removeTab(tab1);
+                finish();
+              }
+
+              info("Unloading a tab...");
+              whenBrowserUnloaded(tab2.linkedBrowser, function() { info("Tab unloaded."); });
+
+              executeSoon(function() {
+                gBrowser.removeTab(tab2);
+                gBrowser.selectedTab = tab1;
+              });
+            }
+
+            let tab2 = gBrowser.addTab(testURL);
+            gBrowser.selectedTab = tab2;
+
+            info("Loading the tab...");
+            whenBrowserLoaded(tab2.linkedBrowser, function() { info("Tab restored."); });
+          }
+        );
+      });
+    }
+  );
+}
new file mode 100644
--- /dev/null
+++ b/dom/media/webaudio/test/browser_mozAudioChannel_muted.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
+<meta charset="utf-8">
+<title>Test for mozinterruptbegin/end in AudioContext</title>
+
+mozAudioChannelTest = <span id="mozAudioChannelTest">FAIL</span>
+
+<script type="application/javascript">
+
+  var ac = new AudioContext();
+
+  ac.onmozinterruptbegin = function(evt) {
+    document.getElementById("mozAudioChannelTest").innerHTML = "mozinterruptbegin";
+  }
+
+  ac.addEventListener('mozinterruptend', function() {
+    document.getElementById("mozAudioChannelTest").innerHTML = "mozinterruptend";
+  }, false);
+
+  document.getElementById("mozAudioChannelTest").innerHTML = "READY";
+
+</script>
new file mode 100644
--- /dev/null
+++ b/dom/media/webaudio/test/browser_mozAudioChannel_muted.js
@@ -0,0 +1,72 @@
+/* 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/. */
+
+function whenBrowserLoaded(aBrowser, aCallback) {
+  aBrowser.addEventListener("load", function onLoad(event) {
+    if (event.target == aBrowser.contentDocument) {
+      aBrowser.removeEventListener("load", onLoad, true);
+      executeSoon(aCallback);
+    }
+  }, true);
+}
+
+function whenTabRestored(aTab, aCallback) {
+  aTab.addEventListener("SSTabRestored", function onRestored(aEvent) {
+    aTab.removeEventListener("SSTabRestored", onRestored, true);
+    executeSoon(function executeWhenTabRestored() {
+      aCallback();
+    });
+  }, true);
+}
+
+function whenBrowserUnloaded(aBrowser, aCallback) {
+  aBrowser.addEventListener("unload", function onUnload() {
+    aBrowser.removeEventListener("unload", onUnload, true);
+    executeSoon(aCallback);
+  }, true);
+}
+
+function test() {
+
+  waitForExplicitFinish();
+
+  let testURL = "http://mochi.test:8888/browser/" +
+    "dom/media/webaudio/test/browser_mozAudioChannel_muted.html";
+
+  SpecialPowers.pushPrefEnv({"set": [["media.defaultAudioChannel", "content" ],
+                                     ["media.useAudioChannelAPI", true ],
+                                     ["media.useAudioChannelService", true ]]},
+    function() {
+      let tab1 = gBrowser.addTab(testURL);
+      gBrowser.selectedTab = tab1;
+
+      whenBrowserLoaded(tab1.linkedBrowser, function() {
+        let doc = tab1.linkedBrowser.contentDocument;
+        is(doc.getElementById("mozAudioChannelTest").textContent, "READY",
+           "Test is ready to run");
+
+        SpecialPowers.pushPrefEnv({"set": [["media.defaultAudioChannel", "telephony" ]]},
+          function() {
+            let tab2 = gBrowser.duplicateTab(tab1);
+            gBrowser.selectedTab = tab2;
+            whenTabRestored(tab2, function() {
+              is(doc.getElementById("mozAudioChannelTest").textContent, "mozinterruptbegin",
+                 "AudioContext should be muted by the second tab.");
+
+              whenBrowserUnloaded(tab2.linkedBrowser, function() {
+                is(doc.getElementById("mozAudioChannelTest").textContent, "mozinterruptend",
+                   "AudioContext should be muted by the second tab.");
+                gBrowser.removeTab(tab1);
+                finish();
+              });
+
+              gBrowser.removeTab(tab2);
+              gBrowser.selectedTab = tab1;
+            });
+          }
+        );
+      });
+    }
+  );
+}
--- a/dom/speakermanager/SpeakerManager.cpp
+++ b/dom/speakermanager/SpeakerManager.cpp
@@ -196,18 +196,18 @@ SpeakerManager::HandleEvent(nsIDOMEvent*
 
   if (mVisible && mForcespeaker) {
     service->ForceSpeaker(mForcespeaker, mVisible);
   }
   // If an application that has called forcespeaker=true, but no audio is
   // currently playing in the app itself, if application switch to
   // the background, we switch 'speakerforced' to false.
   if (!mVisible && mForcespeaker) {
-    nsRefPtr<AudioChannelService> audioChannelService =
-      AudioChannelService::GetOrCreate();
+    AudioChannelService* audioChannelService =
+      AudioChannelService::GetOrCreateAudioChannelService();
     if (audioChannelService && !audioChannelService->AnyAudioChannelIsActive()) {
       service->ForceSpeaker(false, mVisible);
     }
   }
   return NS_OK;
 }
 
 void
--- a/dom/speakermanager/SpeakerManagerService.cpp
+++ b/dom/speakermanager/SpeakerManagerService.cpp
@@ -189,20 +189,23 @@ SpeakerManagerService::SpeakerManagerSer
 {
   MOZ_COUNT_CTOR(SpeakerManagerService);
   if (XRE_IsParentProcess()) {
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     if (obs) {
       obs->AddObserver(this, "ipc:content-shutdown", false);
     }
   }
-  nsRefPtr<AudioChannelService> audioChannelService =
-    AudioChannelService::GetOrCreate();
-  audioChannelService->RegisterSpeakerManager(this);
+  AudioChannelService* audioChannelService =
+    AudioChannelService::GetOrCreateAudioChannelService();
+  if (audioChannelService) {
+    audioChannelService->RegisterSpeakerManager(this);
+  }
 }
 
 SpeakerManagerService::~SpeakerManagerService()
 {
   MOZ_COUNT_DTOR(SpeakerManagerService);
-  nsRefPtr<AudioChannelService> audioChannelService =
-    AudioChannelService::GetOrCreate();
-  audioChannelService->UnregisterSpeakerManager(this);
+  AudioChannelService* audioChannelService =
+    AudioChannelService::GetOrCreateAudioChannelService();
+  if (audioChannelService)
+    audioChannelService->UnregisterSpeakerManager(this);
 }
--- a/dom/speakermanager/SpeakerManagerServiceChild.cpp
+++ b/dom/speakermanager/SpeakerManagerServiceChild.cpp
@@ -91,26 +91,26 @@ SpeakerManagerServiceChild::SetAudioChan
   for (uint32_t i = 0; i < mRegisteredSpeakerManagers.Length(); i++) {
     mRegisteredSpeakerManagers[i]->SetAudioChannelActive(aIsActive);
   }
 }
 
 SpeakerManagerServiceChild::SpeakerManagerServiceChild()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  nsRefPtr<AudioChannelService> audioChannelService = AudioChannelService::GetOrCreate();
+  AudioChannelService* audioChannelService = AudioChannelService::GetOrCreateAudioChannelService();
   if (audioChannelService) {
     audioChannelService->RegisterSpeakerManager(this);
   }
   MOZ_COUNT_CTOR(SpeakerManagerServiceChild);
 }
 
 SpeakerManagerServiceChild::~SpeakerManagerServiceChild()
 {
-  nsRefPtr<AudioChannelService> audioChannelService = AudioChannelService::GetOrCreate();
+  AudioChannelService* audioChannelService = AudioChannelService::GetOrCreateAudioChannelService();
   if (audioChannelService) {
     audioChannelService->UnregisterSpeakerManager(this);
   }
   MOZ_COUNT_DTOR(SpeakerManagerServiceChild);
 }
 
 void
 SpeakerManagerServiceChild::Notify()
--- a/dom/system/gonk/AudioChannelManager.cpp
+++ b/dom/system/gonk/AudioChannelManager.cpp
@@ -126,17 +126,17 @@ void
 AudioChannelManager::NotifyVolumeControlChannelChanged()
 {
   nsCOMPtr<nsIDocShell> docshell = do_GetInterface(GetOwner());
   NS_ENSURE_TRUE_VOID(docshell);
 
   bool isActive = false;
   docshell->GetIsActive(&isActive);
 
-  nsRefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
+  AudioChannelService* service = AudioChannelService::GetOrCreateAudioChannelService();
   if (isActive) {
     service->SetDefaultVolumeControlChannel(mVolumeChannel, isActive);
   } else {
     service->SetDefaultVolumeControlChannel(-1, isActive);
   }
 }
 
 NS_IMETHODIMP
--- a/dom/system/gonk/AudioManager.cpp
+++ b/dom/system/gonk/AudioManager.cpp
@@ -351,17 +351,17 @@ AudioManager::HandleAudioChannelProcessC
   // be called again and therefore the audio manager sets the
   // PHONE_STATE_IN_COMMUNICATION audio state.
 
   if ((mPhoneState == PHONE_STATE_IN_CALL) ||
       (mPhoneState == PHONE_STATE_RINGTONE)) {
     return;
   }
 
-  nsRefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
+  AudioChannelService *service = AudioChannelService::GetOrCreateAudioChannelService();
   MOZ_ASSERT(service);
 
   bool telephonyChannelIsActive = service->TelephonyChannelIsActive();
   telephonyChannelIsActive ? SetPhoneState(PHONE_STATE_IN_COMMUNICATION) :
                              SetPhoneState(PHONE_STATE_NORMAL);
 }
 
 nsresult
@@ -646,23 +646,18 @@ AudioManager::SetPhoneState(int32_t aSta
     if (aState == PHONE_STATE_IN_CALL) {
       // Telephony doesn't be paused by any other channels.
       mPhoneAudioAgent->Init(nullptr, (int32_t)AudioChannel::Telephony, nullptr);
     } else {
       mPhoneAudioAgent->Init(nullptr, (int32_t)AudioChannel::Ringer, nullptr);
     }
 
     // Telephony can always play.
-    float volume = 0.0;
-    bool muted = true;
-
-    nsresult rv = mPhoneAudioAgent->StartPlaying(&volume, &muted);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
+    int32_t canPlay;
+    mPhoneAudioAgent->StartPlaying(&canPlay);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 AudioManager::SetForceForUse(int32_t aUsage, int32_t aForce)
 {
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -52,17 +52,16 @@ WEBIDL_FILES = [
     'BatteryManager.webidl',
     'BeforeAfterKeyboardEvent.webidl',
     'BeforeUnloadEvent.webidl',
     'BiquadFilterNode.webidl',
     'Blob.webidl',
     'BoxObject.webidl',
     'BroadcastChannel.webidl',
     'BrowserElement.webidl',
-    'BrowserElementAudioChannel.webidl',
     'BrowserElementDictionaries.webidl',
     'Cache.webidl',
     'CacheStorage.webidl',
     'CallsList.webidl',
     'CameraCapabilities.webidl',
     'CameraControl.webidl',
     'CameraManager.webidl',
     'CameraUtil.webidl',
--- a/layout/build/nsLayoutModule.cpp
+++ b/layout/build/nsLayoutModule.cpp
@@ -626,17 +626,17 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(Geol
 #define NS_AUDIOCHANNEL_SERVICE_CID \
   { 0xf712e983, 0x048a, 0x443f, { 0x88, 0x02, 0xfc, 0xc3, 0xd9, 0x27, 0xce, 0xac }}
 
 #define NS_DATASTORE_SERVICE_CID \
   { 0x0d4285fe, 0xf1b3, 0x49fa, { 0xbc, 0x51, 0xa4, 0xa8, 0x3f, 0x0a, 0xaf, 0x85 }}
 
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsGeolocationService, nsGeolocationService::GetGeolocationService)
 
-NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(AudioChannelService, AudioChannelService::GetOrCreate)
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(AudioChannelService, AudioChannelService::GetOrCreateAudioChannelService)
 
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(DataStoreService, DataStoreService::GetOrCreate)
 
 #ifdef MOZ_WEBSPEECH_TEST_BACKEND
 NS_GENERIC_FACTORY_CONSTRUCTOR(FakeSpeechRecognitionService)
 #endif
 #ifdef MOZ_WEBSPEECH_POCKETSPHINX
 NS_GENERIC_FACTORY_CONSTRUCTOR(PocketSphinxSpeechRecognitionService)