Bug 1328058 - part1 : notify block-stop event when the tab was resumed. draft
authorAlastor Wu <alwu@mozilla.com>
Fri, 03 Feb 2017 14:47:08 +0800
changeset 478947 222c6fa9f76ddb9bde06877e3e2b6651521bfb31
parent 478946 4c8bc7e91353a1d4e656e72c375045bfc227c5be
child 478948 8e5857585f53650f1505979ccb863039a1d9db7e
push id44105
push useralwu@mozilla.com
push dateSat, 04 Feb 2017 07:04:06 +0000
bugs1328058
milestone54.0a1
Bug 1328058 - part1 : notify block-stop event when the tab was resumed. In present design, the tab would hide the unblocking icon when receives the audio-playback event, but it means we can't hide the icon if the media isn't audible. For example, we won't show the unblocking icon for audio with audio track, but we show the icon for audio with silent audio track which can only be detected after starting decoding. In this case, we can't receive the audio-playback after resuming that media. Therefore, we should dispatch the different event to notify tab UI that the tab has already been resumed. MozReview-Commit-ID: 3xCWQU7nVCl
dom/audiochannel/AudioChannelService.cpp
dom/audiochannel/AudioChannelService.h
dom/base/nsGlobalWindow.cpp
dom/base/nsPIDOMWindow.h
toolkit/content/browser-content.js
toolkit/content/widgets/browser.xml
--- a/dom/audiochannel/AudioChannelService.cpp
+++ b/dom/audiochannel/AudioChannelService.cpp
@@ -1054,16 +1054,39 @@ AudioChannelService::RefreshAgentsAudioF
     AudioChannelWindow* winData = iter.GetNext();
     if (winData->mOwningAudioFocus) {
       winData->AudioFocusChanged(aAgent, aActive);
     }
   }
 }
 
 void
+AudioChannelService::NotifyMediaResumedFromBlock(nsPIDOMWindowOuter* aWindow)
+{
+  MOZ_ASSERT(aWindow);
+  MOZ_ASSERT(aWindow->IsOuterWindow());
+
+  nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetScriptableTop();
+  if (!topWindow) {
+    return;
+  }
+
+  AudioChannelWindow* winData = GetWindowData(topWindow->WindowID());
+  if (!winData) {
+    return;
+  }
+
+  if (!winData->mShouldSendBlockStopEvent) {
+    return;
+  }
+
+  winData->NotifyMediaBlockStop(aWindow);
+}
+
+void
 AudioChannelService::AudioChannelWindow::RequestAudioFocus(AudioChannelAgent* aAgent)
 {
   MOZ_ASSERT(aAgent);
 
   // Don't need to check audio focus for window-less agent.
   if (!aAgent->Window()) {
     return;
   }
@@ -1259,16 +1282,36 @@ AudioChannelService::AudioChannelWindow:
   RemoveAgentAndReduceAgentsNum(aAgent);
   AudioCapturedChanged(aAgent, AudioCaptureState::eNotCapturing);
   AudioAudibleChanged(aAgent,
                       AudibleState::eNotAudible,
                       AudibleChangedReasons::ePauseStateChanged);
 }
 
 void
+AudioChannelService::AudioChannelWindow::NotifyMediaBlockStop(nsPIDOMWindowOuter* aWindow)
+{
+  mShouldSendBlockStopEvent = false;
+  // Can't use raw pointer for lamba variable capturing, use smart ptr.
+  nsCOMPtr<nsPIDOMWindowOuter> window = aWindow;
+  NS_DispatchToCurrentThread(NS_NewRunnableFunction([window] () -> void {
+      nsCOMPtr<nsIObserverService> observerService =
+        services::GetObserverService();
+      if (NS_WARN_IF(!observerService)) {
+        return;
+      }
+
+      observerService->NotifyObservers(ToSupports(window),
+                                       "audio-playback",
+                                       u"blockStop");
+    })
+  );
+}
+
+void
 AudioChannelService::AudioChannelWindow::AppendAgentAndIncreaseAgentsNum(AudioChannelAgent* aAgent)
 {
   MOZ_ASSERT(aAgent);
   MOZ_ASSERT(!mAgents.Contains(aAgent));
 
   int32_t channel = aAgent->AudioChannelType();
   mAgents.AppendElement(aAgent);
 
@@ -1411,21 +1454,24 @@ AudioChannelService::AudioChannelWindow:
     return;
   }
 
   MOZ_ASSERT(window->IsOuterWindow());
   if (window->GetMediaSuspend() != nsISuspendedTypes::SUSPENDED_BLOCK) {
     return;
   }
 
-  NS_DispatchToCurrentThread(NS_NewRunnableFunction([window] () -> void {
-      nsCOMPtr<nsIObserverService> observerService =
-        services::GetObserverService();
-      if (NS_WARN_IF(!observerService)) {
-        return;
-      }
+  if (!mShouldSendBlockStopEvent) {
+      mShouldSendBlockStopEvent = true;
+      NS_DispatchToCurrentThread(NS_NewRunnableFunction([window] () -> void {
+        nsCOMPtr<nsIObserverService> observerService =
+          services::GetObserverService();
+        if (NS_WARN_IF(!observerService)) {
+          return;
+        }
 
-      observerService->NotifyObservers(ToSupports(window),
-                                       "audio-playback",
-                                       u"block");
-    })
-  );
+        observerService->NotifyObservers(ToSupports(window),
+                                         "audio-playback",
+                                         u"blockStart");
+      })
+    );
+  }
 }
--- a/dom/audiochannel/AudioChannelService.h
+++ b/dom/audiochannel/AudioChannelService.h
@@ -196,16 +196,18 @@ public:
 
   void Notify(uint64_t aWindowID);
 
   void ChildStatusReceived(uint64_t aChildID, bool aTelephonyChannel,
                            bool aContentOrNormalChannel, bool aAnyChannel);
 
   void NotifyCreatedNewAgent(AudioChannelAgent* aAgent);
 
+  void NotifyMediaResumedFromBlock(nsPIDOMWindowOuter* aWindow);
+
 private:
   AudioChannelService();
   ~AudioChannelService();
 
   void RefreshAgents(nsPIDOMWindowOuter* aWindow,
                      std::function<void(AudioChannelAgent*)> aFunc);
 
   static void CreateServiceIfNeeded();
@@ -240,41 +242,47 @@ private:
 
   class AudioChannelWindow final
   {
   public:
     explicit AudioChannelWindow(uint64_t aWindowID)
       : mWindowID(aWindowID)
       , mIsAudioCaptured(false)
       , mOwningAudioFocus(!AudioChannelService::IsEnableAudioCompeting())
+      , mShouldSendBlockStopEvent(false)
     {
       // Workaround for bug1183033, system channel type can always playback.
       mChannels[(int16_t)AudioChannel::System].mMuted = false;
     }
 
     void AudioFocusChanged(AudioChannelAgent* aNewPlayingAgent, bool aActive);
     void AudioAudibleChanged(AudioChannelAgent* aAgent,
                              AudibleState aAudible,
                              AudibleChangedReasons aReason);
 
     void AppendAgent(AudioChannelAgent* aAgent, AudibleState aAudible);
     void RemoveAgent(AudioChannelAgent* aAgent);
 
+    void NotifyMediaBlockStop(nsPIDOMWindowOuter* aWindow);
+
     uint64_t mWindowID;
     bool mIsAudioCaptured;
     AudioChannelConfig mChannels[NUMBER_OF_AUDIO_CHANNELS];
 
     // Raw pointer because the AudioChannelAgent must unregister itself.
     nsTObserverArray<AudioChannelAgent*> mAgents;
     nsTObserverArray<AudioChannelAgent*> mAudibleAgents;
 
     // Owning audio focus when the window starts playing audible sound, and
     // lose audio focus when other windows starts playing.
     bool mOwningAudioFocus;
 
+    // If we've dispatched "blockStart" event, we must dispatch another event
+    // "blockStop" when the window is resumed from suspend-block.
+    bool mShouldSendBlockStopEvent;
   private:
     void AudioCapturedChanged(AudioChannelAgent* aAgent,
                               AudioCaptureState aCapture);
 
     void AppendAudibleAgentIfNotContained(AudioChannelAgent* aAgent,
                                           AudibleChangedReasons aReason);
     void RemoveAudibleAgentIfContained(AudioChannelAgent* aAgent,
                                        AudibleChangedReasons aReason);
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -4225,22 +4225,35 @@ void
 nsPIDOMWindowOuter::SetMediaSuspend(SuspendTypes aSuspend)
 {
   if (IsInnerWindow()) {
     mOuterWindow->SetMediaSuspend(aSuspend);
     return;
   }
 
   if (!IsDisposableSuspend(aSuspend)) {
+    MaybeNotifyMediaResumedFromBlock(aSuspend);
     mMediaSuspend = aSuspend;
   }
 
   RefreshMediaElementsSuspend(aSuspend);
 }
 
+void
+nsPIDOMWindowOuter::MaybeNotifyMediaResumedFromBlock(SuspendTypes aSuspend)
+{
+  if (mMediaSuspend == nsISuspendedTypes::SUSPENDED_BLOCK &&
+      aSuspend == nsISuspendedTypes::NONE_SUSPENDED) {
+    RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
+    if (service) {
+      service->NotifyMediaResumedFromBlock(GetOuterWindow());
+    }
+  }
+}
+
 bool
 nsPIDOMWindowOuter::GetAudioMuted() const
 {
   if (IsInnerWindow()) {
     return mOuterWindow->GetAudioMuted();
   }
 
   return mAudioMuted;
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -893,16 +893,17 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsPIDOMWin
 // NB: It's very very important that these two classes have identical vtables
 // and memory layout!
 class nsPIDOMWindowOuter : public nsPIDOMWindow<mozIDOMWindowProxy>
 {
 protected:
   void RefreshMediaElementsVolume();
   void RefreshMediaElementsSuspend(SuspendTypes aSuspend);
   bool IsDisposableSuspend(SuspendTypes aSuspend) const;
+  void MaybeNotifyMediaResumedFromBlock(SuspendTypes aSuspend);
 
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_PIDOMWINDOWOUTER_IID)
 
   static nsPIDOMWindowOuter* From(mozIDOMWindowProxy* aFrom) {
     return static_cast<nsPIDOMWindowOuter*>(aFrom);
   }
 
--- a/toolkit/content/browser-content.js
+++ b/toolkit/content/browser-content.js
@@ -1011,18 +1011,20 @@ var AudioPlaybackListener = {
         break;
     }
   },
 
   observe(subject, topic, data) {
     if (topic === "audio-playback") {
       if (subject && subject.top == global.content) {
         let name = "AudioPlayback:";
-        if (data === "block") {
-          name += "Block";
+        if (data === "blockStart") {
+          name += "BlockStart";
+        } else if (data === "blockStop") {
+          name += "BlockStop";
         } else {
           name += (data === "active") ? "Start" : "Stop";
         }
         sendAsyncMessage(name);
       }
     } else if (topic == "AudioFocusChanged" || topic == "MediaControl") {
       this.handleMediaControlMessage(data);
     }
--- a/toolkit/content/widgets/browser.xml
+++ b/toolkit/content/widgets/browser.xml
@@ -695,47 +695,55 @@
          readonly="true"/>
 
       <method name="audioPlaybackStarted">
         <body>
           <![CDATA[
             let event = document.createEvent("Events");
             event.initEvent("DOMAudioPlaybackStarted", true, false);
             this.dispatchEvent(event);
-            if (this._audioBlocked) {
-              this._audioBlocked = false;
-              event = document.createEvent("Events");
-              event.initEvent("DOMAudioPlaybackBlockStopped", true, false);
-              this.dispatchEvent(event);
-            }
           ]]>
         </body>
       </method>
 
       <method name="audioPlaybackStopped">
         <body>
           <![CDATA[
             let event = document.createEvent("Events");
             event.initEvent("DOMAudioPlaybackStopped", true, false);
             this.dispatchEvent(event);
           ]]>
         </body>
       </method>
 
-      <method name="audioPlaybackBlocked">
+      <method name="audioPlaybackBlockStarted">
         <body>
           <![CDATA[
             this._audioBlocked = true;
             let event = document.createEvent("Events");
             event.initEvent("DOMAudioPlaybackBlockStarted", true, false);
             this.dispatchEvent(event);
           ]]>
         </body>
       </method>
 
+      <method name="audioPlaybackBlockStopped">
+        <body>
+          <![CDATA[
+            if (!this._audioBlocked) {
+              return;
+            }
+            this._audioBlocked = false;
+            let event = document.createEvent("Events");
+            event.initEvent("DOMAudioPlaybackBlockStopped", true, false);
+            this.dispatchEvent(event);
+          ]]>
+        </body>
+      </method>
+
       <field name="_audioMuted">false</field>
       <property name="audioMuted"
                 onget="return this._audioMuted;"
                 readonly="true"/>
 
 
       <field name="_audioBlocked">false</field>
       <property name="audioBlocked"
@@ -954,17 +962,18 @@
           }
 
           if (this.messageManager) {
             this.messageManager.addMessageListener("PopupBlocking:UpdateBlockedPopups", this);
             this.messageManager.addMessageListener("Autoscroll:Start", this);
             this.messageManager.addMessageListener("Autoscroll:Cancel", this);
             this.messageManager.addMessageListener("AudioPlayback:Start", this);
             this.messageManager.addMessageListener("AudioPlayback:Stop", this);
-            this.messageManager.addMessageListener("AudioPlayback:Block", this);
+            this.messageManager.addMessageListener("AudioPlayback:BlockStart", this);
+            this.messageManager.addMessageListener("AudioPlayback:BlockStop", this);
 
             if (this.hasAttribute("selectmenulist")) {
               this.messageManager.addMessageListener("Forms:ShowDropDown", this);
               this.messageManager.addMessageListener("Forms:HideDropDown", this);
             }
 
           }
         ]]>
@@ -1052,18 +1061,21 @@
               this._autoScrollPopup.hidePopup();
               break;
             case "AudioPlayback:Start":
               this.audioPlaybackStarted();
               break;
             case "AudioPlayback:Stop":
               this.audioPlaybackStopped();
               break;
-            case "AudioPlayback:Block":
-              this.audioPlaybackBlocked();
+            case "AudioPlayback:BlockStart":
+              this.audioPlaybackBlockStarted();
+              break;
+            case "AudioPlayback:BlockStop":
+              this.audioPlaybackBlockStopped();
               break;
             case "Forms:ShowDropDown": {
               if (!this._selectParentHelper) {
                 this._selectParentHelper =
                   Cu.import("resource://gre/modules/SelectParentHelper.jsm", {}).SelectParentHelper;
               }
 
               let menulist = document.getElementById(this.getAttribute("selectmenulist"));