[mq]: channel-service-activated.patch
authorChris Pearce <cpearce@mozilla.com>
Mon, 14 Sep 2015 16:24:36 +1200
changeset 583904 0dfe11de72dc284c4261571950600883073842a0
parent 580695 dd2a1d737a64d9a3f23714ec5cc623ec8933b51f
child 583905 42043a52b04232485dab6cd33a18949caffe88d8
push id88096
push usercpearce@mozilla.com
push dateMon, 14 Sep 2015 04:28:25 +0000
treeherdertry@42043a52b042 [default view] [failures only]
milestone43.0a1
[mq]: channel-service-activated.patch
dom/audiochannel/AudioChannelAgent.cpp
dom/audiochannel/AudioChannelAgent.h
dom/audiochannel/AudioChannelService.cpp
dom/audiochannel/AudioChannelService.h
dom/audiochannel/nsIAudioChannelAgent.idl
dom/audiochannel/nsIAudioChannelService.idl
dom/base/nsGlobalWindow.cpp
dom/base/nsPIDOMWindow.h
dom/html/HTMLMediaElement.cpp
dom/html/HTMLMediaElement.h
dom/media/webaudio/AudioDestinationNode.cpp
dom/media/webspeech/synth/nsSpeechTask.cpp
dom/plugins/base/nsNPAPIPluginInstance.cpp
--- a/dom/audiochannel/AudioChannelAgent.cpp
+++ b/dom/audiochannel/AudioChannelAgent.cpp
@@ -32,29 +32,36 @@ NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(AudioChannelAgent)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(AudioChannelAgent)
 
 AudioChannelAgent::AudioChannelAgent()
   : mAudioChannelType(AUDIO_AGENT_CHANNEL_ERROR)
   , mInnerWindowID(0)
   , mIsRegToService(false)
+  , mIsPlaying(false)
 {
 }
 
 AudioChannelAgent::~AudioChannelAgent()
 {
   Shutdown();
 }
 
 void
 AudioChannelAgent::Shutdown()
 {
+  if (mIsPlaying) {
+    NotifyStoppedPlaying(nsIAudioChannelAgent::AUDIO_AGENT_NOTIFY);
+  }
   if (mIsRegToService) {
-    NotifyStoppedPlaying(nsIAudioChannelAgent::AUDIO_AGENT_NOTIFY);
+    nsRefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
+    if (service) {
+      service->UnregisterAudioChannelAgent(this);
+    }
   }
 }
 
 NS_IMETHODIMP AudioChannelAgent::GetAudioChannelType(int32_t *aAudioChannelType)
 {
   *aAudioChannelType = mAudioChannelType;
   return NS_OK;
 }
@@ -125,88 +132,138 @@ AudioChannelAgent::InitInternal(nsIDOMWi
   mAudioChannelType = aChannelType;
 
   if (aUseWeakRef) {
     mWeakCallback = do_GetWeakReference(aCallback);
   } else {
     mCallback = aCallback;
   }
 
+  // Register, so that we get callbacks if blocked status changes.
+  nsRefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
+  if (service == nullptr || mIsRegToService) {
+    return NS_ERROR_FAILURE;
+  }
+  service->RegisterAudioChannelAgent(this);
+  mIsRegToService = true;
+  // TODO: unregister.
+
+  return NS_OK;
+}
+
+bool AudioChannelAgent::IsPlaybackBlocked()
+{
+  if (!mWindow) {
+    return true;
+  }
+  nsCOMPtr<nsIDOMWindow> topWindow;
+  mWindow->GetScriptableTop(getter_AddRefs(topWindow));
+  if (!topWindow) {
+    return true;
+  }
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(topWindow);
+  if (!window) {
+    return true;
+  }
+  window = window->GetOuterWindow();
+  return !window->EverInForeground();
+}
+
+NS_IMETHODIMP AudioChannelAgent::IsPlaybackBlocked(bool* aBlocked)
+{
+  MOZ_ASSERT(aBlocked);
+  *aBlocked = IsPlaybackBlocked();
   return NS_OK;
 }
 
 NS_IMETHODIMP AudioChannelAgent::NotifyStartedPlaying(uint32_t aNotifyPlayback,
                                                       float *aVolume,
                                                       bool* aMuted)
 {
   MOZ_ASSERT(aVolume);
   MOZ_ASSERT(aMuted);
+  MOZ_ASSERT(mIsRegToService);
 
   // Window-less AudioChannelAgents are muted by default.
   if (!mWindow) {
     *aVolume = 0;
     *aMuted = true;
     return NS_OK;
   }
 
   nsRefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
   if (mAudioChannelType == AUDIO_AGENT_CHANNEL_ERROR ||
-      service == nullptr || mIsRegToService) {
+      service == nullptr || mIsPlaying) {
     return NS_ERROR_FAILURE;
   }
 
-  service->RegisterAudioChannelAgent(this, aNotifyPlayback,
+  service->NotifyPlaybackStarted(this, aNotifyPlayback,
     static_cast<AudioChannel>(mAudioChannelType));
 
-  service->GetState(mWindow, mAudioChannelType, aVolume, aMuted);
+  bool blocked = false;
+  service->GetState(mWindow, mAudioChannelType, aVolume, aMuted, &blocked);
+  MOZ_ASSERT(!blocked);
 
-  mIsRegToService = true;
+  mIsPlaying = true;
+
   return NS_OK;
 }
 
 NS_IMETHODIMP AudioChannelAgent::NotifyStoppedPlaying(uint32_t aNotifyPlayback)
 {
   if (mAudioChannelType == AUDIO_AGENT_CHANNEL_ERROR ||
-      !mIsRegToService) {
+    !mIsPlaying) {
     return NS_ERROR_FAILURE;
   }
 
   nsRefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
-  service->UnregisterAudioChannelAgent(this, aNotifyPlayback);
-  mIsRegToService = false;
+  service->NotifyPlaybackStopped(this, aNotifyPlayback);
+  mIsPlaying = false;
   return NS_OK;
 }
 
 already_AddRefed<nsIAudioChannelAgentCallback>
 AudioChannelAgent::GetCallback()
 {
   nsCOMPtr<nsIAudioChannelAgentCallback> callback = mCallback;
-  if (!callback) {
+  if (!callback && mWeakCallback) {
     callback = do_QueryReferent(mWeakCallback);
   }
   return callback.forget();
 }
 
 void
 AudioChannelAgent::WindowVolumeChanged()
 {
   nsCOMPtr<nsIAudioChannelAgentCallback> callback = GetCallback();
   if (!callback) {
     return;
   }
 
   float volume = 1.0;
   bool muted = false;
+  bool blocked = false;
 
   nsRefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
-  service->GetState(mWindow, mAudioChannelType, &volume, &muted);
+  service->GetState(mWindow, mAudioChannelType, &volume, &muted, &blocked);
 
   callback->WindowVolumeChanged(volume, muted);
 }
 
+void
+AudioChannelAgent::WindowPlaybackBlockedChanged()
+{
+  nsCOMPtr<nsIAudioChannelAgentCallback> callback = GetCallback();
+  if (!callback) {
+    return;
+  }
+
+  callback->WindowPlaybackBlockedChanged(IsPlaybackBlocked());
+}
+
 uint64_t
 AudioChannelAgent::WindowID() const
 {
   return mWindow ? mWindow->WindowID() : 0;
 }
 
 void
 AudioChannelAgent::WindowAudioCaptureChanged(uint64_t aInnerWindowID)
--- a/dom/audiochannel/AudioChannelAgent.h
+++ b/dom/audiochannel/AudioChannelAgent.h
@@ -28,18 +28,20 @@ class AudioChannelAgent : public nsIAudi
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_NSIAUDIOCHANNELAGENT
 
   NS_DECL_CYCLE_COLLECTION_CLASS(AudioChannelAgent)
 
   AudioChannelAgent();
 
+  bool IsPlaybackBlocked();
   void WindowVolumeChanged();
   void WindowAudioCaptureChanged(uint64_t aInnerWindowID);
+  void WindowPlaybackBlockedChanged();
 
   nsPIDOMWindow* Window() const
   {
     return mWindow;
   }
 
   uint64_t WindowID() const;
 
@@ -59,15 +61,16 @@ private:
   nsCOMPtr<nsPIDOMWindow> mWindow;
   nsCOMPtr<nsIAudioChannelAgentCallback> mCallback;
 
   nsWeakPtr mWeakCallback;
 
   int32_t mAudioChannelType;
   uint64_t mInnerWindowID;
   bool mIsRegToService;
+  bool mIsPlaying;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 
 #endif
--- a/dom/audiochannel/AudioChannelService.cpp
+++ b/dom/audiochannel/AudioChannelService.cpp
@@ -232,51 +232,75 @@ AudioChannelService::AudioChannelService
                                "dom.audiochannel.mutedByDefault");
 }
 
 AudioChannelService::~AudioChannelService()
 {
 }
 
 void
-AudioChannelService::RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
-                                               uint32_t aNotifyPlayback,
-                                               AudioChannel aChannel)
+AudioChannelService::RegisterAudioChannelAgent(AudioChannelAgent* aAgent)
 {
   uint64_t windowID = aAgent->WindowID();
   AudioChannelWindow* winData = GetWindowData(windowID);
   if (!winData) {
     winData = new AudioChannelWindow(windowID);
     mWindows.AppendElement(winData);
   }
 
   MOZ_ASSERT(!winData->mAgents.Contains(aAgent));
   winData->mAgents.AppendElement(aAgent);
+}
+
+void
+AudioChannelService::UnregisterAudioChannelAgent(AudioChannelAgent* aAgent)
+{
+  AudioChannelWindow* winData = GetWindowData(aAgent->WindowID());
+  if (!winData) {
+    return;
+  }
+
+  if (winData->mAgents.Contains(aAgent)) {
+    // aAgent can be freed after this call.
+    winData->mAgents.RemoveElement(aAgent);
+  }
+
+}
+
+void
+AudioChannelService::NotifyPlaybackStarted(AudioChannelAgent* aAgent,
+                                           uint32_t aNotifyPlayback,
+                                           AudioChannel aChannel)
+{
+  uint64_t windowID = aAgent->WindowID();
+  AudioChannelWindow* winData = GetWindowData(windowID);
+  MOZ_ASSERT(winData); // Should already be registered.
+  MOZ_ASSERT(winData->mAgents.Contains(aAgent));
 
   ++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);
   }
 
   // If this is the first agent for this window, we must notify the observers.
   if (aNotifyPlayback == nsIAudioChannelAgent::AUDIO_AGENT_NOTIFY &&
-      winData->mAgents.Length() == 1) {
+    (++winData->mNotifyCount) == 1) {
     nsRefPtr<MediaPlaybackRunnable> runnable =
       new MediaPlaybackRunnable(aAgent->Window(), true /* active */);
     NS_DispatchToCurrentThread(runnable);
   }
 
   MaybeSendStatusUpdate();
 }
 
 void
-AudioChannelService::UnregisterAudioChannelAgent(AudioChannelAgent* aAgent,
-                                                 uint32_t aNotifyPlayback)
+AudioChannelService::NotifyPlaybackStopped(AudioChannelAgent* aAgent,
+                                           uint32_t aNotifyPlayback)
 {
   AudioChannelWindow* winData = GetWindowData(aAgent->WindowID());
   if (!winData) {
     return;
   }
 
   if (winData->mAgents.Contains(aAgent)) {
     int32_t channel = aAgent->AudioChannelType();
@@ -299,50 +323,55 @@ AudioChannelService::UnregisterAudioChan
   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 (aNotifyPlayback == nsIAudioChannelAgent::AUDIO_AGENT_NOTIFY &&
-      winData->mAgents.IsEmpty()) {
+      (--winData->mNotifyCount) == 0) {
     nsRefPtr<MediaPlaybackRunnable> runnable =
       new MediaPlaybackRunnable(aAgent->Window(), false /* active */);
     NS_DispatchToCurrentThread(runnable);
   }
 
   MaybeSendStatusUpdate();
 }
 
 void
 AudioChannelService::GetState(nsPIDOMWindow* aWindow, uint32_t aAudioChannel,
-                              float* aVolume, bool* aMuted)
+                              float* aVolume, bool* aMuted, bool* aBlocked)
 {
   MOZ_ASSERT(!aWindow || aWindow->IsOuterWindow());
-  MOZ_ASSERT(aVolume && aMuted);
+  MOZ_ASSERT(aVolume && aMuted && aBlocked);
   MOZ_ASSERT(aAudioChannel < NUMBER_OF_AUDIO_CHANNELS);
 
   *aVolume = 1.0;
   *aMuted = false;
 
   if (!aWindow || !aWindow->IsOuterWindow()) {
     return;
   }
 
+  *aBlocked = //!Preferences::GetBool("media.block-play-until-visible", false) ||
+    !aWindow->EverInForeground();
+
   AudioChannelWindow* winData = nullptr;
   nsCOMPtr<nsPIDOMWindow> window = aWindow;
 
   // 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 {
     winData = GetWindowData(window->WindowID());
     if (winData) {
       *aVolume *= winData->mChannels[aAudioChannel].mVolume;
       *aMuted = *aMuted || winData->mChannels[aAudioChannel].mMuted;
+      *aBlocked = //!Preferences::GetBool("media.block-play-until-visible", false) ||
+                  !window->EverInForeground();
     }
 
     *aVolume *= window->GetAudioVolume();
     *aMuted = *aMuted || window->GetAudioMuted();
 
     nsCOMPtr<nsIDOMWindow> win;
     window->GetScriptableParent(getter_AddRefs(win));
     if (window == win) {
@@ -526,16 +555,42 @@ AudioChannelService::Observe(nsISupports
 
     RemoveChildStatus(childID);
   }
 
   return NS_OK;
 }
 
 void
+AudioChannelService::RefreshAgentsPlaybackBlocked(nsPIDOMWindow* aWindow)
+{
+  MOZ_ASSERT(aWindow);
+  MOZ_ASSERT(aWindow->IsOuterWindow());
+
+  nsCOMPtr<nsIDOMWindow> topWindow;
+  aWindow->GetScriptableTop(getter_AddRefs(topWindow));
+  nsCOMPtr<nsPIDOMWindow> pTopWindow = do_QueryInterface(topWindow);
+  if (!pTopWindow) {
+    return;
+  }
+
+  AudioChannelWindow* winData = GetWindowData(pTopWindow->WindowID());
+  if (!winData) {
+    return;
+  }
+
+  nsTObserverArray<AudioChannelAgent*>::ForwardIterator
+    iter(winData->mAgents);
+  while (iter.HasMore()) {
+    iter.GetNext()->WindowPlaybackBlockedChanged(); // TODO: Roll into refresh below somehow...
+  }
+}
+
+
+void
 AudioChannelService::RefreshAgentsVolume(nsPIDOMWindow* aWindow)
 {
   MOZ_ASSERT(aWindow);
   MOZ_ASSERT(aWindow->IsOuterWindow());
 
   nsCOMPtr<nsIDOMWindow> topWindow;
   aWindow->GetScriptableTop(getter_AddRefs(topWindow));
   nsCOMPtr<nsPIDOMWindow> pTopWindow = do_QueryInterface(topWindow);
--- a/dom/audiochannel/AudioChannelService.h
+++ b/dom/audiochannel/AudioChannelService.h
@@ -40,37 +40,40 @@ public:
    * 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 bool IsAudioChannelMutedByDefault();
 
+  void RegisterAudioChannelAgent(AudioChannelAgent* aAgent);
+  void UnregisterAudioChannelAgent(AudioChannelAgent* aAgent);
+
   /**
    * Any audio channel agent that starts playing should register itself to
    * this service, sharing the AudioChannel.
    */
-  void RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
-                                 uint32_t aNotifyPlayback,
-                                 AudioChannel aChannel);
+  void NotifyPlaybackStarted(AudioChannelAgent* aAgent,
+                             uint32_t aNotifyPlayback,
+                             AudioChannel aChannel);
 
   /**
    * Any audio channel agent that stops playing should unregister itself to
    * this service.
    */
-  void UnregisterAudioChannelAgent(AudioChannelAgent* aAgent,
-                                   uint32_t aNotifyPlayback);
+  void NotifyPlaybackStopped(AudioChannelAgent* aAgent,
+                             uint32_t aNotifyPlayback);
 
   /**
    * Return the state to indicate this audioChannel for his window should keep
    * playing/muted.
    */
   void GetState(nsPIDOMWindow* aWindow, uint32_t aChannel,
-                float* aVolume, bool* aMuted);
+                float* aVolume, bool* aMuted, bool* aBlocked);
 
   /* Methods for the BrowserElementAudioChannel */
   float GetAudioChannelVolume(nsPIDOMWindow* aWindow, AudioChannel aChannel);
 
   void SetAudioChannelVolume(nsPIDOMWindow* aWindow, AudioChannel aChannel,
                              float aVolume);
 
   bool GetAudioChannelMuted(nsPIDOMWindow* aWindow, AudioChannel aChannel);
@@ -107,16 +110,17 @@ public:
 
   // This method needs to know the inner window that wants to capture audio. We
   // group agents per top outer window, but we can have multiple innerWindow per
   // top outerWindow (subiframes, etc.) and we have to identify all the agents
   // just for a particular innerWindow.
   void RefreshAgentsCapture(nsPIDOMWindow* aWindow,
                             uint64_t aInnerWindowID);
 
+  void RefreshAgentsPlaybackBlocked(nsPIDOMWindow* aWindow);
 
 #ifdef MOZ_WIDGET_GONK
   void RegisterSpeakerManager(SpeakerManagerService* aSpeakerManager)
   {
     if (!mSpeakerManager.Contains(aSpeakerManager)) {
       mSpeakerManager.AppendElement(aSpeakerManager);
     }
   }
@@ -170,23 +174,26 @@ private:
 
     uint32_t mNumberOfAgents;
   };
 
   struct AudioChannelWindow final
   {
     explicit AudioChannelWindow(uint64_t aWindowID)
       : mWindowID(aWindowID)
+      , mNotifyCount(0)
     {}
 
     uint64_t mWindowID;
     AudioChannelConfig mChannels[NUMBER_OF_AUDIO_CHANNELS];
 
     // Raw pointer because the AudioChannelAgent must unregister itself.
     nsTObserverArray<AudioChannelAgent*> mAgents;
+
+    int32_t mNotifyCount;
   };
 
   AudioChannelWindow*
   GetOrCreateWindowData(nsPIDOMWindow* aWindow);
 
   AudioChannelWindow*
   GetWindowData(uint64_t aWindowID) const;
 
--- a/dom/audiochannel/nsIAudioChannelAgent.idl
+++ b/dom/audiochannel/nsIAudioChannelAgent.idl
@@ -1,45 +1,47 @@
 /* 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(5fe83b24-38b9-4901-a4a1-d1bd57d3fe18)]
+[uuid(0e8fe51b-9377-4f8f-9c71-1dec11bcc4a2)]
 interface nsIAudioChannelAgentCallback : nsISupports
 {
   /**
-   * Notified when the window volume/mute is changed
+   * Notified when the window volume/mute is changed or window first activated.
    */
   void windowVolumeChanged(in float aVolume, in bool aMuted);
 
   /**
    * Notified when the capture state is changed.
    */
   void windowAudioCaptureChanged();
+  
+  void windowPlaybackBlockedChanged(in bool aBlocked);
 };
 
 /**
  * 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(62e0037c-9786-4b79-b986-27111f6e553b)]
+[uuid(94dbee0c-912c-4a09-85fc-4c39ec90528c)]
 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;
@@ -123,9 +125,11 @@ interface nsIAudioChannelAgent : nsISupp
    *   or AUDIO_CHANNEL_DONT_NOTIFY.
    *
    * Note : even if notifyStartedPlaying() returned false, the agent would
    * still be registered with the audio channel service and receive callbacks
    * for status changes. So notifyStoppedPlaying must still eventually be
    * called to unregister the agent with the channel service.
    */
   void notifyStoppedPlaying(in unsigned long notifyPlayback);
+  
+  void isPlaybackBlocked(out bool blocked);
 };
--- a/dom/audiochannel/nsIAudioChannelService.idl
+++ b/dom/audiochannel/nsIAudioChannelService.idl
@@ -2,17 +2,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/. */
 
 #include "nsISupports.idl"
 
 interface nsIDOMWindow;
 
-[scriptable, builtinclass, uuid(323e5472-b8f4-4288-b1b9-53c7c54bbbe8)]
+[scriptable, builtinclass, uuid(ac6e2285-c69e-4d0f-abbd-79400c7adaf4)]
 interface nsIAudioChannelService : nsISupports
 {
   float getAudioChannelVolume(in nsIDOMWindow window,
                               in unsigned short audioChannel);
 
   void setAudioChannelVolume(in nsIDOMWindow window,
                              in unsigned short audioChannel,
                              in float volume);
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -566,17 +566,18 @@ nsPIDOMWindow::nsPIDOMWindow(nsPIDOMWind
   mMayHavePointerEnterLeaveEventListener(false),
   mIsModalContentWindow(false),
   mIsActive(false), mIsBackground(false),
   mAudioMuted(false), mAudioVolume(1.0), mAudioCaptured(false),
   mDesktopModeViewport(false), mInnerWindow(nullptr),
   mOuterWindow(aOuterWindow),
   // Make sure no actual window ends up with mWindowID == 0
   mWindowID(NextWindowID()), mHasNotifiedGlobalCreated(false),
-  mMarkedCCGeneration(0), mServiceWorkersTestingEnabled(false)
+  mMarkedCCGeneration(0), mServiceWorkersTestingEnabled(false),
+  mEverInForeground(false)
  {}
 
 nsPIDOMWindow::~nsPIDOMWindow() {}
 
 // DialogValueHolder CC goop.
 NS_IMPL_CYCLE_COLLECTION(DialogValueHolder, mValue)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DialogValueHolder)
@@ -3761,16 +3762,28 @@ nsPIDOMWindow::SetAudioCapture(bool aCap
   mAudioCaptured = aCapture;
 
   nsRefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
   service->RefreshAgentsCapture(GetOuterWindow(), mWindowID);
 
   return NS_OK;
 }
 
+void
+nsPIDOMWindow::SetIsBackground(bool aIsBackground)
+{
+  MOZ_ASSERT(IsOuterWindow());
+  mIsBackground = aIsBackground;
+  if (!aIsBackground && !mEverInForeground) {
+    mEverInForeground = true;
+    nsRefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
+    service->RefreshAgentsPlaybackBlocked(GetOuterWindow());
+  }
+}
+
 // nsISpeechSynthesisGetter
 
 #ifdef MOZ_WEBSPEECH
 SpeechSynthesis*
 nsGlobalWindow::GetSpeechSynthesis(ErrorResult& aError)
 {
   MOZ_RELEASE_ASSERT(IsInnerWindow());
 
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -101,26 +101,27 @@ public:
     mDesktopModeViewport = aDesktopModeViewport;
   }
   bool IsDesktopModeViewport() const
   {
     MOZ_ASSERT(IsOuterWindow());
     return mDesktopModeViewport;
   }
 
-  virtual void SetIsBackground(bool aIsBackground)
-  {
-    MOZ_ASSERT(IsOuterWindow());
-    mIsBackground = aIsBackground;
-  }
+  virtual void SetIsBackground(bool aIsBackground);
   bool IsBackground()
   {
     MOZ_ASSERT(IsOuterWindow());
     return mIsBackground;
   }
+  bool EverInForeground() const
+  {
+    MOZ_ASSERT(IsOuterWindow());
+    return mEverInForeground;
+  }
 
   mozilla::dom::EventTarget* GetChromeEventHandler() const
   {
     return mChromeEventHandler;
   }
 
   // Outer windows only.
   virtual void SetChromeEventHandler(mozilla::dom::EventTarget* aChromeEventHandler) = 0;
@@ -853,16 +854,18 @@ protected:
   // the (chrome|content)-document-global-created notification.
   bool mHasNotifiedGlobalCreated;
 
   uint32_t mMarkedCCGeneration;
 
   // Let the service workers plumbing know that some feature are enabled while
   // testing.
   bool mServiceWorkersTestingEnabled;
+
+  bool mEverInForeground;
 };
 
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsPIDOMWindow, NS_PIDOMWINDOW_IID)
 
 #ifdef MOZILLA_INTERNAL_API
 PopupControlState
 PushPopupControlState(PopupControlState aState, bool aForce);
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -124,21 +124,21 @@ class MOZ_STACK_CLASS AutoNotifyAudioCha
 public:
   AutoNotifyAudioChannelAgent(mozilla::dom::HTMLMediaElement* aElement,
                               bool aNotify
                               MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
     : mElement(aElement)
     , mShouldNotify(aNotify)
   {
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
-    if (mShouldNotify) {
-      // The audio channel agent may not exist now.
-      if (mElement->MaybeCreateAudioChannelAgent()) {
-        mElement->NotifyAudioChannelAgent(false);
-      }
+    // Note: *Always* create the AudioChannelAgent, in case playback start
+    // is waiting for the window to unblock and send a notification via
+    // the AudioChannelAgent to begin.
+    if (mElement->MaybeCreateAudioChannelAgent() && mShouldNotify) {
+      mElement->NotifyAudioChannelAgent(false);
     }
   }
   ~AutoNotifyAudioChannelAgent()
   {
     if (mShouldNotify) {
       // The audio channel agent is destroyed at this point.
       if (mElement->MaybeCreateAudioChannelAgent()) {
         mElement->NotifyAudioChannelAgent(true);
@@ -2080,17 +2080,16 @@ HTMLMediaElement::HTMLMediaElement(alrea
     mSrcStreamIsPlaying(false),
     mMediaSecurityVerified(false),
     mCORSMode(CORS_NONE),
     mIsEncrypted(false),
     mDownloadSuspendedByCache(false, "HTMLMediaElement::mDownloadSuspendedByCache"),
     mAudioChannelVolume(1.0),
     mPlayingThroughTheAudioChannel(false),
     mDisableVideo(false),
-    mPlayBlockedBecauseHidden(false),
     mElementInTreeState(ELEMENT_NOT_INTREE),
     mHasUserInteraction(false)
 {
   if (!gMediaElementLog) {
     gMediaElementLog = PR_NewLogModule("nsMediaElement");
   }
   if (!gMediaElementEventsLog) {
     gMediaElementEventsLog = PR_NewLogModule("nsMediaElementEvents");
@@ -2193,16 +2192,26 @@ HTMLMediaElement::ResetConnectionState()
   SetCurrentTime(0);
   FireTimeUpdate(false);
   DispatchAsyncEvent(NS_LITERAL_STRING("ended"));
   ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_EMPTY);
   ChangeDelayLoadStatus(false);
   ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_NOTHING);
 }
 
+bool
+HTMLMediaElement::IsPlaybackBlockedByDoc()
+{
+  nsCOMPtr<nsIDOMWindow> topWindow;
+  OwnerDoc()->GetInnerWindow()->GetScriptableTop(getter_AddRefs(topWindow));
+  nsCOMPtr<nsPIDOMWindow> win(do_QueryInterface(topWindow));
+  bool blocked = !win->EverInForeground();
+  return blocked;
+}
+
 void
 HTMLMediaElement::Play(ErrorResult& aRv)
 {
   // Prevent media element from being auto-started by a script when
   // media.autoplay.enabled=false
   if (!mHasUserInteraction
       && !IsAutoplayEnabled()
       && !EventStateManager::IsHandlingUserInput()
@@ -2219,31 +2228,29 @@ HTMLMediaElement::Play(ErrorResult& aRv)
 
   if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_EMPTY) {
     DoLoad();
   }
   if (mSuspendedForPreloadNone) {
     ResumeLoad(PRELOAD_ENOUGH);
   }
 
-  if (Preferences::GetBool("media.block-play-until-visible", false) &&
-      !nsContentUtils::IsCallerChrome() &&
-      OwnerDoc()->Hidden()) {
-    LOG(LogLevel::Debug, ("%p Blocked playback because owner hidden.", this));
-    mPlayBlockedBecauseHidden = true;
-    return;
-  }
+  //if (IsPlaybackBlockedByAudioChannelAgent()) {
+  //  LOG(LogLevel::Debug, ("%p playback blocked by AudioChannelAgent.", this));
+  //  return;
+  //}
 
   // Even if we just did Load() or ResumeLoad(), we could already have a decoder
   // here if we managed to clone an existing decoder.
   if (mDecoder) {
     if (mDecoder->IsEndedOrShutdown()) {
       SetCurrentTime(0);
     }
-    if (!mPausedForInactiveDocumentOrChannel) {
+    if (!mPausedForInactiveDocumentOrChannel && !IsPlaybackBlockedByAudioChannelAgent()) {
+      MOZ_ASSERT(!IsPlaybackBlockedByDoc());
       aRv = mDecoder->Play();
       if (aRv.Failed()) {
         return;
       }
     }
   }
 
   if (mCurrentPlayRangeStart == -1.0) {
@@ -2856,17 +2863,18 @@ nsresult HTMLMediaElement::FinishDecoder
   AddMediaElementToURITable();
 
   // We may want to suspend the new stream now.
   // This will also do an AddRemoveSelfReference.
   NotifyOwnerDocumentActivityChangedInternal();
 
   if (!mPaused) {
     SetPlayedOrSeeked(true);
-    if (!mPausedForInactiveDocumentOrChannel) {
+    if (!mPausedForInactiveDocumentOrChannel &&
+        !IsPlaybackBlockedByAudioChannelAgent()) {
       rv = mDecoder->Play();
     }
   }
 
   if (NS_FAILED(rv)) {
     ShutdownDecoder();
   }
 
@@ -3749,52 +3757,47 @@ void HTMLMediaElement::ChangeNetworkStat
   // Changing mNetworkState affects AddRemoveSelfReference().
   AddRemoveSelfReference();
 }
 
 bool HTMLMediaElement::CanActivateAutoplay()
 {
   // For stream inputs, we activate autoplay on HAVE_NOTHING because
   // this element itself might be blocking the stream from making progress by
-  // being paused. We also activate autopaly when playing a media source since
+  // being paused. We also activate autoplay when playing a media source since
   // the data download is controlled by the script and there is no way to
   // evaluate MediaDecoder::CanPlayThrough().
-  return !mPausedForInactiveDocumentOrChannel &&
+  return !IsPlaybackBlockedByAudioChannelAgent() &&
+         !mPausedForInactiveDocumentOrChannel &&
          mAutoplaying &&
          mPaused &&
          ((mDecoder && mReadyState >= nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA) ||
           mSrcStream || mMediaSource) &&
          HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay) &&
          mAutoplayEnabled &&
          !IsEditable();
 }
 
 void HTMLMediaElement::CheckAutoplayDataReady()
 {
   if (!CanActivateAutoplay()) {
     return;
   }
 
-  if (Preferences::GetBool("media.block-play-until-visible", false) &&
-      OwnerDoc()->Hidden()) {
-    LOG(LogLevel::Debug, ("%p Blocked autoplay because owner hidden.", this));
-    mPlayBlockedBecauseHidden = true;
-    return;
-  }
-
   mPaused = false;
   // We changed mPaused which can affect AddRemoveSelfReference
   AddRemoveSelfReference();
   UpdateSrcMediaStreamPlaying();
 
   if (mDecoder) {
     SetPlayedOrSeeked(true);
     if (mCurrentPlayRangeStart == -1.0) {
       mCurrentPlayRangeStart = CurrentTime();
     }
+    MOZ_ASSERT(!IsPlaybackBlockedByDoc());
     mDecoder->Play();
   } else if (mSrcStream) {
     SetPlayedOrSeeked(true);
   }
   DispatchAsyncEvent(NS_LITERAL_STRING("play"));
 
 }
 
@@ -3987,41 +3990,45 @@ void HTMLMediaElement::SuspendOrResumeEl
       if (mMediaSource) {
         ReportMSETelemetry();
 #ifdef MOZ_EME
         ReportEMETelemetry();
 #endif
       }
 
 #ifdef MOZ_EME
-      // For EME content, force destruction of the CDM client (and CDM
-      // instance if this is the last client for that CDM instance) and
-      // the CDM's decoder. This ensures the CDM gets reliable and prompt
-      // shutdown notifications, as it may have book-keeping it needs
-      // to do on shutdown.
-      if (mMediaKeys) {
-        mMediaKeys->Shutdown();
-        mMediaKeys = nullptr;
-        if (mDecoder) {
-          ShutdownDecoder();
+      // TODO: Unsure whether IsActive() is right here.
+      if (!OwnerDoc()->IsActive()) {
+        // For EME content, force destruction of the CDM client (and CDM
+        // instance if this is the last client for that CDM instance) and
+        // the CDM's decoder. This ensures the CDM gets reliable and prompt
+        // shutdown notifications, as it may have book-keeping it needs
+        // to do on shutdown.
+        if (mMediaKeys) {
+          mMediaKeys->Shutdown();
+          mMediaKeys = nullptr;
+          if (mDecoder) {
+            ShutdownDecoder();
+          }
         }
       }
 #endif
       if (mDecoder) {
         mDecoder->Pause();
         mDecoder->Suspend();
       }
       mEventDeliveryPaused = aSuspendEvents;
     } else {
 #ifdef MOZ_EME
-      MOZ_ASSERT(!mMediaKeys);
+      //MOZ_ASSERT(!mMediaKeys);
 #endif
       if (mDecoder) {
         mDecoder->Resume(false);
         if (!mPaused && !mDecoder->IsEndedOrShutdown()) {
+          MOZ_ASSERT(!IsPlaybackBlockedByDoc());
           mDecoder->Play();
         }
       }
       if (mEventDeliveryPaused) {
         mEventDeliveryPaused = false;
         DispatchPendingMediaEvents();
       }
     }
@@ -4069,36 +4076,29 @@ HTMLMediaElement::NotifyOwnerDocumentAct
 {
   nsIDocument* ownerDoc = OwnerDoc();
   if (mDecoder && !IsBeingDestroyed()) {
     mDecoder->SetElementVisibility(!ownerDoc->Hidden());
     mDecoder->NotifyOwnerActivityChanged();
   }
 
   bool pauseElement = !IsActive();
-#ifdef PAUSE_MEDIA_ELEMENT_FROM_AUDIOCHANNEL
   // Only pause the element when we start playing. If we pause without playing
   // audio, the resource loading would be affected unexpectedly. For example,
   // the media element is muted by default, but we don't want this behavior
   // interrupting the loading process.
   if (mAudioChannelAgent) {
+#ifdef PAUSE_MEDIA_ELEMENT_FROM_AUDIOCHANNEL
     pauseElement |= ComputedMuted();
-  }
 #endif
+    pauseElement |= IsPlaybackBlockedByAudioChannelAgent();
+  }
 
   SuspendOrResumeElement(pauseElement, !IsActive());
 
-  if (!mPausedForInactiveDocumentOrChannel &&
-      mPlayBlockedBecauseHidden &&
-      !OwnerDoc()->Hidden()) {
-    LOG(LogLevel::Debug, ("%p Resuming playback now that owner doc is visble.", this));
-    mPlayBlockedBecauseHidden = false;
-    Play();
-  }
-
   AddRemoveSelfReference();
 
   return pauseElement;
 }
 
 void HTMLMediaElement::AddRemoveSelfReference()
 {
   // XXX we could release earlier here in many situations if we examined
@@ -4493,16 +4493,19 @@ NS_IMETHODIMP HTMLMediaElement::SetMozPr
 ImageContainer* HTMLMediaElement::GetImageContainer()
 {
   VideoFrameContainer* container = GetVideoFrameContainer();
   return container ? container->GetImageContainer() : nullptr;
 }
 
 nsresult HTMLMediaElement::UpdateChannelMuteState(float aVolume, bool aMuted)
 {
+  LOG(LogLevel::Debug, ("%p UpdateChannelMuteState(volume=%f, muted=%d) hidden=%d isActive=%d",
+    this, aVolume, aMuted, OwnerDoc()->Hidden(), IsActive()));
+
   if (mAudioChannelVolume != aVolume) {
     mAudioChannelVolume = aVolume;
     SetVolumeInternal();
   }
 
   // We have to mute this channel.
   if (aMuted && !ComputedMuted()) {
     SetMutedInternal(mMuted | MUTED_BY_AUDIO_CHANNEL);
@@ -4534,28 +4537,50 @@ HTMLMediaElement::MaybeCreateAudioChanne
     MOZ_ASSERT(mAudioChannelAgent);
     mAudioChannelAgent->InitWithWeakCallback(OwnerDoc()->GetInnerWindow(),
                                              static_cast<int32_t>(mAudioChannel),
                                              this);
   }
   return true;
 }
 
+bool
+HTMLMediaElement::IsPlaybackBlockedByAudioChannelAgent()
+{
+  if (!MaybeCreateAudioChannelAgent()) {
+    return false;
+  }
+  bool blocked = false;
+  mAudioChannelAgent->IsPlaybackBlocked(&blocked);
+  return blocked;
+}
+
 void
 HTMLMediaElement::UpdateAudioChannelPlayingState()
 {
   bool playingThroughTheAudioChannel =
      (!mPaused &&
       !Muted() &&
       std::fabs(Volume()) > 1e-7 &&
+      !IsPlaybackBlockedByAudioChannelAgent() &&
       (HasAttr(kNameSpaceID_None, nsGkAtoms::loop) ||
        (mReadyState >= nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA &&
         !IsPlaybackEnded()) ||
        mPlayingThroughTheAudioChannelBeforeSeek));
+
+  LOG(LogLevel::Debug, ("%p UpdateAudioChannelPlayingState paused=%d muted=%d "
+                        "vol=%d loop=%d readyState=%d ended=%d playb4seek=%d blocked=%d",
+                        this, !!mPaused, Muted(), (std::fabs(Volume()) > 1e-7),
+                        HasAttr(kNameSpaceID_None, nsGkAtoms::loop),
+                        mReadyState.Ref(), IsPlaybackEnded(), mPlayingThroughTheAudioChannelBeforeSeek,
+                        IsPlaybackBlockedByAudioChannelAgent()));
+
   if (playingThroughTheAudioChannel != mPlayingThroughTheAudioChannel) {
+    LOG(LogLevel::Debug, ("%p UpdateAudioChannelPlayingState mPlayingThroughTheAudioChannel=%d",
+        this, playingThroughTheAudioChannel));
     mPlayingThroughTheAudioChannel = playingThroughTheAudioChannel;
 
     // If we are not playing, we don't need to create a new audioChannelAgent.
     if (!mAudioChannelAgent && !mPlayingThroughTheAudioChannel) {
        return;
     }
 
     if (MaybeCreateAudioChannelAgent()) {
@@ -4586,16 +4611,27 @@ HTMLMediaElement::NotifyAudioChannelAgen
     mAudioChannelAgent->NotifyStartedPlaying(notify, &volume, &muted);
     WindowVolumeChanged(volume, muted);
   } else {
     mAudioChannelAgent->NotifyStoppedPlaying(notify);
     mAudioChannelAgent = nullptr;
   }
 }
 
+NS_IMETHODIMP HTMLMediaElement::WindowPlaybackBlockedChanged(bool aBlocked)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  //bool pauseElement = !IsActive() || aBlocked;
+  //SuspendOrResumeElement(pauseElement, false);
+  NotifyOwnerDocumentActivityChanged();
+
+  return NS_OK;
+}
+
 NS_IMETHODIMP HTMLMediaElement::WindowVolumeChanged(float aVolume, bool aMuted)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   UpdateChannelMuteState(aVolume, aMuted);
 
 #ifdef PAUSE_MEDIA_ELEMENT_FROM_AUDIOCHANNEL
   mPaused.SetCanPlay(!aMuted);
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -1028,16 +1028,19 @@ protected:
   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);
 
+  bool IsPlaybackBlockedByDoc();
+  bool IsPlaybackBlockedByAudioChannelAgent();
+
   // 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
   void UpdateAudioChannelPlayingState();
@@ -1408,21 +1411,16 @@ protected:
   // 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;
 
-  // True if we blocked either a play() call or autoplay because the
-  // media's owner doc was not visible. Only enforced when the pref
-  // media.block-play-until-visible=true.
-  bool mPlayBlockedBecauseHidden;
-
   // An agent used to join audio channel service.
   nsCOMPtr<nsIAudioChannelAgent> mAudioChannelAgent;
 
   nsRefPtr<TextTrackManager> mTextTrackManager;
 
   nsRefPtr<AudioTrackList> mAudioTrackList;
 
   nsRefPtr<VideoTrackList> mVideoTrackList;
--- a/dom/media/webaudio/AudioDestinationNode.cpp
+++ b/dom/media/webaudio/AudioDestinationNode.cpp
@@ -493,16 +493,22 @@ AudioDestinationNode::SetCanPlay(float a
   if (!mStream) {
     return;
   }
 
   mStream->SetTrackEnabled(AudioNodeStream::AUDIO_TRACK, !aMuted);
   mStream->SetAudioOutputVolume(&gWebAudioOutputKey, aVolume);
 }
 
+NS_IMETHODIMP AudioDestinationNode::WindowPlaybackBlockedChanged(bool aBlocked)
+{
+  // TODO: zen me?
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 AudioDestinationNode::WindowVolumeChanged(float aVolume, bool aMuted)
 {
   if (aMuted != mAudioChannelAgentPlaying) {
     mAudioChannelAgentPlaying = aMuted;
 
     if (UseAudioChannelAPI()) {
       Context()->DispatchTrustedEvent(
--- a/dom/media/webspeech/synth/nsSpeechTask.cpp
+++ b/dom/media/webspeech/synth/nsSpeechTask.cpp
@@ -689,16 +689,23 @@ nsSpeechTask::DestroyAudioChannelAgent()
 NS_IMETHODIMP
 nsSpeechTask::WindowVolumeChanged(float aVolume, bool aMuted)
 {
   SetAudioOutputVolume(aMuted ? 0.0 : mVolume * aVolume);
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsSpeechTask::WindowPlaybackBlockedChanged(bool aBlocked)
+{
+  // TODO: What?
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsSpeechTask::WindowAudioCaptureChanged()
 {
   // This is not supported yet.
   return NS_OK;
 }
 
 void
 nsSpeechTask::SetAudioOutputVolume(float aVolume)
--- a/dom/plugins/base/nsNPAPIPluginInstance.cpp
+++ b/dom/plugins/base/nsNPAPIPluginInstance.cpp
@@ -1814,16 +1814,24 @@ nsNPAPIPluginInstance::GetOrCreateAudioC
   }
 
   nsCOMPtr<nsIAudioChannelAgent> agent = mAudioChannelAgent;
   agent.forget(aAgent);
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsNPAPIPluginInstance::WindowPlaybackBlockedChanged(bool aBlocked)
+{
+  nsresult rv = SetMuted(aBlocked);
+  NS_WARN_IF(NS_FAILED(rv));
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsNPAPIPluginInstance::WindowVolumeChanged(float aVolume, bool aMuted)
 {
   // We just support mute/unmute
   nsresult rv = SetMuted(aMuted);
   NS_WARN_IF(NS_FAILED(rv));
   return rv;
 }