Bug 1543812 - Add ability to block all autoplay. r=johannh,alwu,flod,fluent-reviewers
authorDale Harvey <dale@arandomurl.com>
Thu, 06 Jun 2019 09:06:09 +0000
changeset 477584 75a6f48ca07813830b88470606a2145b2f7610e7
parent 477583 bd02bdaa03ca60e9f404230503f43258e28a1af0
child 477585 0ac292cba16cbc8691ace7f8559bd33cf2e29c87
push id36119
push userncsoregi@mozilla.com
push dateThu, 06 Jun 2019 21:52:09 +0000
treeherdermozilla-central@6a81efd823db [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjohannh, alwu, flod, fluent-reviewers
bugs1543812
milestone69.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 1543812 - Add ability to block all autoplay. r=johannh,alwu,flod,fluent-reviewers Differential Revision: https://phabricator.services.mozilla.com/D30135
browser/app/profile/firefox.js
browser/base/content/browser-siteIdentity.js
browser/base/content/pageinfo/permissions.js
browser/base/content/test/permissions/browser.ini
browser/base/content/test/permissions/browser_autoplay_blocked.js
browser/base/content/test/permissions/browser_autoplay_muted.html
browser/components/preferences/in-content/privacy.js
browser/components/preferences/in-content/privacy.xul
browser/components/preferences/in-content/tests/browser.ini
browser/components/preferences/in-content/tests/browser_permissions_dialog.js
browser/components/preferences/in-content/tests/browser_site_autoplay_media_exceptions.js
browser/components/preferences/permissions.js
browser/components/preferences/sitePermissions.js
browser/components/preferences/sitePermissions.xul
browser/locales/en-US/browser/preferences/permissions.ftl
browser/locales/en-US/browser/preferences/preferences.ftl
browser/locales/en-US/chrome/browser/sitePermissions.properties
browser/modules/SitePermissions.jsm
browser/themes/shared/incontentprefs/privacy.css
dom/media/AutoplayPolicy.cpp
dom/media/nsIAutoplay.idl
modules/libpref/init/StaticPrefList.h
modules/libpref/init/all.js
toolkit/content/tests/browser/browser_autoplay_policy.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1508,17 +1508,17 @@ pref("media.gmp-widevinecdm.enabled", tr
 #endif
 
 pref("media.gmp-gmpopenh264.visible", true);
 pref("media.gmp-gmpopenh264.enabled", true);
 
 // Switch block autoplay logic to v2, and enable UI.
 pref("media.autoplay.enabled.user-gestures-needed", true);
 // Set Firefox to block autoplay, asking for permission by default.
-pref("media.autoplay.default", 1); // 0=Allowed, 1=Blocked
+pref("media.autoplay.default", 1); // 0=Allowed, 1=Blocked, 5=All Blocked
 
 #ifdef NIGHTLY_BUILD
 // Block WebAudio from playing automatically.
 pref("media.autoplay.block-webaudio", true);
 #else
 pref("media.autoplay.block-webaudio", false);
 #endif
 
--- a/browser/base/content/browser-siteIdentity.js
+++ b/browser/base/content/browser-siteIdentity.js
@@ -638,17 +638,18 @@ var gIdentityHandler = {
     }
 
     // keeps track if we should show an indicator that there are active permissions
     let hasGrantedPermissions = false;
 
     // show permission icons
     let permissions = SitePermissions.getAllForBrowser(gBrowser.selectedBrowser);
     for (let permission of permissions) {
-      if (permission.state == SitePermissions.BLOCK) {
+      if (permission.state == SitePermissions.BLOCK ||
+          permission.state == SitePermissions.AUTOPLAY_BLOCKED_ALL) {
         let icon = permissionAnchors[permission.id];
         if (icon) {
           icon.setAttribute("showing", "true");
         }
       } else if (permission.state != SitePermissions.UNKNOWN) {
         hasGrantedPermissions = true;
       }
     }
@@ -1136,17 +1137,18 @@ var gIdentityHandler = {
         let menuitem = document.createXULElement("menuitem");
         // We need to correctly display the default/unknown state, which has its
         // own integer value (0) but represents one of the other states.
         if (state == SitePermissions.getDefault(aPermission.id)) {
           menuitem.setAttribute("value", "0");
         } else {
           menuitem.setAttribute("value", state);
         }
-        menuitem.setAttribute("label", SitePermissions.getMultichoiceStateLabel(state));
+
+        menuitem.setAttribute("label", SitePermissions.getMultichoiceStateLabel(aPermission.id, state));
         menupopup.appendChild(menuitem);
       }
 
       menulist.appendChild(menupopup);
 
       if (aPermission.state == SitePermissions.getDefault(aPermission.id)) {
         menulist.value = "0";
       } else {
--- a/browser/base/content/pageinfo/permissions.js
+++ b/browser/base/content/pageinfo/permissions.js
@@ -146,17 +146,17 @@ function createRow(aPartId) {
   controls.appendChild(spacer);
 
   let radiogroup = document.createXULElement("radiogroup");
   radiogroup.setAttribute("id", radiogroupId);
   radiogroup.setAttribute("orient", "horizontal");
   for (let state of SitePermissions.getAvailableStates(aPartId)) {
     let radio = document.createXULElement("radio");
     radio.setAttribute("id", aPartId + "#" + state);
-    radio.setAttribute("label", SitePermissions.getMultichoiceStateLabel(state));
+    radio.setAttribute("label", SitePermissions.getMultichoiceStateLabel(aPartId, state));
     radio.setAttribute("command", commandId);
     radiogroup.appendChild(radio);
   }
   controls.appendChild(radiogroup);
 
   row.appendChild(controls);
 
   document.getElementById("permList").appendChild(row);
--- a/browser/base/content/test/permissions/browser.ini
+++ b/browser/base/content/test/permissions/browser.ini
@@ -17,13 +17,14 @@ support-files=
 [browser_temporary_permissions.js]
 support-files =
   temporary_permissions_subframe.html
   ../webrtc/get_user_media.html
 [browser_autoplay_blocked.js]
 support-files =
   browser_autoplay_blocked.html
   browser_autoplay_blocked_slow.sjs
+  browser_autoplay_muted.html
   ../general/audio.ogg
 skip-if = verify && os == 'linux' && debug # Bug 1483648
 [browser_temporary_permissions_expiry.js]
 [browser_temporary_permissions_navigation.js]
 [browser_temporary_permissions_tabs.js]
--- a/browser/base/content/test/permissions/browser_autoplay_blocked.js
+++ b/browser/base/content/test/permissions/browser_autoplay_blocked.js
@@ -1,16 +1,18 @@
 /*
  * Test that a blocked request to autoplay media is shown to the user
  */
 
 const AUTOPLAY_PAGE = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "https://example.com") + "browser_autoplay_blocked.html";
 
 const SLOW_AUTOPLAY_PAGE = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "https://example.com") + "browser_autoplay_blocked_slow.sjs";
 
+const MUTED_AUTOPLAY_PAGE = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "https://example.com") + "browser_autoplay_muted.html";
+
 const AUTOPLAY_PREF = "media.autoplay.default";
 const AUTOPLAY_PERM = "autoplay-media";
 
 function openIdentityPopup() {
   let promise = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "popupshown");
   gIdentityHandler._identityBox.click();
   return promise;
 }
@@ -75,25 +77,25 @@ add_task(async function testMainViewVisi
     await openIdentityPopup();
     ok(BrowserTestUtils.is_hidden(emptyLabel), "List of permissions is not empty");
     let labelText = SitePermissions.getPermissionLabel(AUTOPLAY_PERM);
     let labels = permissionsList.querySelectorAll(".identity-popup-permission-label");
     is(labels.length, 1, "One permission visible in main view");
     is(labels[0].textContent, labelText, "Correct value");
 
     let menulist = document.getElementById("identity-popup-popup-menulist");
-    Assert.equal(menulist.label, "Block");
+    Assert.equal(menulist.label, "Block Audio");
 
     await EventUtils.synthesizeMouseAtCenter(menulist, { type: "mousedown" });
-    await BrowserTestUtils.waitForCondition(() => {
-      return menulist.getElementsByTagName("menuitem")[0].label === "Allow";
+    await TestUtils.waitForCondition(() => {
+      return menulist.getElementsByTagName("menuitem")[0].label === "Allow Audio and Video";
     });
 
     let menuitem = menulist.getElementsByTagName("menuitem")[0];
-    Assert.equal(menuitem.getAttribute("label"), "Allow");
+    Assert.equal(menuitem.getAttribute("label"), "Allow Audio and Video");
 
     menuitem.click();
     menulist.menupopup.hidePopup();
     await closeIdentityPopup();
 
     let uri = Services.io.newURI(AUTOPLAY_PAGE);
     let state = SitePermissions.get(uri, AUTOPLAY_PERM).state;
     Assert.equal(state, SitePermissions.ALLOW);
@@ -150,16 +152,17 @@ add_task(async function testBFCache() {
 
   Services.perms.removeAll();
 });
 
 add_task(async function testChangingBlockingSettingDuringNavigation() {
   Services.prefs.setIntPref(AUTOPLAY_PREF, Ci.nsIAutoplay.BLOCKED);
 
   await BrowserTestUtils.withNewTab("about:home", async function(browser) {
+    await blockedIconHidden();
     await BrowserTestUtils.loadURI(browser, AUTOPLAY_PAGE);
     await blockedIconShown();
     Services.prefs.setIntPref(AUTOPLAY_PREF, Ci.nsIAutoplay.ALLOWED);
 
     gBrowser.goBack();
     await blockedIconHidden();
 
     gBrowser.goForward();
@@ -188,8 +191,34 @@ add_task(async function testSlowLoadingP
   await BrowserTestUtils.switchTab(gBrowser, tab2);
   await blockedIconShown();
 
   BrowserTestUtils.removeTab(tab1);
   BrowserTestUtils.removeTab(tab2);
 
   Services.perms.removeAll();
 });
+
+add_task(async function testBlockedAll() {
+  Services.prefs.setIntPref(AUTOPLAY_PREF, Ci.nsIAutoplay.BLOCKED_ALL);
+
+  await BrowserTestUtils.withNewTab("about:home", async function(browser) {
+    await blockedIconHidden();
+    await BrowserTestUtils.loadURI(browser, MUTED_AUTOPLAY_PAGE);
+    await blockedIconShown();
+
+    await openIdentityPopup();
+
+    let menulist = document.getElementById("identity-popup-popup-menulist");
+    await EventUtils.synthesizeMouseAtCenter(menulist, { type: "mousedown" });
+    await TestUtils.waitForCondition(() => {
+      return menulist.getElementsByTagName("menuitem")[1].label === "Block Audio";
+    });
+
+    let menuitem = menulist.getElementsByTagName("menuitem")[0];
+    menuitem.click();
+    menulist.menupopup.hidePopup();
+    await closeIdentityPopup();
+    gBrowser.reload();
+    await blockedIconHidden();
+  });
+  Services.perms.removeAll();
+});
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/permissions/browser_autoplay_muted.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+  <head>
+    <meta charset="utf8">
+  </head>
+  <body>
+    <audio autoplay="autoplay" muted>
+      <source src="audio.ogg" />
+    </audio>
+  </body>
+</html>
--- a/browser/components/preferences/in-content/privacy.js
+++ b/browser/components/preferences/in-content/privacy.js
@@ -283,26 +283,22 @@ var gPrivacyPane = {
     this.updateHistoryModePane();
     this.updatePrivacyMicroControls();
     this.initAutoStartPrivateBrowsingReverter();
     this._initAutocomplete();
 
     /* Initialize Content Blocking */
     this.initContentBlocking();
 
-    this.blockAutoplayReadPrefs();
     this.trackingProtectionReadPrefs();
     this.networkCookieBehaviorReadPrefs();
     this._initTrackingProtectionExtensionControl();
 
     Services.telemetry.setEventRecordingEnabled("pwmgr", true);
 
-    Preferences.get("media.autoplay.default").on("change",
-      gPrivacyPane.blockAutoplayReadPrefs.bind(gPrivacyPane));
-
     Preferences.get("privacy.trackingprotection.enabled").on("change",
       gPrivacyPane.trackingProtectionReadPrefs.bind(gPrivacyPane));
     Preferences.get("privacy.trackingprotection.pbmode.enabled").on("change",
       gPrivacyPane.trackingProtectionReadPrefs.bind(gPrivacyPane));
 
     // Watch all of the prefs that the new Cookies & Site Data UI depends on
     Preferences.get("network.cookie.cookieBehavior").on("change",
       gPrivacyPane.networkCookieBehaviorReadPrefs.bind(gPrivacyPane));
@@ -356,30 +352,28 @@ var gPrivacyPane = {
       gPrivacyPane.showCertificates);
     setEventListener("viewSecurityDevicesButton", "command",
       gPrivacyPane.showSecurityDevices);
 
     this._pane = document.getElementById("panePrivacy");
     this._initMasterPasswordUI();
     this._initSafeBrowsing();
 
+    setEventListener("autoplaySettingsButton", "command",
+      gPrivacyPane.showAutoplayMediaExceptions);
     setEventListener("notificationSettingsButton", "command",
       gPrivacyPane.showNotificationExceptions);
     setEventListener("locationSettingsButton", "command",
       gPrivacyPane.showLocationExceptions);
     setEventListener("cameraSettingsButton", "command",
       gPrivacyPane.showCameraExceptions);
     setEventListener("microphoneSettingsButton", "command",
       gPrivacyPane.showMicrophoneExceptions);
     setEventListener("popupPolicyButton", "command",
       gPrivacyPane.showPopupExceptions);
-    setEventListener("autoplayMediaCheckbox", "command",
-      gPrivacyPane.toggleAutoplayMedia);
-    setEventListener("autoplayMediaPolicyButton", "command",
-      gPrivacyPane.showAutoplayMediaExceptions);
     setEventListener("notificationsDoNotDisturb", "command",
       gPrivacyPane.toggleDoNotDisturbNotifications);
 
     if (AlertsServiceDND) {
       let notificationsDoNotDisturbBox =
         document.getElementById("notificationsDoNotDisturbBox");
       notificationsDoNotDisturbBox.removeAttribute("hidden");
       let checkbox = document.getElementById("notificationsDoNotDisturb");
@@ -1286,41 +1280,20 @@ var gPrivacyPane = {
 
     gSubDialog.open("chrome://browser/content/preferences/sitePermissions.xul",
       "resizable=yes", params);
   },
 
 
   // MEDIA
 
-  blockAutoplayReadPrefs() {
-    let blocked =
-      Preferences.get("media.autoplay.default").value == Ci.nsIAutoplay.BLOCKED;
-    document.getElementById("autoplayMediaCheckbox").checked = blocked;
-  },
+  showAutoplayMediaExceptions() {
+    var params = { permissionType: "autoplay-media" };
 
-  /**
-   * The checkbox enabled sets the pref to BLOCKED
-   */
-  toggleAutoplayMedia(event) {
-    let blocked = event.target.checked ? Ci.nsIAutoplay.BLOCKED : Ci.nsIAutoplay.ALLOWED;
-    Services.prefs.setIntPref("media.autoplay.default", blocked);
-  },
-
-  /**
-   * Displays the autoplay exceptions dialog where specific site autoplay preferences
-   * can be set.
-   */
-  showAutoplayMediaExceptions() {
-    var params = {
-      blockVisible: true, sessionVisible: false, allowVisible: true,
-      prefilledHost: "", permissionType: "autoplay-media",
-    };
-
-    gSubDialog.open("chrome://browser/content/preferences/permissions.xul",
+    gSubDialog.open("chrome://browser/content/preferences/sitePermissions.xul",
       "resizable=yes", params);
   },
 
   // POP-UPS
 
   /**
    * Displays the popup exceptions dialog where specific site popup preferences
    * can be set.
--- a/browser/components/preferences/in-content/privacy.xul
+++ b/browser/components/preferences/in-content/privacy.xul
@@ -703,43 +703,47 @@
                   permissions-button-ok.label,
                   permissions-site-notification-window.title,
                   permissions-site-notification-desc,
                   permissions-site-notification-disable-label,
                   permissions-site-notification-disable-desc,
                 " />
       </hbox>
     </hbox>
-  </vbox>
+
+    <vbox id="notificationsDoNotDisturbBox" hidden="true">
+      <checkbox id="notificationsDoNotDisturb" class="indent"/>
+    </vbox>
 
-  <vbox id="notificationsDoNotDisturbBox" hidden="true">
-    <checkbox id="notificationsDoNotDisturb" class="indent"/>
+    <hbox id="autoplaySettingsRow" align="center" role="group" aria-labelledby="autoplayPermissionsLabel">
+      <description flex="1">
+        <image class="autoplay-icon permission-icon" />
+        <separator orient="vertical" class="thin"/>
+        <label id="autoplayPermissionsLabel"
+               data-l10n-id="permissions-autoplay"/>
+      </description>
+      <hbox pack="end">
+        <button id="autoplaySettingsButton"
+                is="highlightable-button"
+                class="accessory-button"
+                data-l10n-id="permissions-autoplay-settings"
+                search-l10n-ids="
+                  permissions-remove.label,
+                  permissions-remove-all.label,
+                  permissions-button-cancel.label,
+                  permissions-button-ok.label,
+                  permissions-site-autoplay-window.title,
+                  permissions-site-autoplay-desc,
+                " />
+      </hbox>
+    </hbox>
   </vbox>
 
   <separator flex="1"/>
 
-  <hbox align="start" id="autoplayMediaCheckboxWrapper">
-    <checkbox id="autoplayMediaCheckbox"
-              data-l10n-id="permissions-block-autoplay-media2"
-              flex="1" />
-    <!-- Please don't remove the wrapping hbox/vbox/box for these elements. It's used to properly compute the search tooltip position. -->
-    <hbox>
-      <button id="autoplayMediaPolicyButton"
-              is="highlightable-button"
-              class="accessory-button"
-              data-l10n-id="permissions-block-autoplay-media-exceptions"
-              search-l10n-ids="permissions-address,
-                               permissions-button-cancel.label,
-                               permissions-button-ok.label,
-                               permissions-exceptions-autoplay-media-window2.title,
-                               permissions-exceptions-autoplay-media-desc2
-                               " />
-    </hbox>
-  </hbox>
-
   <hbox data-subcategory="permissions-block-popups">
     <checkbox id="popupPolicy" preference="dom.disable_open_during_load"
               data-l10n-id="permissions-block-popups"
               onsyncfrompreference="return gPrivacyPane.updateButtons('popupPolicyButton',
                                          'dom.disable_open_during_load');"
               flex="1" />
     <!-- Please don't remove the wrapping hbox/vbox/box for these elements. It's used to properly compute the search tooltip position. -->
     <hbox>
--- a/browser/components/preferences/in-content/tests/browser.ini
+++ b/browser/components/preferences/in-content/tests/browser.ini
@@ -80,17 +80,16 @@ skip-if = e10s
 [browser_privacypane_3.js]
 [browser_sanitizeOnShutdown_prefLocked.js]
 [browser_searchShowSuggestionsFirst.js]
 [browser_searchsuggestions.js]
 [browser_security-1.js]
 [browser_security-2.js]
 [browser_spotlight.js]
 [browser_site_login_exceptions.js]
-[browser_site_autoplay_media_exceptions.js]
 [browser_permissions_dialog.js]
 [browser_subdialogs.js]
 support-files =
   subdialog.xul
   subdialog2.xul
 [browser_sync_disabled.js]
 [browser_sync_sanitize.js]
 skip-if = os == 'win' && processor == "x86_64" && bits == 64 # bug 1522821
--- a/browser/components/preferences/in-content/tests/browser_permissions_dialog.js
+++ b/browser/components/preferences/in-content/tests/browser_permissions_dialog.js
@@ -10,17 +10,17 @@ var {Services} = ChromeUtils.import("res
 const PERMISSIONS_URL = "chrome://browser/content/preferences/sitePermissions.xul";
 const URL = "http://www.example.com";
 const URI = Services.io.newURI(URL);
 var sitePermissionsDialog;
 
 function checkPermissionItem(origin, state) {
   let doc = sitePermissionsDialog.document;
 
-  let label = doc.getElementsByTagName("label")[2];
+  let label = doc.getElementsByTagName("label")[3];
   Assert.equal(label.value, origin);
 
   let menulist = doc.getElementsByTagName("menulist")[0];
   Assert.equal(menulist.value, state);
 }
 
 async function openPermissionsDialog() {
   let dialogOpened = promiseLoadSubDialog(PERMISSIONS_URL);
deleted file mode 100644
--- a/browser/components/preferences/in-content/tests/browser_site_autoplay_media_exceptions.js
+++ /dev/null
@@ -1,100 +0,0 @@
-"use strict";
-
-const URL = "http://www.example.com";
-const PRINCIPAL = Services.scriptSecurityManager
-  .createCodebasePrincipal(Services.io.newURI(URL), {});
-
-const PERMISSIONS_URL = "chrome://browser/content/preferences/permissions.xul";
-const AUTOPLAY_ENABLED_KEY = "media.autoplay.default";
-
-var exceptionsDialog;
-
-Services.prefs.setIntPref(AUTOPLAY_ENABLED_KEY, Ci.nsIAutoplay.ALLOWED);
-
-async function openExceptionsDialog() {
-  let dialogOpened = promiseLoadSubDialog(PERMISSIONS_URL);
-  await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
-    let exceptionsButton = content.document.getElementById("autoplayMediaPolicyButton");
-    exceptionsButton.click();
-  });
-  exceptionsDialog = await dialogOpened;
-}
-
-add_task(async function ensureCheckboxVisible() {
-  registerCleanupFunction(async function() {
-    Services.prefs.clearUserPref(AUTOPLAY_ENABLED_KEY);
-    gBrowser.removeCurrentTab();
-  });
-
-  await openPreferencesViaOpenPreferencesAPI("privacy", {leaveOpen: true});
-  let win = gBrowser.selectedBrowser.contentWindow;
-  is_element_visible(win.document.getElementById("autoplayMediaCheckbox"),
-                    "Ensure checkbox is visible");
-});
-
-add_task(async function enableBlockingAutoplay() {
-  await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
-    let doc = content.document;
-    let autoplayCheckBox = doc.getElementById("autoplayMediaCheckbox");
-    autoplayCheckBox.click();
-  });
-
-  Assert.equal(Services.prefs.getIntPref(AUTOPLAY_ENABLED_KEY),
-               Ci.nsIAutoplay.BLOCKED,
-               "Ensure we have set autoplay to false");
-});
-
-add_task(async function addException() {
-  await openExceptionsDialog();
-  let doc = exceptionsDialog.document;
-
-  let richlistbox = doc.getElementById("permissionsBox");
-  Assert.equal(richlistbox.itemCount, 0, "Row count should initially be 0");
-
-  let inputBox = doc.getElementById("url");
-  inputBox.focus();
-
-  EventUtils.sendString(URL, exceptionsDialog);
-
-  let btnAllow = doc.getElementById("btnAllow");
-  btnAllow.click();
-
-  await TestUtils.waitForCondition(() => richlistbox.itemCount == 1);
-  Assert.equal(richlistbox.getItemAtIndex(0).getAttribute("origin"), URL);
-
-  let permChanged = TestUtils.topicObserved("perm-changed");
-  let btnApplyChanges = doc.getElementById("btnApplyChanges");
-  btnApplyChanges.click();
-  await permChanged;
-
-  is(Services.perms.testPermissionFromPrincipal(PRINCIPAL, "autoplay-media"),
-     Ci.nsIPermissionManager.ALLOW_ACTION, "Correctly added the exception");
-});
-
-add_task(async function deleteException() {
-  await openExceptionsDialog();
-  let doc = exceptionsDialog.document;
-
-  let richlistbox = doc.getElementById("permissionsBox");
-  Assert.equal(richlistbox.itemCount, 1, "Row count should initially be 1");
-  richlistbox.focus();
-  richlistbox.selectedIndex = 0;
-
-  if (AppConstants.platform == "macosx") {
-    EventUtils.synthesizeKey("KEY_Backspace");
-  } else {
-    EventUtils.synthesizeKey("KEY_Delete");
-  }
-
-  await TestUtils.waitForCondition(() => richlistbox.itemCount == 0);
-  is_element_visible(content.gSubDialog._dialogs[0]._box,
-    "Subdialog is visible after deleting an element");
-
-  let permChanged = TestUtils.topicObserved("perm-changed");
-  let btnApplyChanges = doc.getElementById("btnApplyChanges");
-  btnApplyChanges.click();
-  await permChanged;
-
-  is(Services.perms.testPermissionFromPrincipal(PRINCIPAL, "autoplay-media"),
-     Ci.nsIPermissionManager.UNKNOWN_ACTION, "Correctly removed the exception");
-});
--- a/browser/components/preferences/permissions.js
+++ b/browser/components/preferences/permissions.js
@@ -21,20 +21,16 @@ const permissionExceptionsL10n = {
   "login-saving": {
     window: "permissions-exceptions-saved-logins-window",
     description: "permissions-exceptions-saved-logins-desc",
   },
   "install": {
     window: "permissions-exceptions-addons-window",
     description: "permissions-exceptions-addons-desc",
   },
-  "autoplay-media": {
-    window: "permissions-exceptions-autoplay-media-window2",
-    description: "permissions-exceptions-autoplay-media-desc2",
-  },
 };
 
 function Permission(principal, type, capability) {
   this.principal = principal;
   this.origin = principal.origin;
   this.type = type;
   this.capability = capability;
 }
--- a/browser/components/preferences/sitePermissions.js
+++ b/browser/components/preferences/sitePermissions.js
@@ -28,30 +28,56 @@ const sitePermissionsL10n = {
     disableDescription: "permissions-site-camera-disable-desc",
   },
   "microphone": {
     window: "permissions-site-microphone-window",
     description: "permissions-site-microphone-desc",
     disableLabel: "permissions-site-microphone-disable-label",
     disableDescription: "permissions-site-microphone-disable-desc",
   },
+  "autoplay-media": {
+    window: "permissions-site-autoplay-window",
+    description: "permissions-site-autoplay-desc",
+  },
+};
+
+const sitePermissionsConfig = {
+  "autoplay-media": {
+    _getCapabilityString(capability) {
+      switch (capability) {
+      case SitePermissions.ALLOW:
+        return "permissions-capabilities-autoplay-allow";
+      case SitePermissions.BLOCK:
+        return "permissions-capabilities-autoplay-block";
+      case SitePermissions.AUTOPLAY_BLOCKED_ALL:
+        return "permissions-capabilities-autoplay-blockall";
+      }
+      throw new Error(`Unknown capability: ${capability}`);
+    },
+  },
 };
 
 function Permission(principal, type, capability, l10nId) {
   this.principal = principal;
   this.origin = principal.origin;
   this.type = type;
   this.capability = capability;
   this.l10nId = l10nId;
 }
 
-const PERMISSION_STATES = [SitePermissions.ALLOW, SitePermissions.BLOCK, SitePermissions.PROMPT];
+const PERMISSION_STATES = [
+  SitePermissions.ALLOW, SitePermissions.BLOCK,
+  SitePermissions.PROMPT, SitePermissions.AUTOPLAY_BLOCKED_ALL,
+];
+
 const NOTIFICATIONS_PERMISSION_OVERRIDE_KEY = "webNotificationsDisabled";
 const NOTIFICATIONS_PERMISSION_PREF = "permissions.default.desktop-notification";
 
+const AUTOPLAY_PREF = "media.autoplay.default";
+
 var gSitePermissionsManager = {
   _type: "",
   _isObserving: false,
   _permissions: new Map(),
   _permissionsToChange: new Map(),
   _permissionsToDelete: new Map(),
   _list: null,
   _removeButton: null,
@@ -75,23 +101,28 @@ var gSitePermissionsManager = {
     this._type = params.permissionType;
     this._list = document.getElementById("permissionsBox");
     this._removeButton = document.getElementById("removePermission");
     this._removeAllButton = document.getElementById("removeAllPermissions");
     this._searchBox = document.getElementById("searchBox");
     this._checkbox = document.getElementById("permissionsDisableCheckbox");
     this._disableExtensionButton = document.getElementById("disableNotificationsPermissionExtension");
     this._permissionsDisableDescription = document.getElementById("permissionsDisableDescription");
+    this._setAutoplayPref = document.getElementById("setAutoplayPref");
 
     let permissionsText = document.getElementById("permissionsText");
 
     let l10n = sitePermissionsL10n[this._type];
     document.l10n.setAttributes(permissionsText, l10n.description);
-    document.l10n.setAttributes(this._checkbox, l10n.disableLabel);
-    document.l10n.setAttributes(this._permissionsDisableDescription, l10n.disableDescription);
+    if (l10n.disableLabel) {
+      document.l10n.setAttributes(this._checkbox, l10n.disableLabel);
+    }
+    if (l10n.disableDescription) {
+      document.l10n.setAttributes(this._permissionsDisableDescription, l10n.disableDescription);
+    }
     document.l10n.setAttributes(document.documentElement, l10n.window);
 
     await document.l10n.translateElements([
       permissionsText,
       this._checkbox,
       this._permissionsDisableDescription,
       document.documentElement,
     ]);
@@ -99,24 +130,32 @@ var gSitePermissionsManager = {
     // Initialize the checkbox state and handle showing notification permission UI
     // when it is disabled by an extension.
     this._defaultPermissionStatePrefName = "permissions.default." + this._type;
     this._watchPermissionPrefChange();
 
     this._loadPermissions();
     this.buildPermissionsList();
 
+    if (params.permissionType == "autoplay-media") {
+      this.buildAutoplayMenulist();
+      this._setAutoplayPref.hidden = false;
+    }
+
     this._searchBox.focus();
   },
 
   uninit() {
     if (this._isObserving) {
       Services.obs.removeObserver(this, "perm-changed");
       this._isObserving = false;
     }
+    if (this._setAutoplayPref) {
+      this._setAutoplayPref.hidden = true;
+    }
   },
 
   observe(subject, topic, data) {
     if (topic !== "perm-changed")
       return;
 
     let permission = subject.QueryInterface(Ci.nsIPermission);
 
@@ -125,17 +164,17 @@ var gSitePermissionsManager = {
       return;
 
     if (data == "added") {
       this._addPermissionToList(permission);
       this.buildPermissionsList();
     } else if (data == "changed") {
       let p = this._permissions.get(permission.principal.origin);
       p.capability = permission.capability;
-      p.l10nId = this._getCapabilityString(permission.capability);
+      p.l10nId = this._getCapabilityString(permission.type, permission.capability);
       this._handleCapabilityChange(p);
       this.buildPermissionsList();
     } else if (data == "deleted") {
       this._removePermissionFromList(permission.principal.origin);
     }
   },
 
   _handleCapabilityChange(perm) {
@@ -202,39 +241,39 @@ var gSitePermissionsManager = {
       // An extension can't control these settings if they're locked.
       hideControllingExtension(NOTIFICATIONS_PERMISSION_OVERRIDE_KEY);
     } else {
       let isControlled = await handleControllingExtension(PREF_SETTING_TYPE, NOTIFICATIONS_PERMISSION_OVERRIDE_KEY);
       this._checkbox.disabled = isControlled;
     }
   },
 
-  _getCapabilityString(capability) {
-    let stringKey = null;
+  _getCapabilityString(type, capability) {
+    if (type in sitePermissionsConfig &&
+        sitePermissionsConfig[type]._getCapabilityString) {
+      return sitePermissionsConfig[type]._getCapabilityString(capability);
+    }
+
     switch (capability) {
     case Services.perms.ALLOW_ACTION:
-      stringKey = "permissions-capabilities-allow";
-      break;
+      return "permissions-capabilities-allow";
     case Services.perms.DENY_ACTION:
-      stringKey = "permissions-capabilities-block";
-      break;
+      return "permissions-capabilities-block";
     case Services.perms.PROMPT_ACTION:
-      stringKey = "permissions-capabilities-prompt";
-      break;
+      return "permissions-capabilities-prompt";
     default:
       throw new Error(`Unknown capability: ${capability}`);
     }
-    return stringKey;
   },
 
   _addPermissionToList(perm) {
     // Ignore unrelated permission types and permissions with unknown states.
     if (perm.type !== this._type || !PERMISSION_STATES.includes(perm.capability))
       return;
-    let l10nId = this._getCapabilityString(perm.capability);
+    let l10nId = this._getCapabilityString(perm.type, perm.capability);
     let p = new Permission(perm.principal, perm.type, perm.capability, l10nId);
     this._permissions.set(p.origin, p);
   },
 
   _removePermissionFromList(origin) {
     this._permissions.delete(origin);
     let permissionlistitem = document.getElementsByAttribute("origin", origin)[0];
     if (permissionlistitem) {
@@ -245,46 +284,47 @@ var gSitePermissionsManager = {
   _loadPermissions() {
     // load permissions into a table.
     for (let nextPermission of Services.perms.enumerator) {
       this._addPermissionToList(nextPermission);
     }
   },
 
   _createPermissionListItem(permission) {
+    let width = (permission.type == "autoplay-media") ? "75" : "50";
     let richlistitem = document.createXULElement("richlistitem");
     richlistitem.setAttribute("origin", permission.origin);
     let row = document.createXULElement("hbox");
     row.setAttribute("flex", "1");
 
     let hbox = document.createXULElement("hbox");
     let website = document.createXULElement("label");
     website.setAttribute("value", permission.origin);
-    website.setAttribute("width", "50");
+    website.setAttribute("width", width);
     hbox.setAttribute("class", "website-name");
     hbox.setAttribute("flex", "3");
     hbox.appendChild(website);
 
     let menulist = document.createXULElement("menulist");
     menulist.setAttribute("flex", "1");
-    menulist.setAttribute("width", "50");
+    menulist.setAttribute("width", width);
     menulist.setAttribute("class", "website-status");
     let states = SitePermissions.getAvailableStates(permission.type);
     for (let state of states) {
       // Work around the (rare) edge case when a user has changed their
       // default permission type back to UNKNOWN while still having a
       // PROMPT permission set for an origin.
       if (state == SitePermissions.UNKNOWN &&
           permission.capability == SitePermissions.PROMPT) {
         state = SitePermissions.PROMPT;
       } else if (state == SitePermissions.UNKNOWN) {
         continue;
       }
       let m = menulist.appendItem(undefined, state);
-      document.l10n.setAttributes(m, this._getCapabilityString(state));
+      document.l10n.setAttributes(m, this._getCapabilityString(permission.type, state));
     }
     menulist.value = permission.capability;
 
     menulist.addEventListener("select", () => {
       this.onPermissionChange(permission, Number(menulist.value));
     });
 
     row.appendChild(hbox);
@@ -355,17 +395,17 @@ var gSitePermissionsManager = {
     });
   },
 
   onPermissionChange(perm, capability) {
     let p = this._permissions.get(perm.origin);
     if (p.capability == capability)
       return;
     p.capability = capability;
-    p.l10nId = this._getCapabilityString(capability);
+    p.l10nId = this._getCapabilityString(perm.type, perm.capability);
     this._permissionsToChange.set(p.origin, p);
 
     // enable "remove all" button as needed
     this._setRemoveButtonState();
   },
 
   onApplyChanges() {
     // Stop observing permission changes since we are about
@@ -413,16 +453,33 @@ var gSitePermissionsManager = {
     // Sort permissions.
     this._sortPermissions(this._list, frag, sortCol);
 
     this._list.appendChild(frag);
 
     this._setRemoveButtonState();
   },
 
+  buildAutoplayMenulist() {
+    let menulist = document.createXULElement("menulist");
+    let states = SitePermissions.getAvailableStates("autoplay-media");
+    for (let state of states) {
+      let m = menulist.appendItem(undefined, state);
+      document.l10n.setAttributes(m, this._getCapabilityString("autoplay-media", state));
+    }
+
+    menulist.value = SitePermissions.getDefault("autoplay-media");
+
+    menulist.addEventListener("select", () => {
+      SitePermissions.setDefault("autoplay-media", Number(menulist.value));
+    });
+
+    document.getElementById("setAutoplayPref").appendChild(menulist);
+  },
+
   _sortPermissions(list, frag, column) {
     let sortDirection;
 
     if (!column) {
       column = document.querySelector("treecol[data-isCurrentSortCol=true]");
       sortDirection = column.getAttribute("data-last-sortDirection") || "ascending";
     } else {
       sortDirection = column.getAttribute("data-last-sortDirection");
--- a/browser/components/preferences/sitePermissions.xul
+++ b/browser/components/preferences/sitePermissions.xul
@@ -27,16 +27,20 @@
   <script src="chrome://browser/content/preferences/sitePermissions.js"/>
   <script src="chrome://browser/content/preferences/in-content/extensionControlled.js"/>
 
   <keyset>
     <key data-l10n-id="permissions-close-key" modifiers="accel" oncommand="window.close();"/>
   </keyset>
 
   <vbox class="contentPane">
+
+    <hbox align="center" id="setAutoplayPref" hidden="true">
+      <label data-l10n-id="permissions-autoplay-menu"/>
+    </hbox>
     <description id="permissionsText" control="url"/>
     <separator class="thin"/>
     <hbox align="start">
       <textbox id="searchBox" flex="1" data-l10n-id="permissions-searchbox"
                is="search-textbox" oncommand="gSitePermissionsManager.buildPermissionsList();"/>
     </hbox>
     <separator class="thin"/>
     <listheader>
--- a/browser/locales/en-US/browser/preferences/permissions.ftl
+++ b/browser/locales/en-US/browser/preferences/permissions.ftl
@@ -41,19 +41,28 @@ permissions-remove-all =
 permissions-button-cancel =
     .label = Cancel
     .accesskey = C
 
 permissions-button-ok =
     .label = Save Changes
     .accesskey = S
 
+permissions-autoplay-menu = Default for all websites:
+
 permissions-searchbox =
     .placeholder = Search Website
 
+permissions-capabilities-autoplay-allow =
+    .label = Allow Audio and Video
+permissions-capabilities-autoplay-block =
+    .label = Block Audio
+permissions-capabilities-autoplay-blockall =
+    .label = Block Audio and Video
+
 permissions-capabilities-allow =
     .label = Allow
 permissions-capabilities-block =
     .label = Block
 permissions-capabilities-prompt =
     .label = Always Ask
 
 permissions-capabilities-listitem-allow =
@@ -98,22 +107,22 @@ permissions-exceptions-saved-logins-desc
 
 ## Exceptions - Add-ons
 
 permissions-exceptions-addons-window =
     .title = Allowed Websites - Add-ons Installation
     .style = { permissions-window.style }
 permissions-exceptions-addons-desc = You can specify which websites are allowed to install add-ons. Type the exact address of the site you want to allow and then click Allow.
 
-## Exceptions - Autoplay Media
+## Site Permissions - Autoplay
 
-permissions-exceptions-autoplay-media-window2 =
-    .title = Exceptions - Autoplay
+permissions-site-autoplay-window =
+    .title = Settings - Autoplay
     .style = { permissions-window.style }
-permissions-exceptions-autoplay-media-desc2 = You can specify which websites are always or never allowed to autoplay media with sound. Type the address of the site you want to manage and then click Block or Allow.
+permissions-site-autoplay-desc = You can manage the sites that do not follow your default autoplay settings here.
 
 ## Site Permissions - Notifications
 
 permissions-site-notification-window =
     .title = Settings - Notification Permissions
     .style = { permissions-window.style }
 permissions-site-notification-desc = The following websites have requested to send you notifications. You can specify which websites are allowed to send you notifications. You can also block new requests asking to allow notifications.
 permissions-site-notification-disable-label =
--- a/browser/locales/en-US/browser/preferences/preferences.ftl
+++ b/browser/locales/en-US/browser/preferences/preferences.ftl
@@ -882,17 +882,17 @@ content-blocking-option-private =
   .label = Only in Private Windows
   .accesskey = p
 content-blocking-tracking-protection-change-block-list = Change block list
 
 content-blocking-cookies-label =
   .label = Cookies
   .accesskey = C
 
-content-blocking-expand-section = 
+content-blocking-expand-section =
   .tooltiptext = More information
 
 # Cryptomining refers to using scripts on websites that can use a computer’s resources to mine cryptocurrency without a user’s knowledge.
 content-blocking-cryptominers-label =
   .label = Cryptominers
   .accesskey = y
 
 # Browser fingerprinting is a method of tracking users by the configuration and settings information (their "digital fingerprint")
@@ -931,23 +931,21 @@ permissions-notification-settings =
     .label = Settings…
     .accesskey = t
 permissions-notification-link = Learn more
 
 permissions-notification-pause =
     .label = Pause notifications until { -brand-short-name } restarts
     .accesskey = n
 
-permissions-block-autoplay-media2 =
-    .label = Block websites from automatically playing sound
-    .accesskey = B
+permissions-autoplay = Autoplay
 
-permissions-block-autoplay-media-exceptions =
-    .label = Exceptions…
-    .accesskey = E
+permissions-autoplay-settings =
+    .label = Settings…
+    .accesskey = t
 
 permissions-block-popups =
     .label = Block pop-up windows
     .accesskey = B
 
 permissions-block-popups-exceptions =
     .label = Exceptions…
     .accesskey = E
--- a/browser/locales/en-US/chrome/browser/sitePermissions.properties
+++ b/browser/locales/en-US/chrome/browser/sitePermissions.properties
@@ -23,16 +23,20 @@ state.current.hide = Hide Prompt
 #                    state.multichoice.allowForSession,
 #                    state.multichoice.block):
 # Used to label permission state checkboxes in the page info dialog.
 state.multichoice.alwaysAsk = Always Ask
 state.multichoice.allow = Allow
 state.multichoice.allowForSession = Allow for Session
 state.multichoice.block = Block
 
+state.multichoice.autoplayblock = Block Audio
+state.multichoice.autoplayblockall = Block Audio and Video
+state.multichoice.autoplayallow = Allow Audio and Video
+
 permission.autoplay-media2.label = Autoplay sound
 permission.cookie.label = Set Cookies
 permission.desktop-notification3.label = Send Notifications
 permission.image.label = Load Images
 permission.camera.label = Use the Camera
 permission.microphone.label = Use the Microphone
 permission.screen.label = Share the Screen
 permission.install.label = Install Add-ons
--- a/browser/modules/SitePermissions.jsm
+++ b/browser/modules/SitePermissions.jsm
@@ -206,17 +206,17 @@ const GloballyBlockedPermissions = {
     let permissions = [];
     let entry = this._stateByBrowser.get(browser);
     let prePath = browser.currentURI.prePath;
     if (entry && entry[prePath]) {
       let timeStamps = entry[prePath];
       for (let id of Object.keys(timeStamps)) {
         permissions.push({
           id,
-          state: SitePermissions.BLOCK,
+          state: gPermissionObject[id].getDefault(),
           scope: SitePermissions.SCOPE_GLOBAL,
         });
       }
     }
     return permissions;
   },
 
   // Copies the globally blocked permission state of one browser
@@ -241,16 +241,17 @@ var SitePermissions = {
   // PROMPT_HIDE state is only used to show the "Hide Prompt" state in the identity panel
   // for the "plugin:flash" permission and not in pageinfo.
   UNKNOWN: Services.perms.UNKNOWN_ACTION,
   ALLOW: Services.perms.ALLOW_ACTION,
   BLOCK: Services.perms.DENY_ACTION,
   PROMPT: Services.perms.PROMPT_ACTION,
   ALLOW_COOKIES_FOR_SESSION: Ci.nsICookiePermission.ACCESS_SESSION,
   PROMPT_HIDE: Ci.nsIObjectLoadingContent.PLUGIN_PERMISSION_PROMPT_ACTION_QUIET,
+  AUTOPLAY_BLOCKED_ALL: Ci.nsIAutoplay.BLOCKED_ALL,
 
   // Permission scopes.
   SCOPE_REQUEST: "{SitePermissions.SCOPE_REQUEST}",
   SCOPE_TEMPORARY: "{SitePermissions.SCOPE_TEMPORARY}",
   SCOPE_SESSION: "{SitePermissions.SCOPE_SESSION}",
   SCOPE_PERSISTENT: "{SitePermissions.SCOPE_PERSISTENT}",
   SCOPE_POLICY: "{SitePermissions.SCOPE_POLICY}",
   SCOPE_GLOBAL: "{SitePermissions.SCOPE_GLOBAL}",
@@ -480,16 +481,33 @@ var SitePermissions = {
         gPermissionObject[permissionID].getDefault)
       return gPermissionObject[permissionID].getDefault();
 
     // Otherwise try to get the default preference for that permission.
     return this._defaultPrefBranch.getIntPref(permissionID, this.UNKNOWN);
   },
 
   /**
+   * Set the default state of a particular permission.
+   *
+   * @param {string} permissionID
+   *        The ID to set the default for.
+   *
+   * @param {string} state
+   *        The state to set.
+   */
+  setDefault(permissionID, state) {
+    if (permissionID in gPermissionObject &&
+        gPermissionObject[permissionID].setDefault) {
+      return gPermissionObject[permissionID].setDefault(state);
+    }
+    let key = "permissions.default." + permissionID;
+    return Services.prefs.setIntPref(key, state);
+  },
+  /**
    * Returns the state and scope of a particular permission for a given URI.
    *
    * This method will NOT dispatch a "PermissionStateChange" event on the specified
    * browser if a temporary permission was removed because it has expired.
    *
    * @param {nsIURI} uri
    *        The URI to check.
    * @param {String} permissionID
@@ -765,17 +783,24 @@ var SitePermissions = {
    * a UI for managing permissions.
    *
    * @param {SitePermissions state} state
    *        The state to get the label for.
    *
    * @return {String|null} the localized label or null if an
    *         unknown state was passed.
    */
-  getMultichoiceStateLabel(state) {
+  getMultichoiceStateLabel(permissionID, state) {
+    // If the permission has custom logic for getting its default value,
+    // try that first.
+    if (permissionID in gPermissionObject &&
+        gPermissionObject[permissionID].getMultichoiceStateLabel) {
+      return gPermissionObject[permissionID].getMultichoiceStateLabel(state);
+    }
+
     switch (state) {
       case this.UNKNOWN:
       case this.PROMPT:
         return gStringBundle.GetStringFromName("state.multichoice.alwaysAsk");
       case this.ALLOW:
         return gStringBundle.GetStringFromName("state.multichoice.allow");
       case this.ALLOW_COOKIES_FOR_SESSION:
         return gStringBundle.GetStringFromName("state.multichoice.allowForSession");
@@ -850,27 +875,52 @@ var gPermissionObject = {
    *    Defaults to ALLOW, BLOCK and the default state (see getDefault).
    *    The PROMPT_HIDE state is deliberately excluded from "plugin:flash" since we
    *    don't want to expose a "Hide Prompt" button to the user through pageinfo.
    */
 
   "autoplay-media": {
     exactHostMatch: true,
     getDefault() {
-      let state = Services.prefs.getIntPref("media.autoplay.default",
+      let pref = Services.prefs.getIntPref("media.autoplay.default",
                                             Ci.nsIAutoplay.BLOCKED);
-      if (state == Ci.nsIAutoplay.ALLOWED) {
+      if (pref == Ci.nsIAutoplay.ALLOWED) {
         return SitePermissions.ALLOW;
-      } else if (state == Ci.nsIAutoplay.BLOCKED) {
-        return SitePermissions.BLOCK;
+      }
+      if (pref == Ci.nsIAutoplay.BLOCKED_ALL) {
+        return SitePermissions.AUTOPLAY_BLOCKED_ALL;
       }
-      return SitePermissions.UNKNOWN;
+      return SitePermissions.BLOCK;
+    },
+    setDefault(value) {
+      let prefValue = Ci.nsIAutoplay.BLOCKED;
+      if (value == SitePermissions.ALLOW) {
+        prefValue = Ci.nsIAutoplay.ALLOWED;
+      } else if (value == SitePermissions.AUTOPLAY_BLOCKED_ALL) {
+        prefValue = Ci.nsIAutoplay.BLOCKED_ALL;
+      }
+      Services.prefs.setIntPref("media.autoplay.default", prefValue);
     },
     labelID: "autoplay-media2",
-    states: [ SitePermissions.ALLOW, SitePermissions.BLOCK ],
+    states: [
+      SitePermissions.ALLOW,
+      SitePermissions.BLOCK,
+      SitePermissions.AUTOPLAY_BLOCKED_ALL,
+    ],
+    getMultichoiceStateLabel(state) {
+      switch (state) {
+        case SitePermissions.AUTOPLAY_BLOCKED_ALL:
+          return gStringBundle.GetStringFromName("state.multichoice.autoplayblockall");
+        case SitePermissions.BLOCK:
+          return gStringBundle.GetStringFromName("state.multichoice.autoplayblock");
+        case SitePermissions.ALLOW:
+          return gStringBundle.GetStringFromName("state.multichoice.autoplayallow");
+      }
+      throw new Error(`Unkown state: ${state}`);
+    },
   },
 
   "image": {
     states: [ SitePermissions.ALLOW, SitePermissions.BLOCK ],
   },
 
   "cookie": {
     states: [ SitePermissions.ALLOW, SitePermissions.ALLOW_COOKIES_FOR_SESSION, SitePermissions.BLOCK ],
--- a/browser/themes/shared/incontentprefs/privacy.css
+++ b/browser/themes/shared/incontentprefs/privacy.css
@@ -77,16 +77,20 @@
 .microphone-icon {
   list-style-image: url(chrome://browser/skin/notification-icons/microphone.svg);
 }
 
 .desktop-notification-icon {
   list-style-image: url(chrome://browser/skin/notification-icons/desktop-notification.svg);
 }
 
+.autoplay-icon {
+  list-style-image: url(chrome://browser/skin/notification-icons/autoplay-media-detailed.svg);
+}
+
 .midi-icon {
   list-style-image: url(chrome://browser/skin/notification-icons/midi.svg);
 }
 
 /* Content Blocking */
 
 /* Override styling that sets descriptions as grey */
 #trackingGroup description.indent,
--- a/dom/media/AutoplayPolicy.cpp
+++ b/dom/media/AutoplayPolicy.cpp
@@ -16,16 +16,18 @@
 #include "nsGlobalWindowInner.h"
 #include "nsIAutoplay.h"
 #include "nsContentUtils.h"
 #include "mozilla/dom/Document.h"
 #include "MediaManager.h"
 #include "nsIDocShell.h"
 #include "nsIDocShellTreeItem.h"
 #include "nsPIDOMWindow.h"
+#include "mozilla/Services.h"
+#include "nsIPermissionManager.h"
 
 mozilla::LazyLogModule gAutoplayPermissionLog("Autoplay");
 
 #define AUTOPLAY_LOG(msg, ...) \
   MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
 
 namespace mozilla {
 namespace dom {
@@ -58,28 +60,28 @@ static bool IsActivelyCapturingOrHasAPer
   return (nsContentUtils::IsExactSitePermAllow(principal,
                                                NS_LITERAL_CSTRING("camera")) ||
           nsContentUtils::IsExactSitePermAllow(
               principal, NS_LITERAL_CSTRING("microphone")) ||
           nsContentUtils::IsExactSitePermAllow(principal,
                                                NS_LITERAL_CSTRING("screen")));
 }
 
-static bool IsSiteInAutoplayWhiteList(const Document* aDocument) {
-  return aDocument ? nsContentUtils::IsExactSitePermAllow(
-                         aDocument->NodePrincipal(),
-                         NS_LITERAL_CSTRING("autoplay-media"))
-                   : false;
-}
+static uint32_t SiteAutoplayPerm(const Document* aDocument) {
+  if (!aDocument) {
+    return nsIPermissionManager::DENY_ACTION;
+  }
+  nsIPrincipal* principal = aDocument->NodePrincipal();
+  nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
+  NS_ENSURE_TRUE(permMgr, nsIPermissionManager::DENY_ACTION);
 
-static bool IsSiteInAutoplayBlackList(const Document* aDocument) {
-  return aDocument ? nsContentUtils::IsExactSitePermDeny(
-                         aDocument->NodePrincipal(),
-                         NS_LITERAL_CSTRING("autoplay-media"))
-                   : false;
+  uint32_t perm;
+  nsresult rv = permMgr->TestExactPermissionFromPrincipal(principal, NS_LITERAL_CSTRING("autoplay-media"), &perm);
+  NS_ENSURE_SUCCESS(rv, nsIPermissionManager::DENY_ACTION);
+  return perm;
 }
 
 static bool IsWindowAllowedToPlay(nsPIDOMWindowInner* aWindow) {
   if (!aWindow) {
     return false;
   }
 
   if (IsActivelyCapturingOrHasAPermission(aWindow)) {
@@ -114,103 +116,111 @@ static bool IsWindowAllowedToPlay(nsPIDO
   }
 
   return false;
 }
 
 static uint32_t DefaultAutoplayBehaviour() {
   int prefValue =
       Preferences::GetInt("media.autoplay.default", nsIAutoplay::ALLOWED);
-  if (prefValue < nsIAutoplay::ALLOWED || prefValue > nsIAutoplay::BLOCKED) {
-    // Invalid pref values are just converted to BLOCKED.
-    return nsIAutoplay::BLOCKED;
+  if (prefValue == nsIAutoplay::ALLOWED) {
+    return nsIAutoplay::ALLOWED;
   }
-  return prefValue;
+  if (prefValue == nsIAutoplay::BLOCKED_ALL) {
+    return nsIAutoplay::BLOCKED_ALL;
+  }
+  return nsIAutoplay::BLOCKED;
 }
 
-static bool IsMediaElementAllowedToPlay(const HTMLMediaElement& aElement) {
-  const bool isAllowedMuted =
-      Preferences::GetBool("media.autoplay.allow-muted", true);
-  if ((aElement.Volume() == 0.0 || aElement.Muted()) && isAllowedMuted) {
-    AUTOPLAY_LOG("Allow muted media %p to autoplay.", &aElement);
+static bool IsMediaElementInaudible(const HTMLMediaElement& aElement) {
+  if (aElement.Volume() == 0.0 || aElement.Muted()) {
+    AUTOPLAY_LOG("Media %p is muted.", &aElement);
     return true;
   }
 
   if (!aElement.HasAudio() &&
-      aElement.ReadyState() >= HTMLMediaElement_Binding::HAVE_METADATA &&
-      isAllowedMuted) {
-    AUTOPLAY_LOG("Allow media %p without audio track to autoplay", &aElement);
+      aElement.ReadyState() >= HTMLMediaElement_Binding::HAVE_METADATA) {
+    AUTOPLAY_LOG("Media %p has no audio track", &aElement);
     return true;
   }
 
   return false;
 }
 
 static bool IsAudioContextAllowedToPlay(const AudioContext& aContext) {
   // Offline context won't directly output sound to audio devices.
   return aContext.IsOffline() ||
          IsWindowAllowedToPlay(aContext.GetParentObject());
 }
 
 static bool IsEnableBlockingWebAudioByUserGesturePolicy() {
   return DefaultAutoplayBehaviour() != nsIAutoplay::ALLOWED &&
          Preferences::GetBool("media.autoplay.block-webaudio", false) &&
-         Preferences::GetBool("media.autoplay.enabled.user-gestures-needed",
-                              false);
+         StaticPrefs::MediaAutoplayUserGesturesNeeded();
 }
 
 /* static */
 bool AutoplayPolicy::WouldBeAllowedToPlayIfAutoplayDisabled(
     const HTMLMediaElement& aElement) {
-  return IsMediaElementAllowedToPlay(aElement) ||
+  return IsMediaElementInaudible(aElement) ||
          IsWindowAllowedToPlay(aElement.OwnerDoc()->GetInnerWindow());
 }
 
 /* static */
 bool AutoplayPolicy::WouldBeAllowedToPlayIfAutoplayDisabled(
     const AudioContext& aContext) {
   return IsAudioContextAllowedToPlay(aContext);
 }
 
+static bool IsAllowedToPlayByBlockingModel(const HTMLMediaElement& aElement) {
+  if (!StaticPrefs::MediaAutoplayUserGesturesNeeded()) {
+  // If element is blessed, it would always be allowed to play().
+  return aElement.IsBlessed() || EventStateManager::IsHandlingUserInput();
+  }
+  return IsWindowAllowedToPlay(aElement.OwnerDoc()->GetInnerWindow());
+}
+
 static bool IsAllowedToPlayInternal(const HTMLMediaElement& aElement) {
-  /**
-   * The autoplay checking has 4 different phases,
-   * 1. check whether media element itself meets the autoplay condition
-   * 2. check whethr the site is in the autoplay whitelist
-   * 3. check global autoplay setting and check wether the site is in the
-   *    autoplay blacklist.
-   * 4. check whether media is allowed under current blocking model
-   *    (click-to-play or user-gesture-activation)
-   */
-  if (IsMediaElementAllowedToPlay(aElement)) {
+  Document* approver = ApproverDocOf(*aElement.OwnerDoc());
+
+  bool isInaudible = IsMediaElementInaudible(aElement);
+  bool isUsingAutoplayModel = IsAllowedToPlayByBlockingModel(aElement);
+
+  uint32_t defaultBehaviour = DefaultAutoplayBehaviour();
+  uint32_t sitePermission = SiteAutoplayPerm(approver);
+
+  AUTOPLAY_LOG("IsAllowedToPlayInternal, isInaudible=%d,"
+    "isUsingAutoplayModel=%d, sitePermission=%d, defaultBehaviour=%d",
+    isInaudible, isUsingAutoplayModel, sitePermission, defaultBehaviour);
+
+  // For site permissions we store permissionManager values except
+  // for BLOCKED_ALL, for the default pref values we store
+  // nsIAutoplay values.
+  if (sitePermission == nsIPermissionManager::ALLOW_ACTION) {
     return true;
   }
 
-  Document* approver = ApproverDocOf(*aElement.OwnerDoc());
-  if (IsSiteInAutoplayWhiteList(approver)) {
-    AUTOPLAY_LOG(
-        "Allow autoplay as document has permanent autoplay permission.");
+  if (sitePermission == nsIPermissionManager::DENY_ACTION) {
+    return isInaudible || isUsingAutoplayModel;
+  }
+
+  if (sitePermission == nsIAutoplay::BLOCKED_ALL) {
+    return isUsingAutoplayModel;
+  }
+
+  if (defaultBehaviour == nsIAutoplay::ALLOWED) {
     return true;
   }
 
-  if (DefaultAutoplayBehaviour() == nsIAutoplay::ALLOWED &&
-      !(IsSiteInAutoplayBlackList(approver) &&
-        StaticPrefs::MediaAutoplayBlackListOverrideDefault())) {
-    AUTOPLAY_LOG(
-        "Allow autoplay as global autoplay setting is allowing autoplay by "
-        "default.");
-    return true;
+  if (defaultBehaviour == nsIAutoplay::BLOCKED) {
+    return isInaudible || isUsingAutoplayModel;
   }
 
-  if (!Preferences::GetBool("media.autoplay.enabled.user-gestures-needed",
-                            false)) {
-    // If element is blessed, it would always be allowed to play().
-    return aElement.IsBlessed() || EventStateManager::IsHandlingUserInput();
-  }
-  return IsWindowAllowedToPlay(aElement.OwnerDoc()->GetInnerWindow());
+  MOZ_ASSERT(defaultBehaviour == nsIAutoplay::BLOCKED_ALL);
+  return isUsingAutoplayModel;
 }
 
 /* static */
 bool AutoplayPolicy::IsAllowedToPlay(const HTMLMediaElement& aElement) {
   const bool result = IsAllowedToPlayInternal(aElement);
   AUTOPLAY_LOG("IsAllowedToPlay, mediaElement=%p, isAllowToPlay=%s", &aElement,
                result ? "allowed" : "blocked");
   return result;
@@ -230,24 +240,27 @@ bool AutoplayPolicy::IsAllowedToPlay(con
   if (aContext.IsOffline()) {
     return true;
   }
 
   nsPIDOMWindowInner* window = aContext.GetParentObject();
   Document* approver = aContext.GetParentObject()
                            ? ApproverDocOf(*(window->GetExtantDoc()))
                            : nullptr;
-  if (IsSiteInAutoplayWhiteList(approver)) {
+  uint32_t sitePermission = SiteAutoplayPerm(approver);
+
+  if (sitePermission == nsIPermissionManager::ALLOW_ACTION) {
     AUTOPLAY_LOG(
         "Allow autoplay as document has permanent autoplay permission.");
     return true;
   }
 
   if (DefaultAutoplayBehaviour() == nsIAutoplay::ALLOWED &&
-      !IsSiteInAutoplayBlackList(approver)) {
+      sitePermission != nsIPermissionManager::DENY_ACTION &&
+      sitePermission != nsIAutoplay::BLOCKED_ALL) {
     AUTOPLAY_LOG(
         "Allow autoplay as global autoplay setting is allowing autoplay by "
         "default.");
     return true;
   }
 
   if (!IsEnableBlockingWebAudioByUserGesturePolicy()) {
     return true;
@@ -258,17 +271,17 @@ bool AutoplayPolicy::IsAllowedToPlay(con
 /* static */
 DocumentAutoplayPolicy AutoplayPolicy::IsAllowedToPlay(
     const Document& aDocument) {
   if (DefaultAutoplayBehaviour() == nsIAutoplay::ALLOWED ||
       IsWindowAllowedToPlay(aDocument.GetInnerWindow())) {
     return DocumentAutoplayPolicy::Allowed;
   }
 
-  if (StaticPrefs::MediaAutoplayAllowMuted()) {
+  if (DefaultAutoplayBehaviour() == nsIAutoplay::BLOCKED) {
     return DocumentAutoplayPolicy::Allowed_muted;
   }
 
   return DocumentAutoplayPolicy::Disallowed;
 }
 
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/media/nsIAutoplay.idl
+++ b/dom/media/nsIAutoplay.idl
@@ -8,9 +8,10 @@
 [scriptable, uuid(048a24f6-c4d6-47bc-bea2-f6038d1db80a)]
 interface nsIAutoplay : nsISupports
 {
   /*
    * Possible values for the "media.autoplay.default" preference.
    */
   const uint32_t ALLOWED = 0;
   const uint32_t BLOCKED = 1;
+  const uint32_t BLOCKED_ALL = 5;
 };
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -4700,16 +4700,23 @@ VARCACHE_PREF(
 // Prefs starting with "media."
 //---------------------------------------------------------------------------
 
 // These prefs use camel case instead of snake case for the getter because one
 // reviewer had an unshakeable preference for that. Who could that be?
 
 VARCACHE_PREF(
   Live,
+  "media.autoplay.enabled.user-gestures-needed",
+  MediaAutoplayUserGesturesNeeded,
+  bool, false
+)
+
+VARCACHE_PREF(
+  Live,
   "media.autoplay.allow-muted",
   MediaAutoplayAllowMuted,
   RelaxedAtomicBool, true
 )
 
 VARCACHE_PREF(
   Live,
   "media.autoplay.blackList-override-default",
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -540,19 +540,16 @@ pref("media.recorder.video.frame_drops",
 
 // Whether to autostart a media element with an |autoplay| attribute.
 // ALLOWED=0, BLOCKED=1, defined in dom/media/Autoplay.idl
 pref("media.autoplay.default", 0);
 
 // By default, don't block WebAudio from playing automatically.
 pref("media.autoplay.block-webaudio", false);
 
-// By default, don't block muted media from playing automatically.
-pref("media.autoplay.allow-muted", true);
-
 // By default, don't block the media from extension background script.
 pref("media.autoplay.allow-extension-background-pages", true);
 
 // If "media.autoplay.default" is not ALLOWED, and this pref is true,
 // then audible media would only be allowed to autoplay after website has
 // been activated by specific user gestures, but non-audible
 // media won't be restricted.
 #ifdef NIGHTLY_BUILD
--- a/toolkit/content/tests/browser/browser_autoplay_policy.js
+++ b/toolkit/content/tests/browser/browser_autoplay_policy.js
@@ -1,23 +1,25 @@
 /**
  * This test is used to ensure we can get the correct document's autoplay policy
  * under different situations.
  * Spec discussion : https://github.com/WICG/autoplay/issues/1
  */
 const PAGE = "https://example.com/browser/toolkit/content/tests/browser/file_empty.html";
 
 function setupTestPreferences(isAllowedAutoplay, isAllowedMuted) {
-  const autoplayDefault = isAllowedAutoplay ?
-    SpecialPowers.Ci.nsIAutoplay.ALLOWED : SpecialPowers.Ci.nsIAutoplay.BLOCKED;
+  let autoplayDefault = SpecialPowers.Ci.nsIAutoplay.ALLOWED;
+  if (!isAllowedAutoplay) {
+    autoplayDefault = isAllowedMuted ? SpecialPowers.Ci.nsIAutoplay.BLOCKED
+      : SpecialPowers.Ci.nsIAutoplay.BLOCKED_ALL;
+  }
   return SpecialPowers.pushPrefEnv({"set": [
     ["dom.media.autoplay.autoplay-policy-api", true],
     ["media.autoplay.default", autoplayDefault],
     ["media.autoplay.enabled.user-gestures-needed", true],
-    ["media.autoplay.allow-muted", isAllowedMuted],
   ]});
 }
 
 async function checkAutoplayPolicy(expectedAutoplayPolicy) {
   is(content.document.autoplayPolicy, expectedAutoplayPolicy,
     `Autoplay policy is correct.`);
 }