Bug 1347791 - part4 : keep tab's block state consistent after session restore. draft
authorAlastor Wu <alwu@mozilla.com>
Thu, 29 Jun 2017 05:46:28 -0700
changeset 602019 9ab7ba94ce6f76b5295411e2dcd0beb5813cf524
parent 602018 36379253c46c115323f0c3d4a3047f337965765b
child 602020 0f446f236f3bded13126f80e8778ac02368dd335
push id66245
push useralwu@mozilla.com
push dateThu, 29 Jun 2017 13:14:24 +0000
bugs1347791
milestone56.0a1
Bug 1347791 - part4 : keep tab's block state consistent after session restore. If the tab was resumed before, it could start playing any autoplay media without user's permission after session restore. MozReview-Commit-ID: C3DHIIsLtJA
browser/base/content/tabbrowser.xml
browser/components/sessionstore/SessionStore.jsm
browser/components/sessionstore/TabAttributes.jsm
browser/components/sessionstore/TabState.jsm
browser/components/sessionstore/test/browser_attributes.js
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -2157,17 +2157,17 @@
         "goHome", "homePage", "gotoIndex", "currentURI", "documentURI",
         "preferences", "imageDocument", "isRemoteBrowser", "messageManager",
         "getTabBrowser", "finder", "fastFind", "sessionHistory", "contentTitle",
         "characterSet", "fullZoom", "textZoom", "webProgress",
         "addProgressListener", "removeProgressListener", "audioPlaybackStarted",
         "audioPlaybackStopped", "pauseMedia", "stopMedia",
         "blockMedia", "resumeMedia", "mute", "unmute", "blockedPopups", "lastURI",
         "purgeSessionHistory", "stopScroll", "startScroll",
-        "userTypedValue", "userTypedClear"
+        "userTypedValue", "userTypedClear", "mediaBlocked"
       ]</field>
 
       <method name="_createLazyBrowser">
         <parameter name="aTab"/>
         <body>
           <![CDATA[
             let browser = aTab.linkedBrowser;
 
@@ -2221,18 +2221,31 @@
                       // initializing the reload.
                       aTab.addEventListener("SSTabRestoring", () => {
                         browser[name](params);
                       }, { once: true });
                       gBrowser._insertBrowser(aTab);
                     };
                   };
                   break;
+                case "blockMedia":
+                case "resumeMedia":
+                  getter = () => {
+                    return () => {
+                      // No need to insert a browser, so we just call the browser's
+                      // method.
+                      aTab.addEventListener("SSTabRestoring", () => {
+                        browser[name]();
+                      }, { once: true });
+                    };
+                  };
+                  break;
                 case "userTypedValue":
                 case "userTypedClear":
+                case "mediaBlocked":
                   getter = () => {
                     return SessionStore.getLazyTabValue(aTab, name);
                   };
                   break;
                 default:
                   getter = () => {
                     if (AppConstants.NIGHTLY_BUILD) {
                       let message =
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -3593,16 +3593,22 @@ var SessionStoreInternal = {
     } else {
       tabbrowser.showTab(tab);
     }
 
     if (!!tabData.muted != browser.audioMuted) {
       tab.toggleMuteAudio(tabData.muteReason);
     }
 
+    if (tabData.mediaBlocked) {
+      browser.blockMedia();
+    } else {
+      browser.resumeMedia();
+    }
+
     if (tabData.lastAccessed) {
       tab.updateLastAccessed(tabData.lastAccessed);
     }
 
     if ("attributes" in tabData) {
       // Ensure that we persist tab attributes restored from previous sessions.
       Object.keys(tabData.attributes).forEach(a => TabAttributes.persist(a));
     }
--- a/browser/components/sessionstore/TabAttributes.jsm
+++ b/browser/components/sessionstore/TabAttributes.jsm
@@ -9,18 +9,21 @@ this.EXPORTED_SYMBOLS = ["TabAttributes"
 // We never want to directly read or write these attributes.
 // 'image' should not be accessed directly but handled by using the
 //         gBrowser.getIcon()/setIcon() methods.
 // 'muted' should not be accessed directly but handled by using the
 //         tab.linkedBrowser.audioMuted/toggleMuteAudio methods.
 // 'pending' is used internal by sessionstore and managed accordingly.
 // 'iconLoadingPrincipal' is same as 'image' that it should be handled by
 //                        using the gBrowser.getIcon()/setIcon() methods.
+// 'activemedia-blocked' should not be accessed directly but handled by using
+//                       tab's toggleMuteAudio() or linkedBrowser's methods
+//                       activeMediaBlockStarted()/activeMediaBlockBlockStopped().
 const ATTRIBUTES_TO_SKIP = new Set(["image", "muted", "pending", "iconLoadingPrincipal",
-                                    "skipbackgroundnotify"]);
+                                    "skipbackgroundnotify", "activemedia-blocked"]);
 
 // A set of tab attributes to persist. We will read a given list of tab
 // attributes when collecting tab data and will re-set those attributes when
 // the given tab data is restored to a new tab.
 this.TabAttributes = Object.freeze({
   persist(name) {
     return TabAttributesInternal.persist(name);
   },
--- a/browser/components/sessionstore/TabState.jsm
+++ b/browser/components/sessionstore/TabState.jsm
@@ -97,16 +97,18 @@ var TabStateInternal = {
 
     tabData.hidden = tab.hidden;
 
     if (browser.audioMuted) {
       tabData.muted = true;
       tabData.muteReason = tab.muteReason;
     }
 
+    tabData.mediaBlocked = browser.mediaBlocked;
+
     // Save tab attributes.
     tabData.attributes = TabAttributes.get(tab);
 
     if (tab.__SS_extdata) {
       tabData.extData = tab.__SS_extdata;
     }
 
     // Copy data from the tab state cache only if the tab has fully finished
--- a/browser/components/sessionstore/test/browser_attributes.js
+++ b/browser/components/sessionstore/test/browser_attributes.js
@@ -1,44 +1,56 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * This test makes sure that we correctly preserve tab attributes when storing
  * and restoring tabs. It also ensures that we skip special attributes like
- * 'image', 'muted' and 'pending' that need to be handled differently or internally.
+ * 'image', 'muted', 'activemedia-blocked' and 'pending' that need to be
+ * handled differently or internally.
  */
 
 const PREF = "browser.sessionstore.restore_on_demand";
+const PREF2 = "media.block-autoplay-until-in-foreground";
 
 add_task(async function test() {
   Services.prefs.setBoolPref(PREF, true)
   registerCleanupFunction(() => Services.prefs.clearUserPref(PREF));
 
+  // Since we need to test 'activemedia-blocked' attribute.
+  Services.prefs.setBoolPref(PREF2, true)
+  registerCleanupFunction(() => Services.prefs.clearUserPref(PREF2));
+
   // Add a new tab with a nice icon.
   let tab = BrowserTestUtils.addTab(gBrowser, "about:robots");
   await promiseBrowserLoaded(tab.linkedBrowser);
 
   // Check that the tab has 'image' and 'iconLoadingPrincipal' attributes.
   ok(tab.hasAttribute("image"), "tab.image exists");
   ok(tab.hasAttribute("iconLoadingPrincipal"), "tab.iconLoadingPrincipal exists");
 
   tab.toggleMuteAudio();
   // Check that the tab has a 'muted' attribute.
   ok(tab.hasAttribute("muted"), "tab.muted exists");
 
-  // Make sure we do not persist 'image' or 'muted' attributes.
+  // Pretend to start autoplay media in tab, tab should get the notification.
+  tab.linkedBrowser.activeMediaBlockStarted();
+  ok(tab.hasAttribute("activemedia-blocked"), "tab.activemedia-blocked exists");
+
+  // Make sure we do not persist 'image','muted' and 'activemedia-blocked' attributes.
   ss.persistTabAttribute("image");
   ss.persistTabAttribute("muted");
   ss.persistTabAttribute("iconLoadingPrincipal");
+  ss.persistTabAttribute("activemedia-blocked");
   let {attributes} = JSON.parse(ss.getTabState(tab));
   ok(!("image" in attributes), "'image' attribute not saved");
   ok(!("iconLoadingPrincipal" in attributes), "'iconLoadingPrincipal' attribute not saved");
   ok(!("muted" in attributes), "'muted' attribute not saved");
   ok(!("custom" in attributes), "'custom' attribute not saved");
+  ok(!("activemedia-blocked" in attributes), "'activemedia-blocked' attribute not saved");
 
   // Test persisting a custom attribute.
   tab.setAttribute("custom", "foobar");
   ss.persistTabAttribute("custom");
 
   ({attributes} = JSON.parse(ss.getTabState(tab)));
   is(attributes.custom, "foobar", "'custom' attribute is correct");