Bug 1308153 - part1 : notify tabbrowser when the tab was blocked. r=baku,jaws
authorAlastor Wu <alwu@mozilla.com>
Fri, 11 Nov 2016 10:42:35 +0800
changeset 352060 daaf3d17df3229983b8b825453ce0f3fe5ad008a
parent 352059 7ef35cccfd7b37a69354f08bfa4d118e4fcc4dd3
child 352061 0dcd1140565dca7d936d0d625e1f03cad4d1bb3c
push id6795
push userjlund@mozilla.com
push dateMon, 23 Jan 2017 14:19:46 +0000
treeherdermozilla-esr52@76101b503191 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku, jaws
bugs1308153, 1308399
milestone52.0a1
Bug 1308153 - part1 : notify tabbrowser when the tab was blocked. r=baku,jaws We need to notify tabbrowser about media-blocking so that we can show the unblocking tab icon. See bug1308399 for more UX details. MozReview-Commit-ID: E25lEhZLCZk
browser/base/content/tabbrowser.xml
dom/audiochannel/AudioChannelService.cpp
dom/audiochannel/AudioChannelService.h
toolkit/content/browser-content.js
toolkit/content/widgets/browser.xml
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -469,16 +469,32 @@
             if (!browser.tabModalPromptBox) {
               browser.tabModalPromptBox = new TabModalPromptBox(browser);
             }
             return browser.tabModalPromptBox;
           ]]>
         </body>
       </method>
 
+      <method name="getTabFromAudioEvent">
+        <parameter name="aEvent"/>
+        <body>
+        <![CDATA[
+          if (!Services.prefs.getBoolPref("browser.tabs.showAudioPlayingIcon") ||
+              !aEvent.isTrusted) {
+            return null;
+          }
+
+          var browser = aEvent.originalTarget;
+          var tab = this.getTabForBrowser(browser);
+          return tab;
+        ]]>
+        </body>
+      </method>
+
       <method name="_callProgressListeners">
         <parameter name="aBrowser"/>
         <parameter name="aMethod"/>
         <parameter name="aArguments"/>
         <parameter name="aCallGlobalListeners"/>
         <parameter name="aCallTabsListeners"/>
         <body><![CDATA[
           var rv = true;
@@ -5059,24 +5075,20 @@
           }
 
           tab.removeAttribute("soundplaying");
           this.setIcon(tab, icon, browser.contentPrincipal);
         ]]>
       </handler>
       <handler event="DOMAudioPlaybackStarted">
         <![CDATA[
-          if (!Services.prefs.getBoolPref("browser.tabs.showAudioPlayingIcon") ||
-              !event.isTrusted)
+          var tab = getTabFromAudioEvent(event)
+          if (!tab) {
             return;
-
-          var browser = event.originalTarget;
-          var tab = this.getTabForBrowser(browser);
-          if (!tab)
-            return;
+          }
 
           clearTimeout(tab._soundPlayingAttrRemovalTimer);
           tab._soundPlayingAttrRemovalTimer = 0;
 
           let modifiedAttrs = [];
           if (tab.hasAttribute("soundplaying-scheduledremoval")) {
             tab.removeAttribute("soundplaying-scheduledremoval");
             modifiedAttrs.push("soundplaying-scheduledremoval");
@@ -5087,40 +5099,56 @@
             modifiedAttrs.push("soundplaying");
           }
 
           this._tabAttrModified(tab, modifiedAttrs);
         ]]>
       </handler>
       <handler event="DOMAudioPlaybackStopped">
         <![CDATA[
-          if (!Services.prefs.getBoolPref("browser.tabs.showAudioPlayingIcon") ||
-              !event.isTrusted)
+          var tab = getTabFromAudioEvent(event)
+          if (!tab) {
             return;
-
-          var browser = event.originalTarget;
-          var tab = this.getTabForBrowser(browser);
-          if (!tab)
-            return;
+          }
 
           if (tab.hasAttribute("soundplaying")) {
             let removalDelay = Services.prefs.getIntPref("browser.tabs.delayHidingAudioPlayingIconMS");
 
             tab.style.setProperty("--soundplaying-removal-delay", `${removalDelay - 300}ms`);
             tab.setAttribute("soundplaying-scheduledremoval", "true");
             this._tabAttrModified(tab, ["soundplaying-scheduledremoval"]);
 
             tab._soundPlayingAttrRemovalTimer = setTimeout(() => {
               tab.removeAttribute("soundplaying-scheduledremoval");
               tab.removeAttribute("soundplaying");
               this._tabAttrModified(tab, ["soundplaying", "soundplaying-scheduledremoval"]);
             }, removalDelay);
           }
         ]]>
       </handler>
+      <handler event="DOMAudioPlaybackBlockStarted">
+        <![CDATA[
+          var tab = getTabFromAudioEvent(event)
+          if (!tab) {
+            return;
+          }
+
+          // TODO : implement media-blocking icon in next patch.
+        ]]>
+      </handler>
+      <handler event="DOMAudioPlaybackBlockStopped">
+        <![CDATA[
+          var tab = getTabFromAudioEvent(event)
+          if (!tab) {
+            return;
+          }
+
+          // TODO : implement media-blocking icon in next patch.
+        ]]>
+      </handler>
     </handlers>
   </binding>
 
   <binding id="tabbrowser-tabbox"
            extends="chrome://global/content/bindings/tabbox.xml#tabbox">
     <implementation>
       <property name="tabs" readonly="true"
                 onget="return document.getBindingParent(this).tabContainer;"/>
--- a/dom/audiochannel/AudioChannelService.cpp
+++ b/dom/audiochannel/AudioChannelService.cpp
@@ -1246,16 +1246,17 @@ AudioChannelService::AudioChannelWindow:
   AudioCapturedChanged(aAgent, AudioCaptureState::eCapturing);
   if (aAudible) {
     AudioAudibleChanged(aAgent,
                         AudibleState::eAudible,
                         AudibleChangedReasons::eDataAudibleChanged);
   } else if (IsEnableAudioCompetingForAllAgents() && !aAudible) {
     NotifyAudioCompetingChanged(aAgent, true);
   }
+  MaybeNotifyMediaBlocked(aAgent);
 }
 
 void
 AudioChannelService::AudioChannelWindow::RemoveAgent(AudioChannelAgent* aAgent)
 {
   MOZ_ASSERT(aAgent);
 
   RemoveAgentAndReduceAgentsNum(aAgent);
@@ -1394,8 +1395,35 @@ AudioChannelService::AudioChannelWindow:
                                                              AudioChannel aChannel,
                                                              bool aActive)
 {
   RefPtr<NotifyChannelActiveRunnable> runnable =
     new NotifyChannelActiveRunnable(aWindowID, aChannel, aActive);
   DebugOnly<nsresult> rv = NS_DispatchToCurrentThread(runnable);
   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToCurrentThread failed");
 }
+
+void
+AudioChannelService::AudioChannelWindow::MaybeNotifyMediaBlocked(AudioChannelAgent* aAgent)
+{
+  nsCOMPtr<nsPIDOMWindowOuter> window = aAgent->Window();
+  if (!window) {
+    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;
+      }
+
+      observerService->NotifyObservers(ToSupports(window),
+                                       "audio-playback",
+                                       u"block");
+    })
+  );
+}
--- a/dom/audiochannel/AudioChannelService.h
+++ b/dom/audiochannel/AudioChannelService.h
@@ -293,16 +293,17 @@ private:
     bool IsLastAudibleAgent() const;
 
     void NotifyAudioAudibleChanged(nsPIDOMWindowOuter* aWindow,
                                    AudibleState aAudible,
                                    AudibleChangedReasons aReason);
 
     void NotifyChannelActive(uint64_t aWindowID, AudioChannel aChannel,
                              bool aActive);
+    void MaybeNotifyMediaBlocked(AudioChannelAgent* aAgent);
 
     void RequestAudioFocus(AudioChannelAgent* aAgent);
     void NotifyAudioCompetingChanged(AudioChannelAgent* aAgent, bool aActive);
 
     uint32_t GetCompetingBehavior(AudioChannelAgent* aAgent,
                                   int32_t aIncomingChannelType,
                                   bool aIncomingChannelActive) const;
     bool IsAgentInvolvingInAudioCompeting(AudioChannelAgent* aAgent) const;
--- a/toolkit/content/browser-content.js
+++ b/toolkit/content/browser-content.js
@@ -1008,17 +1008,21 @@ var AudioPlaybackListener = {
         break;
     }
   },
 
   observe(subject, topic, data) {
     if (topic === "audio-playback") {
       if (subject && subject.top == global.content) {
         let name = "AudioPlayback:";
-        name += (data === "active") ? "Start" : "Stop";
+        if (data === "block") {
+          name += "Block";
+        } else {
+          name += (data === "active") ? "Start" : "Stop";
+        }
         sendAsyncMessage(name);
       }
     } else if (topic == "AudioFocusChanged" || topic == "MediaControl") {
       this.handleMediaControlMessage(data);
     }
   },
 
   receiveMessage(msg) {
--- a/toolkit/content/widgets/browser.xml
+++ b/toolkit/content/widgets/browser.xml
@@ -685,35 +685,58 @@
          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">
+        <body>
+          <![CDATA[
+            this._audioBlocked = true;
+            let event = document.createEvent("Events");
+            event.initEvent("DOMAudioPlaybackBlockStarted", 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"
+                onget="return this._audioBlocked;"
+                readonly="true"/>
+
       <method name="mute">
         <body>
           <![CDATA[
             this._audioMuted = true;
             this.messageManager.sendAsyncMessage("AudioPlayback",
                                                  {type: "mute"});
           ]]>
         </body>
@@ -753,27 +776,32 @@
                                                  {type: "mediaControlStopped"});
           ]]>
         </body>
       </method>
 
       <method name="blockMedia">
         <body>
           <![CDATA[
+            this._audioBlocked = true;
             this.messageManager.sendAsyncMessage("AudioPlayback",
                                                  {type: "blockInactivePageMedia"});
           ]]>
         </body>
       </method>
 
       <method name="resumeMedia">
         <body>
           <![CDATA[
+            this._audioBlocked = false;
             this.messageManager.sendAsyncMessage("AudioPlayback",
                                                  {type: "resumeMedia"});
+            let  event = document.createEvent("Events");
+            event.initEvent("DOMAudioPlaybackBlockStopped", true, false);
+            this.dispatchEvent(event);
           ]]>
         </body>
       </method>
 
       <property name="securityUI">
         <getter>
           <![CDATA[
             if (!this.docShell.securityUI) {
@@ -917,16 +945,17 @@
           }
 
           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);
           }
         ]]>
       </constructor>
 
       <destructor>
         <![CDATA[
           this.destroy();
         ]]>
@@ -1003,16 +1032,19 @@
               this._autoScrollPopup.hidePopup();
               break;
             case "AudioPlayback:Start":
               this.audioPlaybackStarted();
               break;
             case "AudioPlayback:Stop":
               this.audioPlaybackStopped();
               break;
+            case "AudioPlayback:Block":
+              this.audioPlaybackBlocked();
+              break;
           }
           return undefined;
         ]]></body>
       </method>
 
       <method name="receiveMessage">
         <parameter name="aMessage"/>
         <body><![CDATA[