Bug 1192818 - Dispatch DOMAudioPlaybackStopped when mute the tab. r=baku, a=gchang
authorAlastor Wu <alwu@mozilla.com>
Wed, 01 Mar 2017 11:40:24 +0800
changeset 376547 c3e6050e17f11a9c8844c83d061b3ff277692c9a
parent 376546 4cd258347baab9f8dbe1ffbdf0238cfe3686509f
child 376548 29f91acdc27ff1f127c194d60b4642fc3284f626
push id6996
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 20:48:21 +0000
treeherdermozilla-beta@d89512dab048 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku, gchang
bugs1192818
milestone53.0a2
Bug 1192818 - Dispatch DOMAudioPlaybackStopped when mute the tab. r=baku, a=gchang
browser/base/content/test/general/browser_audioTabIcon.js
dom/html/HTMLMediaElement.cpp
toolkit/content/tests/browser/file_mediaPlayback2.html
toolkit/content/widgets/browser.xml
--- a/browser/base/content/test/general/browser_audioTabIcon.js
+++ b/browser/base/content/test/general/browser_audioTabIcon.js
@@ -12,29 +12,42 @@ function* wait_for_tab_playing_event(tab
       is(tab.hasAttribute("soundplaying"), expectPlaying, "The tab should " + (expectPlaying ? "" : "not ") + "be playing");
       is(tab.soundPlaying, expectPlaying, "The tab should " + (expectPlaying ? "" : "not ") + "be playing");
       return true;
     }
     return false;
   });
 }
 
+function* is_audio_playing(tab) {
+  let browser = tab.linkedBrowser;
+  let isPlaying = yield ContentTask.spawn(browser, {}, function* () {
+    let audio = content.document.querySelector("audio");
+    return !audio.paused;
+  });
+  return isPlaying;
+}
+
 function* play(tab) {
   let browser = tab.linkedBrowser;
   yield ContentTask.spawn(browser, {}, function* () {
     let audio = content.document.querySelector("audio");
     audio.play();
   });
 
+  // If the tab has already be muted, it means the tab won't get soundplaying,
+  // so we don't need to check this attribute.
+  if (browser.audioMuted) {
+      return;
+  }
+
   yield wait_for_tab_playing_event(tab, true);
 }
 
 function* pause(tab, options) {
-  ok(tab.hasAttribute("soundplaying"), "The tab should have the soundplaying attribute when pause() is called");
-
   let extendedDelay = options && options.extendedDelay;
   if (extendedDelay) {
     // Use 10s to remove possibility of race condition with attr removal.
     Services.prefs.setIntPref(TABATTR_REMOVAL_PREFNAME, 10000);
   }
 
   try {
     let browser = tab.linkedBrowser;
@@ -42,16 +55,22 @@ function* pause(tab, options) {
       BrowserTestUtils.waitForEvent(browser, "DOMAudioPlaybackStopped", "DOMAudioPlaybackStopped event should get fired after pause");
     let awaitTabPausedAttrModified =
       wait_for_tab_playing_event(tab, false);
     yield ContentTask.spawn(browser, {}, function* () {
       let audio = content.document.querySelector("audio");
       audio.pause();
     });
 
+    // If the tab has already be muted, it means the tab won't have soundplaying,
+    // so we don't need to check this attribute.
+    if (browser.audioMuted) {
+      return;
+    }
+
     if (extendedDelay) {
       ok(tab.hasAttribute("soundplaying"), "The tab should still have the soundplaying attribute immediately after pausing");
 
       yield awaitDOMAudioPlaybackStopped;
       ok(tab.hasAttribute("soundplaying"), "The tab should still have the soundplaying attribute immediately after DOMAudioPlaybackStopped");
     }
 
     yield awaitTabPausedAttrModified;
@@ -136,16 +155,23 @@ function* test_mute_tab(tab, icon, expec
   let tooltip = document.getElementById("tabbrowser-tab-tooltip");
 
   yield hover_icon(icon, tooltip);
   EventUtils.synthesizeMouseAtCenter(icon, {button: 0});
   leave_icon(icon);
 
   is(gBrowser.selectedTab, activeTab, "Clicking on mute should not change the currently selected tab");
 
+  // If the audio is playing, we should check whether clicking on icon affects
+  // the media element's playing state.
+  let isAudioPlaying = yield is_audio_playing(tab);
+  if (isAudioPlaying) {
+    yield wait_for_tab_playing_event(tab, !expectMuted);
+  }
+
   return mutedPromise;
 }
 
 function get_tab_state(tab) {
   const ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
   return JSON.parse(ss.getTabState(tab));
 }
 
@@ -163,17 +189,17 @@ function* test_muting_using_menu(tab, ex
   is(toggleMute.accessKey, "M", "Correct accessKey expected");
 
   is(toggleMute.hasAttribute("muted"), expectMuted, "Should have the correct state for the muted attribute");
   ok(!toggleMute.hasAttribute("soundplaying"), "Should not have the soundplaying attribute");
 
   yield play(tab);
 
   is(toggleMute.hasAttribute("muted"), expectMuted, "Should have the correct state for the muted attribute");
-  ok(toggleMute.hasAttribute("soundplaying"), "Should have the soundplaying attribute");
+  is(!toggleMute.hasAttribute("soundplaying"), expectMuted, "The value of soundplaying attribute is incorrect");
 
   yield pause(tab);
 
   is(toggleMute.hasAttribute("muted"), expectMuted, "Should have the correct state for the muted attribute");
   ok(!toggleMute.hasAttribute("soundplaying"), "Should not have the soundplaying attribute");
 
   // Click on the menu and wait for the tab to be muted.
   let mutedPromise = get_wait_for_mute_promise(tab, !expectMuted);
@@ -228,32 +254,32 @@ function* test_playing_icon_on_tab(tab, 
   // Make sure it's possible to mute using the context menu.
   yield test_muting_using_menu(tab, false);
 
   // Make sure it's possible to unmute using the context menu.
   yield test_muting_using_menu(tab, true);
 }
 
 function* test_swapped_browser_while_playing(oldTab, newBrowser) {
+  // The tab was muted so it won't have soundplaying attribute even it's playing.
   ok(oldTab.hasAttribute("muted"), "Expected the correct muted attribute on the old tab");
   is(oldTab.muteReason, null, "Expected the correct muteReason attribute on the old tab");
-  ok(oldTab.hasAttribute("soundplaying"), "Expected the correct soundplaying attribute on the old tab");
+  ok(!oldTab.hasAttribute("soundplaying"), "Expected the correct soundplaying attribute on the old tab");
 
   let newTab = gBrowser.getTabForBrowser(newBrowser);
   let AttrChangePromise = BrowserTestUtils.waitForEvent(newTab, "TabAttrModified", false, event => {
-    return event.detail.changed.includes("soundplaying") &&
-           event.detail.changed.includes("muted");
+    return event.detail.changed.includes("muted");
   });
 
   gBrowser.swapBrowsersAndCloseOther(newTab, oldTab);
   yield AttrChangePromise;
 
   ok(newTab.hasAttribute("muted"), "Expected the correct muted attribute on the new tab");
   is(newTab.muteReason, null, "Expected the correct muteReason property on the new tab");
-  ok(newTab.hasAttribute("soundplaying"), "Expected the correct soundplaying attribute on the new tab");
+  ok(!newTab.hasAttribute("soundplaying"), "Expected the correct soundplaying attribute on the new tab");
 
   let icon = document.getAnonymousElementByAttribute(newTab, "anonid",
                                                      "soundplaying-icon");
   yield test_tooltip(icon, "Unmute tab", true);
 }
 
 function* test_swapped_browser_while_not_playing(oldTab, newBrowser) {
   ok(oldTab.hasAttribute("muted"), "Expected the correct muted attribute on the old tab");
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -996,17 +996,17 @@ private:
             mSuspended == nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE ||
             mSuspended == nsISuspendedTypes::SUSPENDED_BLOCK);
   }
 
   AudibleState
   IsOwnerAudible() const
   {
     // Muted or the volume should not be ~0
-    if (mOwner->Muted() || (std::fabs(mOwner->Volume()) <= 1e-7)) {
+    if (mOwner->mMuted || (std::fabs(mOwner->Volume()) <= 1e-7)) {
       return AudioChannelService::AudibleState::eNotAudible;
     }
 
     // No audio track.
     if (!mOwner->HasAudio()) {
       return AudioChannelService::AudibleState::eNotAudible;
     }
 
@@ -2017,25 +2017,29 @@ void HTMLMediaElement::NotifyMediaTrackD
                         this, aTrack->AsAudioTrack() ? "Audio" : "Video",
                         NS_ConvertUTF16toUTF8(id).get()));
 #endif
 
   MOZ_ASSERT((!aTrack->AsAudioTrack() || !aTrack->AsAudioTrack()->Enabled()) &&
              (!aTrack->AsVideoTrack() || !aTrack->AsVideoTrack()->Selected()));
 
   if (aTrack->AsAudioTrack()) {
-    bool shouldMute = true;
-    for (uint32_t i = 0; i < AudioTracks()->Length(); ++i) {
-      if ((*AudioTracks())[i]->Enabled()) {
-        shouldMute = false;
-        break;
+    // If we don't have any alive track , we don't need to mute MediaElement.
+    if (AudioTracks()->Length() > 0) {
+      bool shouldMute = true;
+      for (uint32_t i = 0; i < AudioTracks()->Length(); ++i) {
+        if ((*AudioTracks())[i]->Enabled()) {
+          shouldMute = false;
+          break;
+        }
       }
-    }
-    if (shouldMute) {
-      SetMutedInternal(mMuted | MUTED_BY_AUDIO_TRACK);
+
+      if (shouldMute) {
+        SetMutedInternal(mMuted | MUTED_BY_AUDIO_TRACK);
+      }
     }
   } else if (aTrack->AsVideoTrack()) {
     if (mSrcStream) {
       MOZ_ASSERT(mSelectedVideoStreamTrack);
       if (mSelectedVideoStreamTrack && mMediaStreamSizeListener) {
         mSelectedVideoStreamTrack->RemoveDirectListener(mMediaStreamSizeListener);
         mMediaStreamSizeListener->Forget();
         mMediaStreamSizeListener = nullptr;
--- a/toolkit/content/tests/browser/file_mediaPlayback2.html
+++ b/toolkit/content/tests/browser/file_mediaPlayback2.html
@@ -2,11 +2,12 @@
 <body>
 <script type="text/javascript">
 var audio = new Audio();
 audio.oncanplay = function() {
   audio.oncanplay = null;
   audio.play();
 };
 audio.src = "audio.ogg";
+audio.loop = true;
 document.body.appendChild(audio);
 </script>
 </body>
--- a/toolkit/content/widgets/browser.xml
+++ b/toolkit/content/widgets/browser.xml
@@ -692,16 +692,19 @@
       <!-- Obsolete name for blockedPopups. Used by android. -->
       <property name="pageReport"
          onget="return this.blockedPopups;"
          readonly="true"/>
 
       <method name="audioPlaybackStarted">
         <body>
           <![CDATA[
+            if (this._audioMuted) {
+              return;
+            }
             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);