Bug 1438363 - Show a doorhanger when an extension first hides a tab r?aswan,dao draft
authorMark Striemer <mstriemer@mozilla.com>
Tue, 24 Apr 2018 09:07:32 -0500
changeset 790832 de5991b0dff4f547ac871d7f153c5c104615d291
parent 790727 1781bcae61d083aa76dc754b7926329e65cf3213
push id108611
push userbmo:mstriemer@mozilla.com
push dateWed, 02 May 2018 22:34:04 +0000
reviewersaswan, dao
bugs1438363
milestone61.0a1
Bug 1438363 - Show a doorhanger when an extension first hides a tab r?aswan,dao MozReview-Commit-ID: DQCr3SSaZTV
browser/components/customizableui/content/panelUI.inc.xul
browser/components/extensions/ExtensionControlledPopup.jsm
browser/components/extensions/parent/ext-tabs.js
browser/components/extensions/test/browser/browser_ext_tabs_hide.js
browser/locales/en-US/chrome/browser/browser.dtd
browser/themes/shared/addons/extension-controlled.inc.css
browser/themes/shared/browser.inc.css
browser/themes/shared/customizableui/panelUI.inc.css
toolkit/locales/en-US/chrome/global/extensions.properties
toolkit/themes/shared/icons/arrow-dropdown-16.svg
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -728,9 +728,25 @@
                      secondarybuttonaccesskey="&homepageControlled.disableButton.accesskey;"
                      closebuttonhidden="true"
                      dropmarkerhidden="true"
                      checkboxhidden="true">
     <popupnotificationcontent orient="vertical">
       <description id="extension-homepage-notification-description"/>
     </popupnotificationcontent>
   </popupnotification>
+  <popupnotification id="extension-tab-hide-notification"
+                     class="extension-controlled-notification"
+                     popupid="extension-tab-hide"
+                     hidden="true"
+                     label="&tabHideControlled.header.message;"
+                     buttonlabel="&tabHideControlled.keepButton.label;"
+                     buttonaccesskey="&tabHideControlled.keepButton.accesskey;"
+                     secondarybuttonlabel="&tabHideControlled.disableButton.label;"
+                     secondarybuttonaccesskey="&tabHideControlled.disableButton.accesskey;"
+                     closebuttonhidden="true"
+                     dropmarkerhidden="true"
+                     checkboxhidden="true">
+    <popupnotificationcontent orient="vertical">
+      <description id="extension-tab-hide-notification-description"/>
+    </popupnotificationcontent>
+  </popupnotification>
 </panel>
--- a/browser/components/extensions/ExtensionControlledPopup.jsm
+++ b/browser/components/extensions/ExtensionControlledPopup.jsm
@@ -44,30 +44,38 @@ class ExtensionControlledPopup {
    * @param {object} opts Options for configuring popup.
    * @param {string} opts.confirmedType
    *                 The type to use for storing a user's confirmation in
    *                 ExtensionSettingsStore.
    * @param {string} opts.observerTopic
    *                 An observer topic to trigger the popup on with Services.obs. If the
    *                 doorhanger should appear on a specific window include it as the
    *                 subject in the observer event.
+   * @param {string} opts.anchorId
+   *                 The id to anchor the popupnotification on. If it is not provided
+   *                 then it will anchor to a browser action or the app menu.
    * @param {string} opts.popupnotificationId
    *                 The id for the popupnotification element in the markup. This
    *                 element should be defined in panelUI.inc.xul.
    * @param {string} opts.settingType
    *                 The setting type to check in ExtensionSettingsStore to retrieve
    *                 the controlling extension.
    * @param {string} opts.settingKey
    *                 The setting key to check in ExtensionSettingsStore to retrieve
    *                 the controlling extension.
    * @param {string} opts.descriptionId
    *                 The id of the element where the description should be displayed.
    * @param {string} opts.descriptionMessageId
    *                 The message id to be used for the description. The translated
    *                 string will have the add-on's name and icon injected into it.
+   * @param {string} opts.getLocalizedDescription
+   *                 A function to get the localized message string. This
+   *                 function is passed doc, message and addonDetails (the
+   *                 add-on's icon and name). If not provided, then the add-on's
+   *                 icon and name are added to the description.
    * @param {string} opts.learnMoreMessageId
    *                 The message id to be used for the text of a "learn more" link which
    *                 will be placed after the description.
    * @param {string} opts.learnMoreLink
    *                 The name of the SUMO page to link to, this is added to
    *                 app.support.baseURL.
    * @param {function} opts.onObserverAdded
    *                   A callback that is triggered when an observer is registered to
@@ -81,21 +89,23 @@ class ExtensionControlledPopup {
    *                   user decides to disable the extension. If this function is async
    *                   then the extension won't be disabled until it is fulfilled.
    *                   This function gets two arguments, the ExtensionControlledPopup
    *                   instance for the panel and the window that the popup appears on.
    */
   constructor(opts) {
     this.confirmedType = opts.confirmedType;
     this.observerTopic = opts.observerTopic;
+    this.anchorId = opts.anchorId;
     this.popupnotificationId = opts.popupnotificationId;
     this.settingType = opts.settingType;
     this.settingKey = opts.settingKey;
     this.descriptionId = opts.descriptionId;
     this.descriptionMessageId = opts.descriptionMessageId;
+    this.getLocalizedDescription = opts.getLocalizedDescription;
     this.learnMoreMessageId = opts.learnMoreMessageId;
     this.learnMoreLink = opts.learnMoreLink;
     this.onObserverAdded = opts.onObserverAdded;
     this.onObserverRemoved = opts.onObserverRemoved;
     this.beforeDisableAddon = opts.beforeDisableAddon;
     this.observerRegistered = false;
   }
 
@@ -145,58 +155,65 @@ class ExtensionControlledPopup {
       Services.obs.addObserver(this, this.observerTopic);
       this.observerRegistered = true;
       if (this.onObserverAdded) {
         this.onObserverAdded();
       }
     }
   }
 
-  async open(targetWindow) {
+  // The extensionId will be looked up in ExtensionSettingsStore if it is not
+  // provided using this.settingType and this.settingKey.
+  async open(targetWindow, extensionId) {
     await ExtensionSettingsStore.initialize();
 
     // Remove the observer since it would open the same dialog again the next time
     // the observer event fires.
     this.removeObserver();
 
-    let item = ExtensionSettingsStore.getSetting(
-      this.settingType, this.settingKey);
+    if (!extensionId) {
+      let item = ExtensionSettingsStore.getSetting(
+        this.settingType, this.settingKey);
+      extensionId = item && item.id;
+    }
 
     // The item should have an extension and the user shouldn't have confirmed
     // the change here, but just to be sure check that it is still controlled
     // and the user hasn't already confirmed the change.
     // If there is no id, then the extension is no longer in control.
-    if (!item || !item.id || this.userHasConfirmed(item.id)) {
+    if (!extensionId || this.userHasConfirmed(extensionId)) {
       return;
     }
 
     // Find the elements we need.
     let win = targetWindow || this.topWindow;
     let doc = win.document;
     let panel = doc.getElementById("extension-notification-panel");
     let popupnotification = doc.getElementById(this.popupnotificationId);
     let urlBarWasFocused = win.gURLBar.focused;
 
     if (!popupnotification) {
       throw new Error(`No popupnotification found for id "${this.popupnotificationId}"`);
     }
 
-    let addon = await AddonManager.getAddonByID(item.id);
+    let addon = await AddonManager.getAddonByID(extensionId);
     this.populateDescription(doc, addon);
 
     // Setup the command handler.
     let handleCommand = async (event) => {
       panel.hidePopup();
 
       if (event.originalTarget.getAttribute("anonid") == "button") {
         // Main action is to keep changes.
-        await this.setConfirmation(item.id);
+        await this.setConfirmation(extensionId);
       } else {
         // Secondary action is to restore settings.
-        await this.beforeDisableAddon(this, win);
+        if (this.beforeDisableAddon) {
+          await this.beforeDisableAddon(this, win);
+        }
         addon.userDisabled = true;
       }
 
       // If the page this is appearing on is the New Tab page then the URL bar may
       // have been focused when the doorhanger stole focus away from it. Once an
       // action is taken the focus state should be restored to what the user was
       // expecting.
       if (urlBarWasFocused) {
@@ -204,27 +221,33 @@ class ExtensionControlledPopup {
       }
     };
     panel.addEventListener("command", handleCommand);
     panel.addEventListener("popuphidden", () => {
       popupnotification.hidden = true;
       panel.removeEventListener("command", handleCommand);
     }, {once: true});
 
-    // Look for a browserAction on the toolbar.
-    let action = CustomizableUI.getWidget(
-      `${makeWidgetId(item.id)}-browser-action`);
-    if (action) {
-      action = action.areaType == "toolbar" && action.forWindow(win).node;
+    let anchorButton;
+    if (this.anchorId) {
+      // If there's an anchorId, use that right away.
+      anchorButton = doc.getElementById(this.anchorId);
+    } else {
+      // Look for a browserAction on the toolbar.
+      let action = CustomizableUI.getWidget(
+        `${makeWidgetId(extensionId)}-browser-action`);
+      if (action) {
+        action = action.areaType == "toolbar" && action.forWindow(win).node;
+      }
+
+      // Anchor to a toolbar browserAction if found, otherwise use the menu button.
+      anchorButton = action || doc.getElementById("PanelUI-menu-button");
     }
-
-    // Anchor to a toolbar browserAction if found, otherwise use the menu button.
     let anchor = doc.getAnonymousElementByAttribute(
-      action || doc.getElementById("PanelUI-menu-button"),
-      "class", "toolbarbutton-icon");
+      anchorButton, "class", "toolbarbutton-icon");
     panel.hidden = false;
     popupnotification.hidden = false;
     panel.openPopup(anchor);
   }
 
   getAddonDetails(doc, addon) {
     const defaultIcon = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
 
@@ -240,18 +263,23 @@ class ExtensionControlledPopup {
   }
 
   populateDescription(doc, addon) {
     let description = doc.getElementById(this.descriptionId);
     description.textContent = "";
 
     let addonDetails = this.getAddonDetails(doc, addon);
     let message = strBundle.GetStringFromName(this.descriptionMessageId);
-    description.appendChild(
-      BrowserUtils.getLocalizedFragment(doc, message, addonDetails));
+    if (this.getLocalizedDescription) {
+      description.appendChild(
+        this.getLocalizedDescription(doc, message, addonDetails));
+    } else {
+      description.appendChild(
+        BrowserUtils.getLocalizedFragment(doc, message, addonDetails));
+    }
 
     let link = doc.createElement("label");
     link.setAttribute("class", "learnMore text-link");
     link.href = Services.urlFormatter.formatURLPref("app.support.baseURL") + this.learnMoreLink;
     link.textContent = strBundle.GetStringFromName(this.learnMoreMessageId);
     description.appendChild(link);
   }
 }
--- a/browser/components/extensions/parent/ext-tabs.js
+++ b/browser/components/extensions/parent/ext-tabs.js
@@ -1,12 +1,16 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+ChromeUtils.defineModuleGetter(this, "BrowserUtils",
+                               "resource://gre/modules/BrowserUtils.jsm");
+ChromeUtils.defineModuleGetter(this, "ExtensionControlledPopup",
+                               "resource:///modules/ExtensionControlledPopup.jsm");
 ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
                                "resource://gre/modules/PrivateBrowsingUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "PromiseUtils",
                                "resource://gre/modules/PromiseUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "Services",
                                "resource://gre/modules/Services.jsm");
 ChromeUtils.defineModuleGetter(this, "SessionStore",
                                "resource:///modules/sessionstore/SessionStore.jsm");
@@ -16,16 +20,36 @@ XPCOMUtils.defineLazyGetter(this, "strBu
 });
 
 var {
   ExtensionError,
 } = ExtensionUtils;
 
 const TABHIDE_PREFNAME = "extensions.webextensions.tabhide.enabled";
 
+const TAB_HIDE_CONFIRMED_TYPE = "tabHideNotification";
+
+
+XPCOMUtils.defineLazyGetter(this, "tabHidePopup", () => {
+  return new ExtensionControlledPopup({
+    confirmedType: TAB_HIDE_CONFIRMED_TYPE,
+    anchorId: "alltabs-button",
+    popupnotificationId: "extension-tab-hide-notification",
+    descriptionId: "extension-tab-hide-notification-description",
+    descriptionMessageId: "tabHideControlled.message",
+    getLocalizedDescription: (doc, message, addonDetails) => {
+      let image = doc.createElement("image");
+      image.setAttribute("class", "extension-controlled-icon alltabs-icon");
+      return BrowserUtils.getLocalizedFragment(doc, message, addonDetails, image);
+    },
+    learnMoreMessageId: "tabHideControlled.learnMore",
+    learnMoreLink: "extension-hiding-tabs",
+  });
+});
+
 function showHiddenTabs(id) {
   let windowsEnum = Services.wm.getEnumerator("navigator:browser");
   while (windowsEnum.hasMoreElements()) {
     let win = windowsEnum.getNext();
     if (win.closed || !win.gBrowser) {
       continue;
     }
 
@@ -308,16 +332,21 @@ this.tabs = class extends ExtensionAPI {
   static onUpdate(id, manifest) {
     if (!manifest.permissions || !manifest.permissions.includes("tabHide")) {
       showHiddenTabs(id);
     }
   }
 
   static onDisable(id) {
     showHiddenTabs(id);
+    tabHidePopup.clearConfirmation(id);
+  }
+
+  static onUninstall(id) {
+    tabHidePopup.clearConfirmation(id);
   }
 
   getAPI(context) {
     let {extension} = context;
 
     let {tabManager} = extension;
 
     function getTabOrActive(tabId) {
@@ -1222,15 +1251,19 @@ this.tabs = class extends ExtensionAPI {
           for (let tab of tabs) {
             if (tab.ownerGlobal && !tab.hidden) {
               tab.ownerGlobal.gBrowser.hideTab(tab, extension.id);
               if (tab.hidden) {
                 hidden.push(tabTracker.getId(tab));
               }
             }
           }
+          if (hidden.length > 0) {
+            let win = Services.wm.getMostRecentWindow("navigator:browser");
+            tabHidePopup.open(win, extension.id);
+          }
           return hidden;
         },
       },
     };
     return self;
   }
 };
--- a/browser/components/extensions/test/browser/browser_ext_tabs_hide.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_hide.js
@@ -30,16 +30,128 @@ add_task(async function test_pref_disabl
     background,
   };
   let extension = ExtensionTestUtils.loadExtension(extdata);
   await extension.startup();
   await extension.awaitFinish("pref-test");
   await extension.unload();
 });
 
+async function doorhangerTest(testFn) {
+  await SpecialPowers.pushPrefEnv({
+    set: [["extensions.webextensions.tabhide.enabled", true]],
+  });
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["tabs", "tabHide"],
+      icons: {
+        48: "addon-icon.png",
+      },
+    },
+    background() {
+      browser.test.onMessage.addListener(async (msg, data) => {
+        let tabs = await browser.tabs.query(data);
+        await browser.tabs[msg](tabs.map(t => t.id));
+        browser.test.sendMessage("done");
+      });
+    },
+    useAddonManager: "temporary",
+  });
+
+  await extension.startup();
+
+  // Open some tabs so we can hide them.
+  let firstTab = gBrowser.selectedTab;
+  let tabs = [
+    await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/?one", true, true),
+    await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/?two", true, true),
+  ];
+  gBrowser.selectedTab = firstTab;
+
+  await testFn(extension);
+
+  BrowserTestUtils.removeTab(tabs[0]);
+  BrowserTestUtils.removeTab(tabs[1]);
+
+  await extension.unload();
+}
+
+add_task(function test_doorhanger_keep() {
+  return doorhangerTest(async function(extension) {
+    is(gBrowser.visibleTabs.length, 3, "There are 3 visible tabs");
+
+    // Hide the first tab, expect the doorhanger.
+    let panel = document.getElementById("extension-notification-panel");
+    let popupShown = promisePopupShown(panel);
+    extension.sendMessage("hide", {url: "*://*/?one"});
+    await extension.awaitMessage("done");
+    await popupShown;
+
+    is(gBrowser.visibleTabs.length, 2, "There are 2 visible tabs now");
+    is(panel.anchorNode.closest("toolbarbutton").id,
+       "alltabs-button", "The doorhanger is anchored to the all tabs button");
+
+    // Click the Keep Tabs Hidden button.
+    let popupnotification = document.getElementById("extension-tab-hide-notification");
+    let popupHidden = promisePopupHidden(panel);
+    document.getAnonymousElementByAttribute(
+      popupnotification, "anonid", "button").click();
+    await popupHidden;
+
+    // Hide another tab and ensure the popup didn't open.
+    extension.sendMessage("hide", {url: "*://*/?two"});
+    await extension.awaitMessage("done");
+    is(panel.state, "closed", "The popup is still closed");
+    is(gBrowser.visibleTabs.length, 1, "There's one visible tab now");
+
+    extension.sendMessage("show", {});
+    await extension.awaitMessage("done");
+  });
+});
+
+add_task(function test_doorhanger_disable() {
+  return doorhangerTest(async function(extension) {
+    is(gBrowser.visibleTabs.length, 3, "There are 3 visible tabs");
+
+    // Hide the first tab, expect the doorhanger.
+    let panel = document.getElementById("extension-notification-panel");
+    let popupShown = promisePopupShown(panel);
+    extension.sendMessage("hide", {url: "*://*/?one"});
+    await extension.awaitMessage("done");
+    await popupShown;
+
+    is(gBrowser.visibleTabs.length, 2, "There are 2 visible tabs now");
+    is(panel.anchorNode.closest("toolbarbutton").id,
+       "alltabs-button", "The doorhanger is anchored to the all tabs button");
+
+    // verify the contents of the description.
+    let popupnotification = document.getElementById("extension-tab-hide-notification");
+    let description = popupnotification.querySelector("description");
+    let addon = await AddonManager.getAddonByID(extension.id);
+    ok(description.textContent.includes(addon.name),
+       "The extension name is in the description");
+    let images = Array.from(description.querySelectorAll("image"));
+    is(images.length, 2, "There are two images");
+    ok(images.some(img => img.src.includes("addon-icon.png")),
+       "There's an icon for the extension");
+    ok(images.some(img => getComputedStyle(img).backgroundImage.includes("arrow-dropdown-16.svg")),
+       "There's an icon for the all tabs menu");
+
+    // Click the Disable Extension button.
+    let popupHidden = promisePopupHidden(panel);
+    document.getAnonymousElementByAttribute(
+      popupnotification, "anonid", "secondarybutton").click();
+    await popupHidden;
+
+    is(gBrowser.visibleTabs.length, 3, "There are 3 visible tabs again");
+    is(addon.userDisabled, true, "The extension is now disabled");
+  });
+});
+
 add_task(async function test_tabs_showhide() {
   await SpecialPowers.pushPrefEnv({
     set: [["extensions.webextensions.tabhide.enabled", true]],
   });
 
   async function background() {
     browser.test.onMessage.addListener(async (msg, data) => {
       switch (msg) {
@@ -71,16 +183,17 @@ add_task(async function test_tabs_showhi
         }
       }
     });
   }
 
   let extdata = {
     manifest: {permissions: ["tabs", "tabHide"]},
     background,
+    useAddonManager: "temporary", // So the doorhanger can find the addon.
   };
   let extension = ExtensionTestUtils.loadExtension(extdata);
   await extension.startup();
 
   let sessData = {
     windows: [{
       tabs: [
         {entries: [{url: "about:blank", triggeringPrincipal_base64}]},
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -985,16 +985,22 @@ you can use these alternative items. Oth
 
 <!ENTITY homepageControlled.message "An extension has changed what you see as your home page. You can restore your settings if you do not want this change.">
 <!ENTITY homepageControlled.header.message "Your home page has changed.">
 <!ENTITY homepageControlled.keepButton.label "Keep Changes">
 <!ENTITY homepageControlled.keepButton.accesskey "K">
 <!ENTITY homepageControlled.disableButton.label "Disable Extension">
 <!ENTITY homepageControlled.disableButton.accesskey "D">
 
+<!ENTITY tabHideControlled.header.message "Access Your Hidden Tabs">
+<!ENTITY tabHideControlled.keepButton.label "Keep Tabs Hidden">
+<!ENTITY tabHideControlled.keepButton.accesskey "K">
+<!ENTITY tabHideControlled.disableButton.label "Disable Extension">
+<!ENTITY tabHideControlled.disableButton.accesskey "D">
+
 <!ENTITY pageActionButton.tooltip "Page actions">
 <!ENTITY pageAction.addToUrlbar.label "Add to Address Bar">
 <!ENTITY pageAction.removeFromUrlbar.label "Remove from Address Bar">
 <!ENTITY pageAction.allowInUrlbar.label "Show in Address Bar">
 <!ENTITY pageAction.disallowInUrlbar.label "Don’t Show in Address Bar">
 <!ENTITY pageAction.manageExtension.label "Manage Extension…">
 
 <!ENTITY pageAction.sendTabToDevice.label "Send Tab to Device">
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/addons/extension-controlled.inc.css
@@ -0,0 +1,33 @@
+#extension-notification-panel > .panel-arrowcontainer > .panel-arrowcontent {
+  padding: 0;
+}
+
+.extension-controlled-notification > .popup-notification-body-container > .popup-notification-body {
+  width: 30em;
+}
+
+.extension-controlled-notification > .popup-notification-body-container > .popup-notification-body > hbox > vbox > .popup-notification-description {
+  font-size: 1.3em;
+  font-weight: lighter;
+}
+
+.extension-controlled-notification {
+  margin-bottom: 0;
+}
+
+.extension-controlled-notification > popupnotificationcontent > description > .extension-controlled-icon {
+  height: 16px;
+  width: 16px;
+  vertical-align: bottom;
+}
+
+.extension-controlled-icon.alltabs-icon {
+  background: url("chrome://global/skin/icons/arrow-dropdown-16.svg");
+  /* This icon has a lot of extra space to the sides, reduce that a little. */
+  margin: 0 -1px 1px;
+}
+
+.extension-controlled-notification > .popup-notification-body-container > .popup-notification-body > .popup-notification-warning,
+.extension-controlled-notification > .popup-notification-body-container > .popup-notification-icon {
+  display: none;
+}
--- a/browser/themes/shared/browser.inc.css
+++ b/browser/themes/shared/browser.inc.css
@@ -1,13 +1,14 @@
 /* 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/. */
 
 %include downloads/indicator.inc.css
+%include addons/extension-controlled.inc.css
 
 %filter substitution
 %define navbarTabsShadowSize 1px
 %define themeTransition background-color 0.1s cubic-bezier(.17,.67,.83,.67)
 
 :root {
   /* Note: Setting this to 0 (without px) breaks CSS calculations for OSX. */
   --space-above-tabbar: 0px;
--- a/browser/themes/shared/customizableui/panelUI.inc.css
+++ b/browser/themes/shared/customizableui/panelUI.inc.css
@@ -310,46 +310,16 @@ panelview:not([mainview]) .toolbarbutton
   text-align: start;
   display: -moz-box;
 }
 
 .cui-widget-panel > .panel-arrowcontainer > .panel-arrowcontent {
   padding: 4px 0;
 }
 
-/* START notification popups for extension controlled content */
-#extension-notification-panel > .panel-arrowcontainer > .panel-arrowcontent {
-  padding: 0;
-}
-
-.extension-controlled-notification > .popup-notification-body-container > .popup-notification-body {
-  width: 30em;
-}
-
-.extension-controlled-notification > .popup-notification-body-container > .popup-notification-body > hbox > vbox > .popup-notification-description {
-  font-size: 1.3em;
-  font-weight: lighter;
-}
-
-.extension-controlled-notification {
-  margin-bottom: 0;
-}
-
-.extension-controlled-notification > popupnotificationcontent > description > .extension-controlled-icon {
-  height: 16px;
-  width: 16px;
-  vertical-align: bottom;
-}
-
-.extension-controlled-notification > .popup-notification-body-container > .popup-notification-body > .popup-notification-warning,
-.extension-controlled-notification > .popup-notification-body-container > .popup-notification-icon {
-  display: none;
-}
-/* END notification popups for extension controlled content */
-
 #appMenu-popup > .panel-arrowcontainer > .panel-arrowcontent,
 panel[photon] > .panel-arrowcontainer > .panel-arrowcontent {
   padding: 0;
 }
 
 #appMenu-popup panelview,
 #customizationui-widget-multiview panelview:not([extension]) {
   min-width: @menuPanelWidth@;
--- a/toolkit/locales/en-US/chrome/global/extensions.properties
+++ b/toolkit/locales/en-US/chrome/global/extensions.properties
@@ -33,12 +33,16 @@ saveaspdf.saveasdialog.title = Save As
 #LOCALIZATION NOTE (newTabControlled.message2) %S is the icon and name of the extension which updated the New Tab page.
 newTabControlled.message2 = An extension, %S, changed the page you see when you open a new tab.
 newTabControlled.learnMore = Learn more
 
 #LOCALIZATION NOTE (homepageControlled.message) %S is the icon and name of the extension which updated the homepage.
 homepageControlled.message = An extension, %S, changed what you see when you open your homepage and new windows.
 homepageControlled.learnMore = Learn more
 
+#LOCALIZATION NOTE (tabHideControlled.message) %1$S is the icon and name of the extension which hid tabs, %2$S is the icon of the all tabs button.
+tabHideControlled.message = An extension, %1$S, is hiding some of your tabs. You can still access all of your tabs from %2$S.
+tabHideControlled.learnMore = Learn more
+
 # LOCALIZATION NOTE (defaultTheme.name): This is displayed in about:addons -> Appearance
 defaultTheme.name=Default
 defaultTheme.description=The default theme.
 
--- a/toolkit/themes/shared/icons/arrow-dropdown-16.svg
+++ b/toolkit/themes/shared/icons/arrow-dropdown-16.svg
@@ -1,6 +1,6 @@
 <!-- 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/. -->
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
-  <path fill="context-fill" fill-opacity="context-fill-opacity" d="M8,12L3,7,4,6l4,4,4-4,1,1Z"/>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
+  <path fill="context-fill" fill-opacity="context-fill-opacity" d="M8 12a1 1 0 0 1-.707-.293l-5-5a1 1 0 0 1 1.414-1.414L8 9.586l4.293-4.293a1 1 0 0 1 1.414 1.414l-5 5A1 1 0 0 1 8 12z"></path>
 </svg>