Bug 1347791 - part3 : move block/resume logic from back-end to front-end. r=baku,mikedeboer
☠☠ backed out by 76ed52c3ca1a ☠ ☠
authorAlastor Wu <alwu@mozilla.com>
Wed, 17 May 2017 11:56:06 +0800
changeset 358736 37d295d7eec9d32ca4ef4f5d1e49ea7af52ea4b8
parent 358735 be04f96bf78ba11fb2b6ac4445028124fca24daf
child 358737 888dc371081aad0695694e283106b5d2d00f2302
push id90373
push usercbook@mozilla.com
push dateWed, 17 May 2017 10:28:10 +0000
treeherdermozilla-inbound@89f59c12ff7f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku, mikedeboer
bugs1347791
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1347791 - part3 : move block/resume logic from back-end to front-end. r=baku,mikedeboer Previous design allows us calling resume/block from both front-end and back-end, it's not easy to know who called these operations. So move all these logic to frond-end side, it's more clear than before. One important thing is that we should block tab before loading the content. If we block the tab after loading, the media might not be blocked because it had already started (that is one situation I observed from test). The value of block state would be stored in the outer window, before media want to start, it would check this value to know whether it can start playing or not. --- In addition, introduce new attribute "media-blocked". The "media-blocked" attribute indicates that whether the tab is allowed to play autoplay media. The "activemedia-blocked" attribute indicates whether the tab has blocked the autoplay media. MozReview-Commit-ID: FnNh3zmItxo
browser/base/content/tabbrowser.xml
dom/base/nsDocument.cpp
dom/base/nsDocument.h
dom/base/nsGlobalWindow.cpp
toolkit/content/widgets/browser.xml
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -641,16 +641,22 @@
               // is one that we shouldn't be ignoring, then stop ignoring.
               if ((ignoreBlank &&
                    aStateFlags & nsIWebProgressListener.STATE_STOP &&
                    aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) ||
                   !ignoreBlank && this.mBlank) {
                 this.mBlank = false;
               }
 
+              // Should block media before loading content.
+              if (this.mTab.hasAttribute("media-blocked") &&
+                  !this.mTab.linkedBrowser.mediaBlocked) {
+                this.mTab.setMediaBlock(true /* block */);
+              }
+
               if (aStateFlags & nsIWebProgressListener.STATE_START) {
                 this.mRequestCount++;
               } else if (aStateFlags & nsIWebProgressListener.STATE_STOP) {
                 const NS_ERROR_UNKNOWN_HOST = 2152398878;
                 if (--this.mRequestCount > 0 && aStatus == NS_ERROR_UNKNOWN_HOST) {
                   // to prevent bug 235825: wait for the request handled
                   // by the automatic keyword resolver
                   return;
@@ -2500,16 +2506,20 @@
 
             if (animate) {
               requestAnimationFrame(function() {
                 // kick the animation off
                 t.setAttribute("fadein", "true");
               });
             }
 
+            // All tabs would be blocked by default, and would be resumed when
+            // tab is visited or by clicking the play tab icon.
+            t.setAttribute("media-blocked", Services.prefs.getBoolPref("media.block-autoplay-until-in-foreground"));
+
             return t;
           ]]>
         </body>
       </method>
 
       <method name="warnAboutClosingTabs">
       <parameter name="aCloseTabs"/>
       <parameter name="aTab"/>
@@ -3372,16 +3382,20 @@
             this.tabContainer.adjustTabstrip();
 
             this.tabContainer._setPositionalAttributes();
 
             let event = document.createEvent("Events");
             event.initEvent("TabShow", true, false);
             aTab.dispatchEvent(event);
           }
+
+          if (aTab.hasAttribute("media-blocked")) {
+            aTab.setMediaBlock(false /* unblock */);
+          }
         ]]>
         </body>
       </method>
 
       <method name="hideTab">
         <parameter name="aTab"/>
         <body>
         <![CDATA[
@@ -4960,17 +4974,17 @@
           } else if (tab._overPlayingIcon) {
             let stringID;
             if (tab.selected) {
               stringID = tab.linkedBrowser.audioMuted ?
                 "tabs.unmuteAudio.tooltip" :
                 "tabs.muteAudio.tooltip";
               label = stringWithShortcut(stringID, "key_toggleMute");
             } else {
-              if (tab.linkedBrowser.audioBlocked) {
+              if (tab.hasAttribute("activemedia-blocked")) {
                 stringID = "tabs.unblockAudio.tooltip";
               } else {
                 stringID = tab.linkedBrowser.audioMuted ?
                   "tabs.unmuteAudio.background.tooltip" :
                   "tabs.muteAudio.background.tooltip";
               }
 
               label = this.mStringBundle.getString(stringID);
@@ -7367,21 +7381,21 @@
             return;
           }
 
           let tabContainer = this.parentNode;
           let browser = this.linkedBrowser;
           let modifiedAttrs = [];
           let hist = Services.telemetry.getHistogramById("TAB_AUDIO_INDICATOR_USED");
 
-          if (browser.audioBlocked) {
+          if (this.hasAttribute("activemedia-blocked")) {
             this.removeAttribute("activemedia-blocked");
             modifiedAttrs.push("activemedia-blocked");
 
-            browser.resumeMedia();
+            this.setMediaBlock(false /* unblock */);
             hist.add(3 /* unblockByClickingIcon */);
             this.finishMediaBlockTimer();
           } else {
             if (browser.audioMuted) {
               browser.unmute();
               this.removeAttribute("muted");
               BrowserUITelemetry.countTabMutingEvent("unmute", aMuteReason);
               hist.add(1 /* unmute */);
@@ -7394,16 +7408,37 @@
             this.muteReason = aMuteReason || null;
             modifiedAttrs.push("muted");
           }
           tabContainer.tabbrowser._tabAttrModified(this, modifiedAttrs);
         ]]>
         </body>
       </method>
 
+      <method name="setMediaBlock">
+        <parameter name="aBlock"/>
+        <body>
+        <![CDATA[
+          let tabContainer = this.parentNode;
+          let browser = this.linkedBrowser;
+
+          let modifiedAttrs = [];
+          if (aBlock) {
+            this.setAttribute("media-blocked", true);
+            browser.blockMedia();
+          } else {
+            this.removeAttribute("media-blocked");
+            browser.resumeMedia();
+          }
+          modifiedAttrs.push("media-blocked");
+          tabContainer.tabbrowser._tabAttrModified(this, modifiedAttrs);
+        ]]>
+        </body>
+      </method>
+
       <method name="setUserContextId">
         <parameter name="aUserContextId"/>
         <body>
         <![CDATA[
           if (aUserContextId) {
             if (this.linkedBrowser) {
               this.linkedBrowser.setAttribute("usercontextid", aUserContextId);
             }
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -12256,20 +12256,16 @@ nsDocument::UpdateVisibilityState()
   mVisibilityState = GetVisibilityState();
   if (oldState != mVisibilityState) {
     nsContentUtils::DispatchTrustedEvent(this, static_cast<nsIDocument*>(this),
                                          NS_LITERAL_STRING("visibilitychange"),
                                          /* bubbles = */ true,
                                          /* cancelable = */ false);
     EnumerateActivityObservers(NotifyActivityChanged, nullptr);
   }
-
-  if (mVisibilityState == dom::VisibilityState::Visible) {
-    MaybeActiveMediaComponents();
-  }
 }
 
 VisibilityState
 nsDocument::GetVisibilityState() const
 {
   // We have to check a few pieces of information here:
   // 1)  Are we in bfcache (!IsVisible())?  If so, nothing else matters.
   // 2)  Do we have an outer window?  If not, we're hidden.  Note that we don't
@@ -12295,26 +12291,16 @@ nsDocument::GetVisibilityState() const
 /* virtual */ void
 nsDocument::PostVisibilityUpdateEvent()
 {
   nsCOMPtr<nsIRunnable> event =
     NewRunnableMethod(this, &nsDocument::UpdateVisibilityState);
   Dispatch("nsDocument::UpdateVisibilityState", TaskCategory::Other, event.forget());
 }
 
-void
-nsDocument::MaybeActiveMediaComponents()
-{
-  if (!mWindow) {
-    return;
-  }
-
-  GetWindow()->MaybeActiveMediaComponents();
-}
-
 NS_IMETHODIMP
 nsDocument::GetHidden(bool* aHidden)
 {
   *aHidden = Hidden();
   return NS_OK;
 }
 
 NS_IMETHODIMP
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -1182,20 +1182,16 @@ public:
   mozilla::dom::Promise* GetOrientationPendingPromise() const override;
 
   // This method may fire a DOM event; if it does so it will happen
   // synchronously.
   void UpdateVisibilityState();
   // Posts an event to call UpdateVisibilityState
   virtual void PostVisibilityUpdateEvent() override;
 
-  // Since we wouldn't automatically play media from non-visited page, we need
-  // to notify window when the page was first visited.
-  void MaybeActiveMediaComponents();
-
   virtual void DocAddSizeOfExcludingThis(nsWindowSizes* aWindowSizes) const override;
   // DocAddSizeOfIncludingThis is inherited from nsIDocument.
 
   virtual nsIDOMNode* AsDOMNode() override { return this; }
 
   // WebIDL bits
   virtual mozilla::dom::DOMImplementation*
     GetImplementation(mozilla::ErrorResult& rv) override;
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -994,18 +994,17 @@ nsPIDOMWindow<T>::nsPIDOMWindow(nsPIDOMW
   mIsHandlingResizeEvent(false), mIsInnerWindow(aOuterWindow != nullptr),
   mMayHavePaintEventListener(false), mMayHaveTouchEventListener(false),
   mMayHaveMouseEnterLeaveEventListener(false),
   mMayHaveMouseMoveEventListener(false),
   mMayHavePointerEnterLeaveEventListener(false),
   mInnerObjectsFreed(false),
   mIsModalContentWindow(false),
   mIsActive(false), mIsBackground(false),
-  mMediaSuspend(Preferences::GetBool("media.block-autoplay-until-in-foreground", true) ?
-    nsISuspendedTypes::SUSPENDED_BLOCK : nsISuspendedTypes::NONE_SUSPENDED),
+  mMediaSuspend(nsISuspendedTypes::NONE_SUSPENDED),
   mAudioMuted(false), mAudioVolume(1.0), mAudioCaptured(false),
   mDesktopModeViewport(false), mIsRootOuterWindow(false), mInnerWindow(nullptr),
   mOuterWindow(aOuterWindow),
   // Make sure no actual window ends up with mWindowID == 0
   mWindowID(NextWindowID()), mHasNotifiedGlobalCreated(false),
   mMarkedCCGeneration(0), mServiceWorkersTestingEnabled(false),
   mLargeAllocStatus(LargeAllocStatus::NONE)
 {
--- a/toolkit/content/widgets/browser.xml
+++ b/toolkit/content/widgets/browser.xml
@@ -718,49 +718,51 @@
             this.dispatchEvent(event);
           ]]>
         </body>
       </method>
 
       <method name="audioPlaybackBlockStarted">
         <body>
           <![CDATA[
-            this._audioBlocked = true;
+            this._hasAnyPlayingMediaBeenBlocked = true;
             let event = document.createEvent("Events");
             event.initEvent("DOMAudioPlaybackBlockStarted", true, false);
             this.dispatchEvent(event);
           ]]>
         </body>
       </method>
 
       <method name="audioPlaybackBlockStopped">
         <body>
           <![CDATA[
-            if (!this._audioBlocked) {
+            if (!this._hasAnyPlayingMediaBeenBlocked) {
               return;
             }
-            this._audioBlocked = false;
+            this._hasAnyPlayingMediaBeenBlocked = 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"
-                onget="return this._audioBlocked;"
+      <field name="_mediaBlocked">false</field>
+      <property name="mediaBlocked"
+                onget="return this._mediaBlocked;"
                 readonly="true"/>
 
+      <field name="_hasAnyPlayingMediaBeenBlocked">false</field>
+
       <method name="mute">
         <body>
           <![CDATA[
             this._audioMuted = true;
             this.messageManager.sendAsyncMessage("AudioPlayback",
                                                  {type: "mute"});
           ]]>
         </body>
@@ -800,32 +802,39 @@
                                                  {type: "mediaControlStopped"});
           ]]>
         </body>
       </method>
 
       <method name="blockMedia">
         <body>
           <![CDATA[
-            this._audioBlocked = true;
+            if (this._mediaBlocked) {
+              return;
+            }
+            this._mediaBlocked = true;
             this.messageManager.sendAsyncMessage("AudioPlayback",
                                                  {type: "blockInactivePageMedia"});
           ]]>
         </body>
       </method>
 
       <method name="resumeMedia">
         <body>
           <![CDATA[
-            this._audioBlocked = false;
+            this._mediaBlocked = false;
             this.messageManager.sendAsyncMessage("AudioPlayback",
                                                  {type: "resumeMedia"});
-            let event = document.createEvent("Events");
-            event.initEvent("DOMAudioPlaybackBlockStopped", true, false);
-            this.dispatchEvent(event);
+
+            if (this._hasAnyPlayingMediaBeenBlocked) {
+              this._hasAnyPlayingMediaBeenBlocked = false;
+              let event = document.createEvent("Events");
+              event.initEvent("DOMAudioPlaybackBlockStopped", true, false);
+              this.dispatchEvent(event);
+            }
           ]]>
         </body>
       </method>
 
       <property name="securityUI">
         <getter>
           <![CDATA[
             if (!this.docShell.securityUI) {