Bug 1390161 - Show that a WebExtension is managing the tracking protection setting, r=jaws,mstriemer
authorBob Silverberg <bsilverberg@mozilla.com>
Mon, 06 Nov 2017 12:41:48 -0500
changeset 453247 85eb824006694370bcf6cb78aed7aeb11d8ab5d1
parent 453246 09f0935fcc43f257568dc231deb684b3419ecfbf
child 453248 179d25b3000055ef640f33339745a6c6669ab20a
push id1648
push usermtabara@mozilla.com
push dateThu, 01 Mar 2018 12:45:47 +0000
treeherdermozilla-release@cbb9688c2eeb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjaws, mstriemer
bugs1390161
milestone59.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 1390161 - Show that a WebExtension is managing the tracking protection setting, r=jaws,mstriemer This adjusts both the new Tracking Protection UI and the old Tracking Protection UI on about:preferences to indicate when an extension is controlling Tracking Protection. It will disable the controls on about:preferences if an extension is in control, and provide a button to disable the extension. MozReview-Commit-ID: G04jWrS6Pr9
browser/components/preferences/in-content/main.js
browser/components/preferences/in-content/preferences.js
browser/components/preferences/in-content/privacy.js
browser/components/preferences/in-content/privacy.xul
browser/components/preferences/in-content/tests/browser_extension_controlled.js
browser/locales/en-US/chrome/browser/preferences/preferences.properties
browser/locales/en-US/chrome/browser/preferences/privacy.dtd
--- a/browser/components/preferences/in-content/main.js
+++ b/browser/components/preferences/in-content/main.js
@@ -32,17 +32,16 @@ const PREF_DISABLED_PLUGIN_TYPES = "plug
 const PREF_CONTAINERS_EXTENSION = "privacy.userContext.extension";
 
 // Preferences that affect which entries to show in the list.
 const PREF_SHOW_PLUGINS_IN_LIST = "browser.download.show_plugins_in_list";
 const PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS =
   "browser.download.hide_plugins_without_extensions";
 
 // Strings to identify ExtensionSettingsStore overrides
-const PREF_SETTING_TYPE = "prefs";
 const CONTAINERS_KEY = "privacy.containers";
 const HOMEPAGE_OVERRIDE_KEY = "homepage_override";
 const URL_OVERRIDES_TYPE = "url_overrides";
 const NEW_TAB_KEY = "newTabURL";
 
 /*
  * Preferences where we store handling information about the feed type.
  *
--- a/browser/components/preferences/in-content/preferences.js
+++ b/browser/components/preferences/in-content/preferences.js
@@ -26,16 +26,19 @@ Cu.import("resource://gre/modules/AppCon
 
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionSettingsStore",
                                   "resource://gre/modules/ExtensionSettingsStore.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                   "resource://gre/modules/AddonManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "formAutofillParent",
                                   "resource://formautofill/FormAutofillParent.jsm");
 
+XPCOMUtils.defineLazyPreferenceGetter(this, "trackingprotectionUiEnabled",
+                                      "privacy.trackingprotection.ui.enabled");
+
 var gLastHash = "";
 
 var gCategoryInits = new Map();
 function init_category_if_required(category) {
   let categoryInfo = gCategoryInits.get(category);
   if (!categoryInfo) {
     throw "Unknown in-content prefs category! Can't init " + category;
   }
@@ -413,119 +416,147 @@ function appendSearchKeywords(aId, keywo
   let element = document.getElementById(aId);
   let searchKeywords = element.getAttribute("searchkeywords");
   if (searchKeywords) {
     keywords.push(searchKeywords);
   }
   element.setAttribute("searchkeywords", keywords.join(" "));
 }
 
+const PREF_SETTING_TYPE = "prefs";
+
 let extensionControlledContentIds = {
   "privacy.containers": "browserContainersExtensionContent",
   "homepage_override": "browserHomePageExtensionContent",
   "newTabURL": "browserNewTabExtensionContent",
   "defaultSearch": "browserDefaultSearchExtensionContent",
+  get "websites.trackingProtectionMode"() {
+    return {
+      button: "trackingProtectionExtensionContentButton",
+      section:
+        trackingprotectionUiEnabled ?
+          "trackingProtectionExtensionContentLabel" :
+          "trackingProtectionPBMExtensionContentLabel",
+    };
+  }
 };
 
 let extensionControlledIds = {};
 
 /**
   * Check if a pref is being managed by an extension.
   */
 async function getControllingExtensionInfo(type, settingName) {
   await ExtensionSettingsStore.initialize();
   return ExtensionSettingsStore.getSetting(type, settingName);
 }
 
-function getControllingExtensionEl(settingName) {
-  return document.getElementById(extensionControlledContentIds[settingName]);
+function getControllingExtensionEls(settingName) {
+  let idInfo = extensionControlledContentIds[settingName];
+  let section = document.getElementById(idInfo.section || idInfo);
+  let button = idInfo.button ?
+    document.getElementById(idInfo.button) :
+    section.querySelector("button");
+  return {
+    section,
+    button,
+    description: section.querySelector("description"),
+  };
 }
 
 async function handleControllingExtension(type, settingName) {
   let info = await getControllingExtensionInfo(type, settingName);
   let addon = info && info.id
     && await AddonManager.getAddonByID(info.id);
 
   // Sometimes the ExtensionSettingsStore gets in a bad state where it thinks
   // an extension is controlling a setting but the extension has been uninstalled
   // outside of the regular lifecycle. If the extension isn't currently installed
   // then we should treat the setting as not being controlled.
   // See https://bugzilla.mozilla.org/show_bug.cgi?id=1411046 for an example.
   if (addon) {
     extensionControlledIds[settingName] = info.id;
     showControllingExtension(settingName, addon);
   } else {
-    if (extensionControlledIds[settingName] && !document.hidden) {
+    let elements = getControllingExtensionEls(settingName);
+    if (extensionControlledIds[settingName]
+        && !document.hidden
+        && elements.button) {
       showEnableExtensionMessage(settingName);
     } else {
       hideControllingExtension(settingName);
     }
     delete extensionControlledIds[settingName];
   }
 
   return !!addon;
 }
 
 async function showControllingExtension(settingName, addon) {
   // Tell the user what extension is controlling the setting.
-  let extensionControlledContent = getControllingExtensionEl(settingName);
-  extensionControlledContent.classList.remove("extension-controlled-disabled");
+  let elements = getControllingExtensionEls(settingName);
+
+  elements.section.classList.remove("extension-controlled-disabled");
   const defaultIcon = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
   let stringParts = document
     .getElementById("bundlePreferences")
     .getString(`extensionControlled.${settingName}`)
     .split("%S");
-  let description = extensionControlledContent.querySelector("description");
+  let description = elements.description;
 
   // Remove the old content from the description.
   while (description.firstChild) {
     description.firstChild.remove();
   }
 
   // Populate the description.
   description.appendChild(document.createTextNode(stringParts[0]));
   let image = document.createElement("image");
   image.setAttribute("src", addon.iconURL || defaultIcon);
   image.classList.add("extension-controlled-icon");
   description.appendChild(image);
   description.appendChild(document.createTextNode(` ${addon.name}`));
   description.appendChild(document.createTextNode(stringParts[1]));
 
-  let disableButton = extensionControlledContent.querySelector("button");
-  if (disableButton) {
-    disableButton.hidden = false;
+  if (elements.button) {
+    elements.button.hidden = false;
   }
 
   // Show the controlling extension row and hide the old label.
-  extensionControlledContent.hidden = false;
+  elements.section.hidden = false;
 }
 
 function hideControllingExtension(settingName) {
-  getControllingExtensionEl(settingName).hidden = true;
+  let elements = getControllingExtensionEls(settingName);
+  elements.section.hidden = true;
+  if (elements.button) {
+    elements.button.hidden = true;
+  }
 }
 
 function showEnableExtensionMessage(settingName) {
-  let extensionControlledContent = getControllingExtensionEl(settingName);
-  extensionControlledContent.classList.add("extension-controlled-disabled");
+  let elements = getControllingExtensionEls(settingName);
+
+  elements.button.hidden = true;
+  elements.section.classList.add("extension-controlled-disabled");
   let icon = url => `<image src="${url}" class="extension-controlled-icon"/>`;
   let addonIcon = icon("chrome://mozapps/skin/extensions/extensionGeneric-16.svg");
   let toolbarIcon = icon("chrome://browser/skin/menu.svg");
   let message = document
     .getElementById("bundlePreferences")
     .getFormattedString("extensionControlled.enable", [addonIcon, toolbarIcon]);
-  let description = extensionControlledContent.querySelector("description");
   // eslint-disable-next-line no-unsanitized/property
-  description.innerHTML = message;
+  elements.description.innerHTML = message;
   let dismissButton = document.createElement("image");
   dismissButton.setAttribute("class", "extension-controlled-icon close-icon");
   dismissButton.addEventListener("click", function dismissHandler() {
     hideControllingExtension(settingName);
     dismissButton.removeEventListener("click", dismissHandler);
   });
-  description.appendChild(dismissButton);
+  elements.description.appendChild(dismissButton);
 }
 
 function makeDisableControllingExtension(type, settingName) {
   return async function disableExtension() {
     let {id} = await getControllingExtensionInfo(type, settingName);
     let addon = await AddonManager.getAddonByID(id);
     addon.userDisabled = true;
   };
--- a/browser/components/preferences/in-content/privacy.js
+++ b/browser/components/preferences/in-content/privacy.js
@@ -12,20 +12,27 @@ Components.utils.import("resource://gre/
 
 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
   "resource://gre/modules/PluralForm.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
   "resource://gre/modules/LoginHelper.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SiteDataManager",
   "resource:///modules/SiteDataManager.jsm");
 
+XPCOMUtils.defineLazyPreferenceGetter(this, "trackingprotectionUiEnabled",
+                                      "privacy.trackingprotection.ui.enabled");
+
 Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 const PREF_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";
 
+const TRACKING_PROTECTION_KEY = "websites.trackingProtectionMode";
+const TRACKING_PROTECTION_PREFS = ["privacy.trackingprotection.enabled",
+                                   "privacy.trackingprotection.pbmode.enabled"];
+
 XPCOMUtils.defineLazyGetter(this, "AlertsServiceDND", function() {
   try {
     let alertsService = Cc["@mozilla.org/alerts-service;1"]
       .getService(Ci.nsIAlertsService)
       .QueryInterface(Ci.nsIAlertsDoNotDisturb);
     // This will throw if manualDoNotDisturb isn't implemented.
     alertsService.manualDoNotDisturb;
     return alertsService;
@@ -128,17 +135,17 @@ var gPrivacyPane = {
    */
   _shouldPromptForRestart: true,
 
   /**
    * Show the Tracking Protection UI depending on the
    * privacy.trackingprotection.ui.enabled pref, and linkify its Learn More link
    */
   _initTrackingProtection() {
-    if (!Services.prefs.getBoolPref("privacy.trackingprotection.ui.enabled")) {
+    if (!trackingprotectionUiEnabled) {
       return;
     }
 
     let link = document.getElementById("trackingProtectionLearnMore");
     let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "tracking-protection";
     link.setAttribute("href", url);
 
     this.trackingProtectionReadPrefs();
@@ -148,19 +155,81 @@ var gPrivacyPane = {
     document.getElementById("trackingProtectionPBMBox").hidden = true;
   },
 
   /**
    * Linkify the Learn More link of the Private Browsing Mode Tracking
    * Protection UI.
    */
   _initTrackingProtectionPBM() {
-    let link = document.getElementById("trackingProtectionPBMLearnMore");
+    if (trackingprotectionUiEnabled) {
+      return;
+    }
+
+    let link = document.getElementById("trackingProtectionLearnMore");
     let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "tracking-protection-pbm";
     link.setAttribute("href", url);
+
+    this._updateTrackingProtectionUI();
+  },
+
+  /**
+   * Update the tracking protection UI to deal with extension control.
+   */
+  _updateTrackingProtectionUI() {
+    let isLocked = TRACKING_PROTECTION_PREFS.some(
+      pref => Services.prefs.prefIsLocked(pref));
+
+    function setInputsDisabledState(isControlled) {
+      let disabled = isLocked || isControlled;
+      if (trackingprotectionUiEnabled) {
+        document.querySelectorAll("#trackingProtectionRadioGroup > radio")
+          .forEach((element) => {
+            element.disabled = disabled;
+          });
+        document.querySelector("#trackingProtectionDesc > label")
+          .disabled = disabled;
+      } else {
+        document.getElementById("trackingProtectionPBM").disabled = disabled;
+        document.getElementById("trackingProtectionPBMLabel")
+          .disabled = disabled;
+      }
+    }
+
+    if (isLocked) {
+      // An extension can't control this setting if either pref is locked.
+      hideControllingExtension(TRACKING_PROTECTION_KEY);
+      setInputsDisabledState(false);
+    } else {
+      handleControllingExtension(
+        PREF_SETTING_TYPE,
+        TRACKING_PROTECTION_KEY)
+          .then(setInputsDisabledState);
+    }
+  },
+
+  /**
+   * Set up handlers for showing and hiding controlling extension info
+   * for tracking protection.
+   */
+  _initTrackingProtectionExtensionControl() {
+    let trackingProtectionObserver = {
+      observe(subject, topic, data) {
+        gPrivacyPane._updateTrackingProtectionUI();
+      },
+    };
+
+    for (let pref of TRACKING_PROTECTION_PREFS) {
+      Services.prefs.addObserver(pref, trackingProtectionObserver);
+    }
+    window.addEventListener("unload", () => {
+      for (let pref of TRACKING_PROTECTION_PREFS) {
+        Services.prefs.removeObserver(pref, trackingProtectionObserver);
+      }
+    });
   },
 
   /**
    * Initialize autocomplete to ensure prefs are in sync.
    */
   _initAutocomplete() {
     Components.classes["@mozilla.org/autocomplete/search;1?name=unifiedcomplete"]
       .getService(Components.interfaces.mozIPlacesAutoComplete);
@@ -178,16 +247,17 @@ var gPrivacyPane = {
 
     this._updateSanitizeSettingsButton();
     this.initializeHistoryMode();
     this.updateHistoryModePane();
     this.updatePrivacyMicroControls();
     this.initAutoStartPrivateBrowsingReverter();
     this._initTrackingProtection();
     this._initTrackingProtectionPBM();
+    this._initTrackingProtectionExtensionControl();
     this._initAutocomplete();
 
     Preferences.get("privacy.sanitize.sanitizeOnShutdown").on("change",
       gPrivacyPane._updateSanitizeSettingsButton.bind(gPrivacyPane));
     Preferences.get("browser.privatebrowsing.autostart").on("change",
       gPrivacyPane.updatePrivacyMicroControls.bind(gPrivacyPane));
     setEventListener("historyMode", "command", function() {
       gPrivacyPane.updateHistoryModePane();
@@ -222,16 +292,19 @@ var gPrivacyPane = {
     setEventListener("privateBrowsingAutoStart", "command",
       gPrivacyPane.updateAutostart);
     setEventListener("cookieExceptions", "command",
       gPrivacyPane.showCookieExceptions);
     setEventListener("showCookiesButton", "command",
       gPrivacyPane.showCookies);
     setEventListener("clearDataSettings", "command",
       gPrivacyPane.showClearPrivateDataSettings);
+    setEventListener("disableTrackingProtectionExtension", "command",
+      makeDisableControllingExtension(
+        PREF_SETTING_TYPE, TRACKING_PROTECTION_KEY));
     setEventListener("trackingProtectionRadioGroup", "command",
       gPrivacyPane.trackingProtectionWritePrefs);
     setEventListener("trackingProtectionExceptions", "command",
       gPrivacyPane.showTrackingProtectionExceptions);
     setEventListener("changeBlockList", "command",
       gPrivacyPane.showBlockLists);
     setEventListener("passwordExceptions", "command",
       gPrivacyPane.showPasswordExceptions);
@@ -414,16 +487,18 @@ var gPrivacyPane = {
   /**
    * Selects the right item of the Tracking Protection radiogroup.
    */
   trackingProtectionReadPrefs() {
     let enabledPref = Preferences.get("privacy.trackingprotection.enabled");
     let pbmPref = Preferences.get("privacy.trackingprotection.pbmode.enabled");
     let radiogroup = document.getElementById("trackingProtectionRadioGroup");
 
+    this._updateTrackingProtectionUI();
+
     // Global enable takes precedence over enabled in Private Browsing.
     if (enabledPref.value) {
       radiogroup.value = "always";
     } else if (pbmPref.value) {
       radiogroup.value = "private";
     } else {
       radiogroup.value = "never";
     }
--- a/browser/components/preferences/in-content/privacy.xul
+++ b/browser/components/preferences/in-content/privacy.xul
@@ -318,51 +318,65 @@
 
 <!-- Tracking -->
 <groupbox id="trackingGroup" data-category="panePrivacy" hidden="true">
   <caption><label>&trackingProtectionHeader2.label;</label></caption>
   <vbox>
     <hbox align="start">
       <vbox flex="1">
         <description>
-          &trackingProtection2.description;
+          &trackingProtection3.description;
+          <label id="trackingProtectionLearnMore" class="learnMore text-link">&trackingProtectionLearnMore2.label;</label>
         </description>
       </vbox>
       <spacer flex="1"/>
     </hbox>
     <hbox>
       <vbox id="trackingProtectionBox" flex="1" hidden="true">
-        <description id="trackingProtectionDesc"
-                     control="trackingProtectionRadioGroup">
-           <label class="tail-with-learn-more">&trackingProtection2.radioGroupLabel;</label>
-           <label id="trackingProtectionLearnMore" class="learnMore text-link">&trackingProtectionLearnMore.label;</label>
-        </description>
-        <radiogroup id="trackingProtectionRadioGroup" aria-labelledby="trackingProtectionDesc">
-          <radio value="always"
-                 label="&trackingProtectionAlways.label;"
-                 accesskey="&trackingProtectionAlways.accesskey;"/>
-          <radio value="private"
-                 label="&trackingProtectionPrivate.label;"
-                 accesskey="&trackingProtectionPrivate.accesskey;"/>
-          <radio value="never"
-                 label="&trackingProtectionNever.label;"
-                 accesskey="&trackingProtectionNever.accesskey;"/>
-        </radiogroup>
+        <vbox>
+          <hbox id="trackingProtectionExtensionContentLabel" align="center" hidden="true">
+            <description control="disableTrackingProtectionExtension" flex="1"/>
+          </hbox>
+          <vbox>
+            <description id="trackingProtectionDesc"
+                         control="trackingProtectionRadioGroup">
+               <label>&trackingProtection3.radioGroupLabel;</label>
+            </description>
+            <radiogroup id="trackingProtectionRadioGroup" aria-labelledby="trackingProtectionDesc">
+              <radio value="always"
+                     label="&trackingProtectionAlways.label;"
+                     accesskey="&trackingProtectionAlways.accesskey;"/>
+              <radio value="private"
+                     label="&trackingProtectionPrivate.label;"
+                     accesskey="&trackingProtectionPrivate.accesskey;"/>
+              <radio value="never"
+                     label="&trackingProtectionNever.label;"
+                     accesskey="&trackingProtectionNever.accesskey;"/>
+            </radiogroup>
+          </vbox>
+        </vbox>
       </vbox>
       <vbox id="trackingProtectionPBMBox" flex="1">
-        <hbox align="center">
+        <hbox id="trackingProtectionPBMExtensionContentLabel" align="center" hidden="true">
+          <description control="disableTrackingProtectionExtension" flex="1"/>
+        </hbox>
+        <hbox align="start">
           <checkbox id="trackingProtectionPBM"
                     preference="privacy.trackingprotection.pbmode.enabled"
                     accesskey="&trackingProtectionPBM6.accesskey;"/>
-          <label flex="1">&trackingProtectionPBM6.label;<spacer class="tail-with-learn-more" /><label id="trackingProtectionPBMLearnMore"
-                 class="learnMore text-link">&trackingProtectionPBMLearnMore.label;</label>
-          </label>
+          <label id="trackingProtectionPBMLabel" flex="1">&trackingProtectionPBM6.label;</label>
         </hbox>
       </vbox>
       <vbox id="trackingProtectionAdvancedSettings">
+        <hbox id="trackingProtectionExtensionContentButton" hidden="true">
+          <button id="disableTrackingProtectionExtension"
+                  class="extension-controlled-button accessory-button"
+                  flex="1"
+                  label="&disableExtension.label;"/>
+        </hbox>
         <!-- 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="trackingProtectionExceptions"
                   class="accessory-button"
                   flex="1"
                   hidden="true"
                   label="&trackingProtectionExceptions.label;"
                   accesskey="&trackingProtectionExceptions.accesskey;"
--- a/browser/components/preferences/in-content/tests/browser_extension_controlled.js
+++ b/browser/components/preferences/in-content/tests/browser_extension_controlled.js
@@ -1,8 +1,10 @@
+/* eslint-env webextensions */
+
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionSettingsStore",
                                   "resource://gre/modules/ExtensionSettingsStore.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
                                    "@mozilla.org/browser/aboutnewtab-service;1",
                                    "nsIAboutNewTabService");
 
 const TEST_DIR = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
 const CHROME_URL_ROOT = TEST_DIR + "/";
@@ -462,8 +464,143 @@ add_task(async function testExtensionCon
 
   // Reload the ExtensionSettingsStore again so it clears the data we added.
   // Don't finalize the current store since it will write out the bad data.
   await ExtensionSettingsStore._reloadFile(false);
 
   is(ExtensionSettingsStore.getSetting("prefs", "homepage_override"), null,
      "The ExtensionSettingsStore is left empty.");
 });
+
+add_task(async function testExtensionControlledTrackingProtection() {
+  const TP_UI_PREF = "privacy.trackingprotection.ui.enabled";
+  const TP_PREF = "privacy.trackingprotection.enabled";
+  const TP_DEFAULT = false;
+  const EXTENSION_ID = "@set_tp";
+  const CONTROLLED_LABEL_ID = {
+    new: "trackingProtectionExtensionContentLabel",
+    old: "trackingProtectionPBMExtensionContentLabel"
+  };
+  const CONTROLLED_BUTTON_ID = "trackingProtectionExtensionContentButton";
+
+  let tpEnabledPref = () => Services.prefs.getBoolPref(TP_PREF);
+
+  await SpecialPowers.pushPrefEnv(
+    {"set": [[TP_PREF, TP_DEFAULT], [TP_UI_PREF, true]]});
+
+  function background() {
+    browser.privacy.websites.trackingProtectionMode.set({value: "always"});
+  }
+
+  function verifyState(isControlled) {
+    is(tpEnabledPref(), isControlled, "TP pref is set to the expected value.");
+
+    let controlledLabel = doc.getElementById(CONTROLLED_LABEL_ID[uiType]);
+
+    is(controlledLabel.hidden, !isControlled, "The extension controlled row's visibility is as expected.");
+    is(controlledButton.hidden, !isControlled, "The disable extension button's visibility is as expected.");
+    if (isControlled) {
+      let controlledDesc = controlledLabel.querySelector("description");
+      // There are two spaces before "set_tp" because it's " <image /> set_tp".
+      is(controlledDesc.textContent, "An extension,  set_tp, is controlling tracking protection.",
+         "The user is notified that an extension is controlling TP.");
+    }
+
+    if (uiType === "new") {
+      for (let element of doc.querySelectorAll("#trackingProtectionRadioGroup > radio")) {
+        is(element.disabled, isControlled, "TP controls are enabled.");
+      }
+      is(doc.querySelector("#trackingProtectionDesc > label").disabled,
+         isControlled,
+         "TP control label is enabled.");
+    } else {
+      is(doc.getElementById("trackingProtectionPBM").disabled,
+         isControlled,
+         "TP control is enabled.");
+      is(doc.getElementById("trackingProtectionPBMLabel").disabled,
+         isControlled,
+         "TP control label is enabled.");
+    }
+  }
+
+  async function disableViaClick() {
+    let labelId = CONTROLLED_LABEL_ID[uiType];
+    let controlledLabel = doc.getElementById(labelId);
+
+    let enableMessageShown = waitForEnableMessage(labelId);
+    doc.getElementById("disableTrackingProtectionExtension").click();
+    await enableMessageShown;
+
+    // The user is notified how to enable the extension.
+    let controlledDesc = controlledLabel.querySelector("description");
+    is(controlledDesc.textContent, "To enable the extension go to  Add-ons in the  menu.",
+       "The user is notified of how to enable the extension again");
+
+    // The user can dismiss the enable instructions.
+    let hidden = waitForMessageHidden(labelId);
+    controlledLabel.querySelector("image:last-of-type").click();
+    await hidden;
+  }
+
+  async function reEnableExtension(addon) {
+    let controlledMessageShown = waitForMessageShown(CONTROLLED_LABEL_ID[uiType]);
+    addon.userDisabled = false;
+    await controlledMessageShown;
+  }
+
+  let uiType = "new";
+
+  await openPreferencesViaOpenPreferencesAPI("panePrivacy", {leaveOpen: true});
+  // eslint-disable-next-line mozilla/no-cpows-in-tests
+  let doc = gBrowser.contentDocument;
+
+  is(gBrowser.currentURI.spec, "about:preferences#privacy",
+   "#privacy should be in the URI for about:preferences");
+
+  let controlledButton = doc.getElementById(CONTROLLED_BUTTON_ID);
+
+  verifyState(false);
+
+  // Install an extension that sets Tracking Protection.
+  let extension = ExtensionTestUtils.loadExtension({
+    useAddonManager: "permanent",
+    manifest: {
+      name: "set_tp",
+      applications: {gecko: {id: EXTENSION_ID}},
+      permissions: ["privacy"],
+    },
+    background,
+  });
+
+  let messageShown = waitForMessageShown(CONTROLLED_LABEL_ID[uiType]);
+  await extension.startup();
+  await messageShown;
+  let addon = await AddonManager.getAddonByID(EXTENSION_ID);
+
+  verifyState(true);
+
+  await disableViaClick();
+
+  verifyState(false);
+
+  // Switch to the "old" Tracking Protection UI.
+  uiType = "old";
+  Services.prefs.setBoolPref(TP_UI_PREF, false);
+
+  verifyState(false);
+
+  await reEnableExtension(addon);
+
+  verifyState(true);
+
+  await disableViaClick();
+
+  verifyState(false);
+
+  // Enable the extension so we get the UNINSTALL event, which is needed by
+  // ExtensionPreferencesManager to clean up properly.
+  // TODO: BUG 1408226
+  await reEnableExtension(addon);
+
+  await extension.unload();
+
+  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
--- a/browser/locales/en-US/chrome/browser/preferences/preferences.properties
+++ b/browser/locales/en-US/chrome/browser/preferences/preferences.properties
@@ -287,14 +287,18 @@ extensionControlled.newTabURL = An exten
 # by an extension. %S is the icon and name of the extension.
 extensionControlled.defaultSearch = An extension, %S, has set your default search engine.
 
 # LOCALIZATION NOTE (extensionControlled.privacy.containers):
 # This string is shown to notify the user that Container Tabs are being enabled by an extension
 # %S is the container addon controlling it
 extensionControlled.privacy.containers = An extension, %S, requires Container Tabs.
 
+# LOCALIZATION NOTE (extensionControlled.websites.trackingProtectionMode):
+# This string is shown to notify the user that their tracking protection preferences are being controlled by an extension.
+extensionControlled.websites.trackingProtectionMode = An extension, %S, is controlling tracking protection.
+
 # LOCALIZATION NOTE (extensionControlled.enable):
 # %1$S is replaced with the icon for the add-ons menu.
 # %2$S is replaced with the icon for the toolbar menu.
 # This string is shown to notify the user how to enable an extension that they disabled.
 # Note, this string will be used as raw markup. Avoid characters like <, >, &
 extensionControlled.enable = To enable the extension go to %1$S Add-ons in the %2$S menu.
--- a/browser/locales/en-US/chrome/browser/preferences/privacy.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/privacy.dtd
@@ -1,22 +1,22 @@
 <!-- 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/. -->
 
 <!ENTITY  trackingProtectionHeader2.label      "Tracking Protection">
-<!ENTITY  trackingProtection2.description      "Tracking is the collection of your browsing data across multiple websites. Tracking can be used to build a profile and display content based on your browsing and personal information.">
-<!ENTITY  trackingProtection2.radioGroupLabel  "Use Tracking Protection to block known trackers">
+<!ENTITY  trackingProtection3.description      "Tracking Protection blocks online trackers that collect your browsing data across multiple websites.">
+<!ENTITY  trackingProtection3.radioGroupLabel  "Use Tracking Protection to block known trackers">
 <!ENTITY  trackingProtectionAlways.label       "Always">
 <!ENTITY  trackingProtectionAlways.accesskey   "y">
 <!ENTITY  trackingProtectionPrivate.label      "Only in private windows">
 <!ENTITY  trackingProtectionPrivate.accesskey  "l">
 <!ENTITY  trackingProtectionNever.label        "Never">
 <!ENTITY  trackingProtectionNever.accesskey    "n">
-<!ENTITY  trackingProtectionLearnMore.label    "Learn more">
+<!ENTITY  trackingProtectionLearnMore2.label    "Learn more about Tracking Protection and your privacy">
 <!ENTITY  trackingProtectionExceptions.label   "Exceptions…">
 <!ENTITY  trackingProtectionExceptions.accesskey "x">
 
 <!-- LOCALIZATION NOTE (trackingProtectionPBM6.label): This string is displayed if privacy.trackingprotection.ui.enabled is set to false. This currently happens on the release and beta channel. -->
 <!ENTITY trackingProtectionPBM6.label         "Use Tracking Protection in Private Browsing to block known trackers">
 <!ENTITY trackingProtectionPBM6.accesskey     "v">
 <!ENTITY trackingProtectionPBMLearnMore.label "Learn more">
 <!ENTITY changeBlockList2.label               "Change Block List…">