Bug 1491438 move addon-installed notification to the appMenu, r=Gijs
authorShane Caraveo <scaraveo@mozilla.com>
Mon, 08 Oct 2018 14:56:39 +0000
changeset 488420 9b3b400bac42508665bf0481a084fbf29dd4540b
parent 488419 7a1ac5e2c00cb4dd4e3f6d54b859c7f49e15699a
child 488421 632c0d2d8c2459422a8c2d8743b4f925da0596ef
push id246
push userfmarier@mozilla.com
push dateSat, 13 Oct 2018 00:15:40 +0000
reviewersGijs
bugs1491438
milestone64.0a1
Bug 1491438 move addon-installed notification to the appMenu, r=Gijs Differential Revision: https://phabricator.services.mozilla.com/D7949
browser/base/content/popup-notifications.inc
browser/base/content/test/webextensions/head.js
browser/components/customizableui/content/panelUI.inc.xul
browser/components/customizableui/content/panelUI.js
browser/modules/ExtensionsUI.jsm
toolkit/modules/AppMenuNotifications.jsm
toolkit/mozapps/extensions/test/browser/browser_webapi_install.js
toolkit/mozapps/extensions/test/browser/browser_webapi_theme.js
toolkit/mozapps/extensions/test/browser/head.js
toolkit/mozapps/extensions/test/xpinstall/browser_doorhanger_installs.js
toolkit/mozapps/extensions/test/xpinstall/head.js
--- a/browser/base/content/popup-notifications.inc
+++ b/browser/base/content/popup-notifications.inc
@@ -75,22 +75,16 @@
     <popupnotification id="addon-webext-permissions-notification" hidden="true">
       <popupnotificationcontent class="addon-webext-perm-notification-content" orient="vertical">
         <description id="addon-webext-perm-text" class="addon-webext-perm-text"/>
         <label id="addon-webext-perm-intro" class="addon-webext-perm-text"/>
         <html:ul id="addon-webext-perm-list" class="addon-webext-perm-list"/>
       </popupnotificationcontent>
     </popupnotification>
 
-    <popupnotification id="addon-installed-notification" hidden="true">
-      <popupnotificationcontent class="addon-installed-notification-content" orient="vertical">
-        <description>&addonPostInstallMessage.label;</description>
-      </popupnotificationcontent>
-    </popupnotification>
-
     <popupnotification id="contextual-feature-recommendation-notification" hidden="true">
       <popupnotificationheader id="cfr-notification-header">
         <stack id="cfr-notification-header-stack">
           <description id="cfr-notification-header-label"></description>
           <label id="cfr-notification-header-link" class="text-link">
             <xul:image id="cfr-notification-header-image"/>
           </label>
         </stack>
--- a/browser/base/content/test/webextensions/head.js
+++ b/browser/base/content/test/webextensions/head.js
@@ -34,16 +34,37 @@ function promisePopupNotificationShown(n
       PopupNotifications.panel.removeEventListener("popupshown", popupshown);
       resolve(PopupNotifications.panel.firstElementChild);
     }
 
     PopupNotifications.panel.addEventListener("popupshown", popupshown);
   });
 }
 
+function promiseAppMenuNotificationShown(id) {
+  ChromeUtils.import("resource://gre/modules/AppMenuNotifications.jsm");
+  return new Promise(resolve => {
+    function popupshown() {
+      let notification = AppMenuNotifications.activeNotification;
+      if (!notification) { return; }
+
+      is(notification.id, id, `${id} notification shown`);
+      ok(PanelUI.isNotificationPanelOpen, "notification panel open");
+
+      PanelUI.notificationPanel.removeEventListener("popupshown", popupshown);
+
+      let popupnotificationID = PanelUI._getPopupId(notification);
+      let popupnotification = document.getElementById(popupnotificationID);
+
+      resolve(popupnotification);
+    }
+    PanelUI.notificationPanel.addEventListener("popupshown", popupshown);
+  });
+}
+
 /**
  * Wait for a specific install event to fire for a given addon
  *
  * @param {AddonWrapper} addon
  *        The addon to watch for an event on
  * @param {string}
  *        The name of the event to watch for (e.g., onInstallEnded)
  *
@@ -302,17 +323,17 @@ async function testInstallMethod(install
 
     if (cancel) {
       panel.secondaryButton.click();
       try {
         await installMethodPromise;
       } catch (err) {}
     } else {
       // Look for post-install notification
-      let postInstallPromise = promisePopupNotificationShown("addon-installed");
+      let postInstallPromise = promiseAppMenuNotificationShown("addon-installed");
       panel.button.click();
 
       // Press OK on the post-install notification
       panel = await postInstallPromise;
       panel.button.click();
 
       await installMethodPromise;
     }
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -142,16 +142,29 @@
                      dropmarkerhidden="true"
                      checkboxhidden="true"
                      buttonhighlight="true"
                      hidden="true">
     <popupnotificationcontent id="update-restart-notification-content" orient="vertical">
       <description id="update-restart-description">&updateRestart.message2;</description>
     </popupnotificationcontent>
   </popupnotification>
+
+  <popupnotification id="appMenu-addon-installed-notification"
+                     popupid="addon-installed"
+                     closebuttonhidden="true"
+                     secondarybuttonhidden="true"
+                     dropmarkerhidden="true"
+                     checkboxhidden="true"
+                     buttonhighlight="true"
+                     hidden="true">
+    <popupnotificationcontent class="addon-installed-notification-content" orient="vertical">
+      <description>&addonPostInstallMessage.label;</description>
+    </popupnotificationcontent>
+  </popupnotification>
 </panel>
 
 <menupopup id="customizationPaletteItemContextMenu"
            onpopupshowing="gCustomizeMode.onPaletteContextMenuShowing(event)">
   <menuitem oncommand="gCustomizeMode.addToToolbar(document.popupNode)"
             class="customize-context-addToToolbar"
             accesskey="&customizeMenu.addToToolbar.accesskey;"
             label="&customizeMenu.addToToolbar.label;"/>
--- a/browser/components/customizableui/content/panelUI.js
+++ b/browser/components/customizableui/content/panelUI.js
@@ -748,27 +748,46 @@ const PanelUI = {
   },
 
   _clearAllNotifications() {
     this._clearNotificationPanel();
     this._clearBadge();
     this._clearBannerItem();
   },
 
+  _formatDescriptionMessage(n) {
+    let text = {};
+    let array = n.options.message.split("<>");
+    text.start = array[0] || "";
+    text.name = n.options.name || "";
+    text.end = array[1] || "";
+    return text;
+  },
+
   _refreshNotificationPanel(notification) {
     this._clearNotificationPanel();
 
     let popupnotificationID = this._getPopupId(notification);
     let popupnotification = document.getElementById(popupnotificationID);
 
     popupnotification.setAttribute("id", popupnotificationID);
     popupnotification.setAttribute("buttoncommand", "PanelUI._onNotificationButtonEvent(event, 'buttoncommand');");
     popupnotification.setAttribute("secondarybuttoncommand",
       "PanelUI._onNotificationButtonEvent(event, 'secondarybuttoncommand');");
 
+    if (notification.options.message) {
+      let desc = this._formatDescriptionMessage(notification);
+      popupnotification.setAttribute("label", desc.start);
+      popupnotification.setAttribute("name", desc.name);
+      popupnotification.setAttribute("endlabel", desc.end);
+    }
+    if (notification.options.popupIconURL) {
+      popupnotification.setAttribute("icon", notification.options.popupIconURL);
+    }
+
     popupnotification.notification = notification;
     popupnotification.hidden = false;
   },
 
   _showBadge(notification) {
     let badgeStatus = this._getBadgeStatus(notification);
     this.menuButton.setAttribute("badge-status", badgeStatus);
   },
--- a/browser/modules/ExtensionsUI.jsm
+++ b/browser/modules/ExtensionsUI.jsm
@@ -396,18 +396,17 @@ var ExtensionsUI = {
       let {browser, window} = getTabBrowser(target);
       window.PopupNotifications.show(browser, "addon-webext-defaultsearch", strings.text,
                                      "addons-notification-icon", action,
                                      secondaryActions, popupOptions);
     });
   },
 
   showInstallNotification(target, addon) {
-    let {browser, window} = getTabBrowser(target);
-    let popups = window.PopupNotifications;
+    let {window} = getTabBrowser(target);
 
     let brandBundle = window.document.getElementById("bundle_brand");
     let appName = brandBundle.getString("brandShortName");
     let bundle = window.gNavigatorBundle;
 
     let message = bundle.getFormattedString("addonPostInstall.message1",
                                             ["<>", appName]);
     return new Promise(resolve => {
@@ -416,26 +415,20 @@ var ExtensionsUI = {
         accessKey: bundle.getString("addonPostInstall.okay.key"),
         callback: resolve,
       };
 
       let icon = addon.isWebExtension ?
                  AddonManager.getPreferredIconURL(addon, 32, window) || DEFAULT_EXTENSION_ICON :
                  "chrome://browser/skin/addons/addon-install-installed.svg";
       let options = {
-        hideClose: true,
-        timeout: Date.now() + 30000,
+        name: addon.name,
+        message,
         popupIconURL: icon,
-        eventCallback(topic) {
-          if (topic == "dismissed") {
-            resolve();
-          }
-        },
-        name: addon.name,
+        onDismissed: resolve,
       };
 
-      popups.show(browser, "addon-installed", message, "addons-notification-icon",
-                  action, null, options);
+      AppMenuNotifications.showNotification("addon-installed", action, null, options);
     });
   },
 };
 
 EventEmitter.decorate(ExtensionsUI);
--- a/toolkit/modules/AppMenuNotifications.jsm
+++ b/toolkit/modules/AppMenuNotifications.jsm
@@ -117,16 +117,19 @@ var AppMenuNotifications = {
       notifications = this._notifications.filter(n => n.id == id);
     } else {
       // If it's not a string, assume RegExp
       notifications = this._notifications.filter(n => id.test(n.id));
     }
 
     notifications.forEach(n => {
       n.dismissed = true;
+      if (n.options.onDismissed) {
+        n.options.onDismissed();
+      }
     });
     this._updateNotifications();
   },
 
   callMainAction(win, notification, fromDoorhanger) {
     let action = notification.mainAction;
     this._callAction(win, notification, action, fromDoorhanger);
   },
--- a/toolkit/mozapps/extensions/test/browser/browser_webapi_install.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_webapi_install.js
@@ -192,17 +192,17 @@ function makeRegularTest(options, what) 
         props: {state: "STATE_INSTALLING"},
       },
       {
         event: "onInstallEnded",
         props: {state: "STATE_INSTALLED"},
       },
     ];
 
-    let promptPromise = promiseNotification("addon-installed");
+    let promptPromise = acceptAppMenuNotificationWhenShown("addon-installed");
 
     await testInstall(browser, options, steps, what);
 
     await promptPromise;
 
     let version = Services.prefs.getIntPref("webapitest.active_version");
     is(version, 1, "the install really did work");
 
--- a/toolkit/mozapps/extensions/test/browser/browser_webapi_theme.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_webapi_theme.js
@@ -15,17 +15,17 @@ add_task(async function test_theme_insta
     function observer(subject, topic, data) {
       updates.push(data);
     }
     Services.obs.addObserver(observer, "lightweight-theme-styling-update");
     registerCleanupFunction(() => {
       Services.obs.removeObserver(observer, "lightweight-theme-styling-update");
     });
 
-    let promptPromise = promiseNotification("addon-installed");
+    let promptPromise = acceptAppMenuNotificationWhenShown("addon-installed");
 
     let installPromise = ContentTask.spawn(browser, URL, async (url) => {
       let install = await content.navigator.mozAddonManager.createInstall({url});
       return install.install();
     });
 
     await promptPromise;
     await installPromise;
--- a/toolkit/mozapps/extensions/test/browser/head.js
+++ b/toolkit/mozapps/extensions/test/browser/head.js
@@ -1382,8 +1382,30 @@ function promiseNotification(id = "addon
         PopupNotifications.panel.removeEventListener("popupshown", popupshown);
         PopupNotifications.panel.firstElementChild.button.click();
         resolve();
       }
     }
     PopupNotifications.panel.addEventListener("popupshown", popupshown);
   });
 }
+
+function acceptAppMenuNotificationWhenShown(id) {
+  ChromeUtils.import("resource://gre/modules/AppMenuNotifications.jsm");
+  return new Promise(resolve => {
+    function popupshown() {
+      let notification = AppMenuNotifications.activeNotification;
+      if (!notification) { return; }
+
+      is(notification.id, id, `${id} notification shown`);
+      ok(PanelUI.isNotificationPanelOpen, "notification panel open");
+
+      PanelUI.notificationPanel.removeEventListener("popupshown", popupshown);
+
+      let popupnotificationID = PanelUI._getPopupId(notification);
+      let popupnotification = document.getElementById(popupnotificationID);
+      popupnotification.button.click();
+
+      resolve();
+    }
+    PanelUI.notificationPanel.addEventListener("popupshown", popupshown);
+  });
+}
--- a/toolkit/mozapps/extensions/test/xpinstall/browser_doorhanger_installs.js
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser_doorhanger_installs.js
@@ -70,16 +70,38 @@ async function waitForProgressNotificati
     ok(notification, `Should have seen the right notification`);
     is(notification.button.hasAttribute("disabled"), wantDisabled,
        "The install button should be disabled?");
   }
 
   return PopupNotifications.panel;
 }
 
+function acceptAppMenuNotificationWhenShown(id) {
+  ChromeUtils.import("resource://gre/modules/AppMenuNotifications.jsm");
+  return new Promise(resolve => {
+    function popupshown() {
+      let notification = AppMenuNotifications.activeNotification;
+      if (!notification) { return; }
+
+      is(notification.id, id, `${id} notification shown`);
+      ok(PanelUI.isNotificationPanelOpen, "notification panel open");
+
+      PanelUI.notificationPanel.removeEventListener("popupshown", popupshown);
+
+      let popupnotificationID = PanelUI._getPopupId(notification);
+      let popupnotification = document.getElementById(popupnotificationID);
+      popupnotification.button.click();
+
+      resolve();
+    }
+    PanelUI.notificationPanel.addEventListener("popupshown", popupshown);
+  });
+}
+
 async function waitForNotification(aId, aExpectedCount = 1) {
   info("Waiting for " + aId + " notification");
 
   let topic = getObserverTopic(aId);
 
   let observerPromise;
   if (aId !== "addon-webext-permissions") {
     observerPromise = new Promise(resolve => {
@@ -118,16 +140,19 @@ async function waitForNotification(aId, 
     let notification = nodes.find(n => n.id == aId + "-notification");
     ok(notification, "Should have seen the " + aId + " notification");
   }
 
   return PopupNotifications.panel;
 }
 
 function waitForNotificationClose() {
+  if (!PopupNotifications.isPanelOpen) {
+    return Promise.resolve();
+  }
   return new Promise(resolve => {
     info("Waiting for notification to close");
     PopupNotifications.panel.addEventListener("popuphidden", function() {
       resolve();
     }, {once: true});
   });
 }
 
@@ -224,19 +249,19 @@ async function test_blockedInstall() {
   EventUtils.synthesizeMouse(notification.button, 20, 10, {});
   // Notification should have changed to progress notification
   ok(PopupNotifications.isPanelOpen, "Notification should still be open");
   notification = panel.childNodes[0];
   is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
 
   let installDialog = await dialogPromise;
 
-  notificationPromise = waitForNotification("addon-installed");
+  notificationPromise = acceptAppMenuNotificationWhenShown("addon-installed");
   installDialog.button.click();
-  panel = await notificationPromise;
+  await notificationPromise;
 
   let installs = await AddonManager.getAllInstalls();
   is(installs.length, 0, "Should be no pending installs");
 
   let addon = await AddonManager.getAddonByID("amosigned-xpi@tests.mozilla.org");
   addon.uninstall();
 
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
@@ -258,17 +283,17 @@ async function test_whitelistedInstall()
                                                 + triggers).then(newTab => tab = newTab);
   await progressPromise;
   let installDialog = await dialogPromise;
   await BrowserTestUtils.waitForCondition(() => !!tab, "tab should be present");
 
   is(gBrowser.selectedTab, tab,
      "tab selected in response to the addon-install-confirmation notification");
 
-  let notificationPromise = waitForNotification("addon-installed");
+  let notificationPromise = acceptAppMenuNotificationWhenShown("addon-installed");
   acceptInstallDialog(installDialog);
   await notificationPromise;
 
   let installs = await AddonManager.getAllInstalls();
   is(installs.length, 0, "Should be no pending installs");
 
   let addon = await AddonManager.getAddonByID("amosigned-xpi@tests.mozilla.org");
   addon.uninstall();
@@ -354,17 +379,17 @@ async function test_restartless() {
   let triggers = encodeURIComponent(JSON.stringify({
     "XPI": "restartless.xpi",
   }));
   gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
   BrowserTestUtils.loadURI(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
   await progressPromise;
   let installDialog = await dialogPromise;
 
-  let notificationPromise = waitForNotification("addon-installed");
+  let notificationPromise = acceptAppMenuNotificationWhenShown("addon-installed");
   acceptInstallDialog(installDialog);
   await notificationPromise;
 
   let installs = await AddonManager.getAllInstalls();
   is(installs.length, 0, "Should be no pending installs");
 
   let addon = await AddonManager.getAddonByID("restartless-xpi@tests.mozilla.org");
   addon.uninstall();
@@ -471,17 +496,17 @@ async function test_allUnverified() {
   let message = notification.getAttribute("label");
   is(message, "Caution: This site would like to install an unverified add-on in " + gApp + ". Proceed at your own risk.");
 
   let container = document.getElementById("addon-install-confirmation-content");
   is(container.children.length, 1, "Should be one item listed");
   is(container.children[0].firstElementChild.getAttribute("value"), "XPI Test", "Should have the right add-on");
   is(container.children[0].children.length, 1, "Shouldn't have the unverified marker");
 
-  let notificationPromise = waitForNotification("addon-installed");
+  let notificationPromise = acceptAppMenuNotificationWhenShown("addon-installed");
   acceptInstallDialog(installDialog);
   await notificationPromise;
 
   let addon = await AddonManager.getAddonByID("restartless-xpi@tests.mozilla.org");
   addon.uninstall();
 
   Services.perms.remove(makeURI("http://example.com/"), "install");
   await removeTabAndWaitForNotificationClose();
@@ -582,17 +607,17 @@ async function test_urlBar() {
   await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
   gURLBar.value = TESTROOT + "amosigned.xpi";
   gURLBar.focus();
   EventUtils.synthesizeKey("KEY_Enter");
 
   await progressPromise;
   let installDialog = await dialogPromise;
 
-  let notificationPromise = waitForNotification("addon-installed");
+  let notificationPromise = acceptAppMenuNotificationWhenShown("addon-installed");
   installDialog.button.click();
   await notificationPromise;
 
   let installs = await AddonManager.getAllInstalls();
   is(installs.length, 0, "Should be no pending installs");
 
   let addon = await AddonManager.getAddonByID("amosigned-xpi@tests.mozilla.org");
   addon.uninstall();
--- a/toolkit/mozapps/extensions/test/xpinstall/head.js
+++ b/toolkit/mozapps/extensions/test/xpinstall/head.js
@@ -108,16 +108,17 @@ var Harness = {
       Services.obs.addObserver(this, "addon-install-failed");
       Services.obs.addObserver(this, "addon-install-complete");
 
       AddonManager.addInstallListener(this);
 
       Services.wm.addListener(this);
 
       window.addEventListener("popupshown", this);
+      PanelUI.notificationPanel.addEventListener("popupshown", this);
 
       var self = this;
       registerCleanupFunction(async function() {
         Services.prefs.clearUserPref(PREF_LOGGING_ENABLED);
         Services.prefs.clearUserPref(PREF_INSTALL_REQUIRESECUREORIGIN);
         Services.obs.removeObserver(self, "addon-install-started");
         Services.obs.removeObserver(self, "addon-install-disabled");
         // Services.obs.removeObserver(self, "addon-install-cancelled");
@@ -126,32 +127,38 @@ var Harness = {
         Services.obs.removeObserver(self, "addon-install-failed");
         Services.obs.removeObserver(self, "addon-install-complete");
 
         AddonManager.removeInstallListener(self);
 
         Services.wm.removeListener(self);
 
         window.removeEventListener("popupshown", self);
+        PanelUI.notificationPanel.removeEventListener("popupshown", self);
 
         let aInstalls = await AddonManager.getAllInstalls();
         is(aInstalls.length, 0, "Should be no active installs at the end of the test");
         aInstalls.forEach(function(aInstall) {
           info("Install for " + aInstall.sourceURI + " is in state " + aInstall.state);
           aInstall.cancel();
         });
       });
     }
 
     this.installCount = 0;
     this.pendingCount = 0;
     this.runningInstalls = [];
   },
 
   finish() {
+    // Some tests using this harness somehow finish leaving
+    // the addon-installed panel open.  hiding here addresses
+    // that which fixes the rest of the tests.  Since no test
+    // here cares about this panel, we just need it to close.
+    PanelUI.notificationPanel.hidePopup();
     finish();
   },
 
   endTest() {
     let callback = this.installsCompletedCallback;
     let count = this.installCount;
 
     is(this.runningInstalls.length, 0, "Should be no running installs left");
@@ -231,21 +238,23 @@ var Harness = {
       panel.secondaryButton.click();
     } else {
       panel.button.click();
     }
   },
 
   handleEvent(event) {
     if (event.type === "popupshown") {
-      if (event.target.firstElementChild) {
+      if (event.target == PanelUI.notificationPanel) {
+        PanelUI.notificationPanel.hidePopup();
+      } else if (event.target.firstElementChild) {
         let popupId = event.target.getAttribute("popupid");
         if (popupId === "addon-webext-permissions") {
           this.popupReady(event.target.firstElementChild);
-        } else if (popupId === "addon-installed" || popupId === "addon-install-failed") {
+        } else if (popupId === "addon-install-failed") {
           event.target.firstElementChild.button.click();
         }
       }
     }
   },
 
   // Install blocked handling