Bug 1079303 - fix popupnotifications.jsm to not hide its async-ness and immediately dismiss open notifications when another anchor is clicked, r=dolske
authorGijs Kruitbosch <gijskruitbosch@gmail.com>
Mon, 24 Nov 2014 23:54:33 +0000
changeset 221204 6766cfb95e93b465b43b3e6616308e29c3a7f364
parent 221203 cb25573de7e7003725d267d9a1c0da679fcca995
child 221205 a76ba87bf502a66ad94afa4c66d920683cc99437
push id28013
push userphilringnalda@gmail.com
push dateWed, 24 Dec 2014 23:31:28 +0000
treeherdermozilla-central@38471b0310c9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdolske
bugs1079303
milestone37.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 1079303 - fix popupnotifications.jsm to not hide its async-ness and immediately dismiss open notifications when another anchor is clicked, r=dolske
toolkit/modules/PopupNotifications.jsm
--- a/toolkit/modules/PopupNotifications.jsm
+++ b/toolkit/modules/PopupNotifications.jsm
@@ -2,16 +2,17 @@
  * 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/. */
 
 this.EXPORTED_SYMBOLS = ["PopupNotifications"];
 
 var Cc = Components.classes, Ci = Components.interfaces, Cu = Components.utils;
 
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
 
 const NOTIFICATION_EVENT_DISMISSED = "dismissed";
 const NOTIFICATION_EVENT_REMOVED = "removed";
 const NOTIFICATION_EVENT_SHOWING = "showing";
 const NOTIFICATION_EVENT_SHOWN = "shown";
 const NOTIFICATION_EVENT_SWAPPING = "swapping";
 
 const ICON_SELECTOR = ".notification-anchor-icon";
@@ -151,30 +152,16 @@ PopupNotifications.prototype = {
       iconBox.addEventListener("keypress", this, false);
     }
   },
   get iconBox() {
     return this._iconBox;
   },
 
   /**
-   * Enable or disable the opening/closing transition.
-   * @param state
-   *        Boolean state
-   */
-  set transitionsEnabled(state) {
-    if (state) {
-      this.panel.removeAttribute("animate");
-    }
-    else {
-      this.panel.setAttribute("animate", "false");
-    }
-  },
-
-  /**
    * Retrieve a Notification object associated with the browser/ID pair.
    * @param id
    *        The Notification ID to search for.
    * @param browser
    *        The browser whose notifications should be searched. If null, the
    *        currently selected browser's notifications will be searched.
    *
    * @returns the corresponding Notification object, or null if no such
@@ -480,25 +467,26 @@ PopupNotifications.prototype = {
     if (browser)
       browser.focus();
   },
 
   /**
    * Hides the notification popup.
    */
   _hidePanel: function PopupNotifications_hide() {
-    // We need to disable the closing animation when setting _ignoreDismissal
-    // to true, otherwise the popuphidden event will fire after we have set
-    // _ignoreDismissal back to false.
-    let transitionsEnabled = this.transitionsEnabled;
-    this.transitionsEnabled = false;
-    this._ignoreDismissal = true;
+    if (this.panel.state == "closed") {
+      return Promise.resolve();
+    }
+    if (this._ignoreDismissal) {
+      return this._ignoreDismissal.promise;
+    }
+    let deferred = Promise.defer();
+    this._ignoreDismissal = deferred;
     this.panel.hidePopup();
-    this._ignoreDismissal = false;
-    this.transitionsEnabled = transitionsEnabled;
+    return deferred.promise;
   },
 
   /**
    * Removes all notifications from the notification popup.
    */
   _clearPanel: function () {
     let popupnotification;
     while ((popupnotification = this.panel.lastChild)) {
@@ -625,43 +613,43 @@ PopupNotifications.prototype = {
         this._fireCallback(n, NOTIFICATION_EVENT_SHOWN);
       }, this);
       return;
     }
 
     // If the panel is already open but we're changing anchors, we need to hide
     // it first.  Otherwise it can appear in the wrong spot.  (_hidePanel is
     // safe to call even if the panel is already hidden.)
-    this._hidePanel();
-
-    // If the anchor element is hidden or null, use the tab as the anchor. We
-    // only ever show notifications for the current browser, so we can just use
-    // the current tab.
-    let selectedTab = this.tabbrowser.selectedTab;
-    if (anchorElement) {
-      let bo = anchorElement.boxObject;
-      if (bo.height == 0 && bo.width == 0)
-        anchorElement = selectedTab; // hidden
-    } else {
-      anchorElement = selectedTab; // null
-    }
+    let promise = this._hidePanel().then(() => {
+      // If the anchor element is hidden or null, use the tab as the anchor. We
+      // only ever show notifications for the current browser, so we can just use
+      // the current tab.
+      let selectedTab = this.tabbrowser.selectedTab;
+      if (anchorElement) {
+        let bo = anchorElement.boxObject;
+        if (bo.height == 0 && bo.width == 0)
+          anchorElement = selectedTab; // hidden
+      } else {
+        anchorElement = selectedTab; // null
+      }
 
-    this._currentAnchorElement = anchorElement;
+      this._currentAnchorElement = anchorElement;
 
-    // On OS X and Linux we need a different panel arrow color for
-    // click-to-play plugins, so copy the popupid and use css.
-    this.panel.setAttribute("popupid", this.panel.firstChild.getAttribute("popupid"));
-    notificationsToShow.forEach(function (n) {
-      // Remember the time the notification was shown for the security delay.
-      n.timeShown = this.window.performance.now();
-    }, this);
-    this.panel.openPopup(anchorElement, "bottomcenter topleft");
-    notificationsToShow.forEach(function (n) {
-      this._fireCallback(n, NOTIFICATION_EVENT_SHOWN);
-    }, this);
+      // On OS X and Linux we need a different panel arrow color for
+      // click-to-play plugins, so copy the popupid and use css.
+      this.panel.setAttribute("popupid", this.panel.firstChild.getAttribute("popupid"));
+      notificationsToShow.forEach(function (n) {
+        // Remember the time the notification was shown for the security delay.
+        n.timeShown = this.window.performance.now();
+      }, this);
+      this.panel.openPopup(anchorElement, "bottomcenter topleft");
+      notificationsToShow.forEach(function (n) {
+        this._fireCallback(n, NOTIFICATION_EVENT_SHOWN);
+      }, this);
+    });
   },
 
   /**
    * Updates the notification state in response to window activation or tab
    * selection changes.
    *
    * @param notifications an array of Notification instances. if null,
    *                      notifications will be retrieved off the current
@@ -804,16 +792,26 @@ PopupNotifications.prototype = {
     if (this._currentNotifications.length == 0)
       return;
 
     // Get the anchor that is the immediate child of the icon box
     let anchor = event.target;
     while (anchor && anchor.parentNode != this.iconBox)
       anchor = anchor.parentNode;
 
+    if (!anchor) {
+      return;
+    }
+
+    // If the panel is not closed, and the anchor is different, immediately mark all
+    // active notifications for the previous anchor as dismissed
+    if (this.panel.state != "closed" && anchor != this._currentAnchorElement) {
+      this._dismissOrRemoveCurrentNotifications();
+    }
+
     this._reshowNotifications(anchor);
   },
 
   _reshowNotifications: function PopupNotifications_reshowNotifications(anchor, browser) {
     // Mark notifications anchored to this anchor as un-dismissed
     let notifications = this._getNotificationsForBrowser(browser || this.tabbrowser.selectedBrowser);
     notifications.forEach(function (n) {
       if (n.anchorElement == anchor)
@@ -876,45 +874,54 @@ PopupNotifications.prototype = {
         return n.options.eventCallback.call(n, event, ...args);
     } catch (error) {
       Cu.reportError(error);
     }
     return undefined;
   },
 
   _onPopupHidden: function PopupNotifications_onPopupHidden(event) {
-    if (event.target != this.panel || this._ignoreDismissal)
+    if (event.target != this.panel || this._ignoreDismissal) {
+      if (this._ignoreDismissal) {
+        this._ignoreDismissal.resolve();
+        this._ignoreDismissal = null;
+      }
       return;
+    }
 
+    this._dismissOrRemoveCurrentNotifications();
+
+    this._clearPanel();
+
+    this._update();
+  },
+
+  _dismissOrRemoveCurrentNotifications: function() {
     let browser = this.panel.firstChild &&
                   this.panel.firstChild.notification.browser;
     if (!browser)
       return;
 
     let notifications = this._getNotificationsForBrowser(browser);
     // Mark notifications as dismissed and call dismissal callbacks
     Array.forEach(this.panel.childNodes, function (nEl) {
       let notificationObj = nEl.notification;
       // Never call a dismissal handler on a notification that's been removed.
       if (notifications.indexOf(notificationObj) == -1)
         return;
 
       // Do not mark the notification as dismissed or fire NOTIFICATION_EVENT_DISMISSED
       // if the notification is removed.
-      if (notificationObj.options.removeOnDismissal)
+      if (notificationObj.options.removeOnDismissal) {
         this._remove(notificationObj);
-      else {
+      } else {
         notificationObj.dismissed = true;
         this._fireCallback(notificationObj, NOTIFICATION_EVENT_DISMISSED);
       }
     }, this);
-
-    this._clearPanel();
-
-    this._update();
   },
 
   _onButtonCommand: function PopupNotifications_onButtonCommand(event) {
     // Need to find the associated notification object, which is a bit tricky
     // since it isn't associated with the button directly - this is kind of
     // gross and very dependent on the structure of the popupnotification
     // binding's content.
     let target = event.originalTarget;