Bug 641892 - Support showing multiple popup notification icons at the same time. r=margaret
authorOHZEKI Tetsuharu <saneyuki.s.snyk@gmail.com>
Thu, 03 May 2012 18:33:47 -0400
changeset 93049 aeea5b83cf89dba073ca078b9528707c9b467505
parent 93048 26738df8a4e08a5474342dcfa36e16b537fb5eeb
child 93050 b5fd22efe23d194b72eea7ff8446e641fcef7435
push id22608
push useremorley@mozilla.com
push dateFri, 04 May 2012 12:47:48 +0000
treeherdermozilla-central@9ebf3dc839c5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmargaret
bugs641892
milestone15.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 641892 - Support showing multiple popup notification icons at the same time. r=margaret
browser/base/content/browser.css
browser/base/content/test/browser_popupNotification.js
browser/themes/gnomestripe/browser.css
browser/themes/winstripe/browser.css
toolkit/content/PopupNotifications.jsm
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -425,30 +425,21 @@ window[chromehidden~="toolbar"] toolbar:
   -moz-binding: url("chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-image");
   margin: 0;
 }
 
 
 /* notification anchors should only be visible when their associated
    notifications are */
 .notification-anchor-icon {
-  display: none;
   -moz-user-focus: normal;
 }
 
-/* We use the iconBox as the notification anchor when a popup notification is
-   created with a null anchorID, so in that case use a default anchor icon. */
-#notification-popup-box[anchorid="notification-popup-box"] > #default-notification-icon,
-#notification-popup-box[anchorid="geo-notification-icon"] > #geo-notification-icon,
-#notification-popup-box[anchorid="indexedDB-notification-icon"] > #indexedDB-notification-icon,
-#notification-popup-box[anchorid="addons-notification-icon"] > #addons-notification-icon,
-#notification-popup-box[anchorid="password-notification-icon"] > #password-notification-icon,
-#notification-popup-box[anchorid="webapps-notification-icon"] > #webapps-notification-icon,
-#notification-popup-box[anchorid="plugins-notification-icon"] > #plugins-notification-icon {
-  display: -moz-box;
+.notification-anchor-icon:not([showing]) {
+  display: none;
 }
 
 #invalid-form-popup > description {
   max-width: 280px;
 }
 
 #geolocation-notification {
   -moz-binding: url("chrome://browser/content/urlbarBindings.xml#geolocation-notification");
--- a/browser/base/content/test/browser_popupNotification.js
+++ b/browser/base/content/test/browser_popupNotification.js
@@ -637,16 +637,94 @@ var tests = [
       checkPopup(popup, this.notifyObj);
       dismissNotification(popup);
     },
     onHidden: function (popup) {
       ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered");
       ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
     }
   },
+  // Test multiple notification icons are shown
+  { // Test #21
+    run: function () {
+      this.notifyObj1 = new basicNotification();
+      this.notifyObj1.id += "_1";
+      this.notifyObj1.anchorID = "default-notification-icon";
+      this.notification1 = showNotification(this.notifyObj1);
+
+      this.notifyObj2 = new basicNotification();
+      this.notifyObj2.id += "_2";
+      this.notifyObj2.anchorID = "geo-notification-icon";
+      this.notification2 = showNotification(this.notifyObj2);
+    },
+    onShown: function (popup) {
+      checkPopup(popup, this.notifyObj2);
+
+      // check notifyObj1 anchor icon is showing
+      isnot(document.getElementById("default-notification-icon").boxObject.width, 0,
+            "default anchor should be visible");
+      // check notifyObj2 anchor icon is showing
+      isnot(document.getElementById("geo-notification-icon").boxObject.width, 0,
+            "geo anchor should be visible");
+
+      dismissNotification(popup);
+    },
+    onHidden: [
+      function (popup) {
+      },
+      function (popup) {
+        this.notification1.remove();
+        ok(this.notifyObj1.removedCallbackTriggered, "removed callback triggered");
+
+        this.notification2.remove();
+        ok(this.notifyObj2.removedCallbackTriggered, "removed callback triggered");
+      }
+    ],
+  },
+  // Test that multiple notification icons are removed when switching tabs
+  { // Test #22
+    run: function () {
+      // show the notification on old tab.
+      this.notifyObjOld = new basicNotification();
+      this.notifyObjOld.anchorID = "default-notification-icon";
+      this.notificationOld = showNotification(this.notifyObjOld);
+
+      // switch tab
+      this.oldSelectedTab = gBrowser.selectedTab;
+      gBrowser.selectedTab = gBrowser.addTab("about:blank");
+
+      // show the notification on new tab.
+      this.notifyObjNew = new basicNotification();
+      this.notifyObjNew.anchorID = "geo-notification-icon";
+      this.notificationNew = showNotification(this.notifyObjNew);
+    },
+    onShown: function (popup) {
+      checkPopup(popup, this.notifyObjNew);
+
+      // check notifyObjOld anchor icon is removed
+      is(document.getElementById("default-notification-icon").boxObject.width, 0,
+         "default anchor shouldn't be visible");
+      // check notifyObjNew anchor icon is showing
+      isnot(document.getElementById("geo-notification-icon").boxObject.width, 0,
+            "geo anchor should be visible");
+
+      dismissNotification(popup);
+    },
+    onHidden: [
+      function (popup) {
+      },
+      function (popup) {
+        this.notificationNew.remove();
+        gBrowser.removeTab(gBrowser.selectedTab);
+
+        gBrowser.selectedTab = this.oldSelectedTab;
+        this.notificationOld.remove();
+      }
+    ],
+  }
 ];
 
 function showNotification(notifyObj) {
   return PopupNotifications.show(notifyObj.browser,
                                  notifyObj.id,
                                  notifyObj.message,
                                  notifyObj.anchorID,
                                  notifyObj.mainAction,
--- a/browser/themes/gnomestripe/browser.css
+++ b/browser/themes/gnomestripe/browser.css
@@ -1255,16 +1255,17 @@ toolbar[iconsize="small"] #feed-button {
 #notification-popup-box:-moz-locale-dir(rtl),
 .notification-anchor-icon:-moz-locale-dir(rtl) {
   -moz-transform: scaleX(-1);
 }
 
 .notification-anchor-icon {
   width: 16px;
   height: 16px;
+  margin: 0 2px;
 }
 
 .notification-anchor-icon:-moz-focusring {
   outline: 1px dotted -moz-DialogText;
 }
 
 #default-notification-icon {
   list-style-image: url(chrome://global/skin/icons/information-16.png);
--- a/browser/themes/winstripe/browser.css
+++ b/browser/themes/winstripe/browser.css
@@ -2316,16 +2316,17 @@ toolbarbutton.bookmark-item[dragover="tr
 #notification-popup-box:-moz-locale-dir(rtl),
 .notification-anchor-icon:-moz-locale-dir(rtl) {
   -moz-transform: scaleX(-1);
 }
 
 .notification-anchor-icon {
   width: 16px;
   height: 16px;
+  margin: 0 2px;
 }
 
 .notification-anchor-icon:-moz-focusring {
   outline: 1px dotted -moz-DialogText;
   outline-offset: -3px;
 }
 
 #default-notification-icon {
--- a/toolkit/content/PopupNotifications.jsm
+++ b/toolkit/content/PopupNotifications.jsm
@@ -41,16 +41,19 @@ var EXPORTED_SYMBOLS = ["PopupNotificati
 var Cc = Components.classes, Ci = Components.interfaces;
 
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 const NOTIFICATION_EVENT_DISMISSED = "dismissed";
 const NOTIFICATION_EVENT_REMOVED = "removed";
 const NOTIFICATION_EVENT_SHOWN = "shown";
 
+const ICON_SELECTOR = ".notification-anchor-icon";
+const ICON_ATTRIBUTE_SHOWING = "showing";
+
 /**
  * Notification object describes a single popup notification.
  *
  * @see PopupNotifications.show()
  */
 function Notification(id, message, anchorID, mainAction, secondaryActions,
                       browser, owner, options) {
   this.id = id;
@@ -77,25 +80,28 @@ Notification.prototype = {
   /**
    * Removes the notification and updates the popup accordingly if needed.
    */
   remove: function Notification_remove() {
     this.owner.remove(this);
   },
 
   get anchorElement() {
-    if (!this.owner.iconBox)
+    let iconBox = this.owner.iconBox;
+    if (!iconBox)
       return null;
 
     let anchorElement = null;
     if (this.anchorID)
-      anchorElement = this.owner.iconBox.querySelector("#"+this.anchorID);
+      anchorElement = iconBox.querySelector("#"+this.anchorID);
 
+    // Use a default anchor icon if it's available
     if (!anchorElement)
-      anchorElement = this.owner.iconBox;
+      anchorElement = iconBox.querySelector("#default-notification-icon") ||
+                      iconBox;
 
     return anchorElement;
   }
 };
 
 /**
  * The PopupNotifications object manages popup notifications for a given browser
  * window.
@@ -388,16 +394,18 @@ PopupNotifications.prototype = {
     let notifications = this._getNotificationsForBrowser(notification.browser);
     if (!notifications)
       return;
 
     var index = notifications.indexOf(notification);
     if (index == -1)
       return;
 
+    notification.anchorElement.removeAttribute(ICON_ATTRIBUTE_SHOWING);
+
     // remove the notification
     notifications.splice(index, 1);
     this._fireCallback(notification, NOTIFICATION_EVENT_REMOVED);
   },
 
   /**
    * Dismisses the notification without removing it.
    */
@@ -497,31 +505,37 @@ PopupNotifications.prototype = {
     }, this);
   },
 
   /**
    * Updates the notification state in response to window activation or tab
    * selection changes.
    */
   _update: function PopupNotifications_update(anchor) {
+    if (this.iconBox) {
+      // hide icons of the previous tab.
+      this._hideIcons();
+    }
+
     let anchorElement, notificationsToShow = [];
-    let haveNotifications = this._currentNotifications.length > 0;
+    let currentNotifications = this._currentNotifications;
+    let haveNotifications = currentNotifications.length > 0;
     if (haveNotifications) {
       // Only show the notifications that have the passed-in anchor (or the
       // first notification's anchor, if none was passed in). Other
       // notifications will be shown once these are dismissed.
-      anchorElement = anchor || this._currentNotifications[0].anchorElement;
+      anchorElement = anchor || currentNotifications[0].anchorElement;
 
       if (this.iconBox) {
+        this._showIcons(currentNotifications);
         this.iconBox.hidden = false;
-        this.iconBox.setAttribute("anchorid", anchorElement.id);
       }
 
       // Also filter out notifications that have been dismissed.
-      notificationsToShow = this._currentNotifications.filter(function (n) {
+      notificationsToShow = currentNotifications.filter(function (n) {
         return !n.dismissed && n.anchorElement == anchorElement &&
                !n.options.neverShow;
       });
     }
 
     if (notificationsToShow.length > 0) {
       this._showPanel(notificationsToShow, anchorElement);
     } else {
@@ -534,16 +548,32 @@ PopupNotifications.prototype = {
 
       // Only hide the iconBox if we actually have no notifications (as opposed
       // to not having any showable notifications)
       if (this.iconBox && !haveNotifications)
         this.iconBox.hidden = true;
     }
   },
 
+  _showIcons: function PopupNotifications_showIcons(aCurrentNotifications) {
+    for (let notification of aCurrentNotifications) {
+      let anchorElm = notification.anchorElement;
+      if (anchorElm) {
+        anchorElm.setAttribute(ICON_ATTRIBUTE_SHOWING, "true");
+      }
+    }
+  },
+
+  _hideIcons: function PopupNotifications_hideIcons() {
+    let icons = this.iconBox.querySelectorAll(ICON_SELECTOR);
+    for (let icon of icons) {
+      icon.removeAttribute(ICON_ATTRIBUTE_SHOWING);
+    }
+  },
+
   _getNotificationsForBrowser: function PopupNotifications_getNotifications(browser) {
     if (browser.popupNotifications)
       return browser.popupNotifications;
 
     return browser.popupNotifications = [];
   },
 
   _onIconBoxCommand: function PopupNotifications_onIconBoxCommand(event) {