Bug 624843: PopupNotifications need to work after customizing the location bar, r=dolske, a=blocking
authorGavin Sharp <gavin@gavinsharp.com>
Tue, 18 Jan 2011 00:11:08 -0800
changeset 61088 f9e25d57bb252f1099ed8d3de25520ed0d39e32c
parent 61087 ffb56732bdbeba4566897e7656a99b257749cc17
child 61089 599e9b6cd5e5cf56d3b1858b2cb2cbbcc85c090e
push idunknown
push userunknown
push dateunknown
reviewersdolske, blocking
bugs624843
milestone2.0b10pre
Bug 624843: PopupNotifications need to work after customizing the location bar, r=dolske, a=blocking
browser/base/content/browser.js
browser/base/content/test/Makefile.in
browser/base/content/test/browser_customize_popupNotification.js
browser/base/content/test/browser_popupNotification.js
toolkit/content/PopupNotifications.jsm
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -3537,16 +3537,22 @@ function BrowserToolboxCustomizeDone(aTo
     gProxyFavIcon = document.getElementById("page-proxy-favicon");
     gHomeButton.updateTooltip();
     gIdentityHandler._cacheElements();
     window.XULBrowserWindow.init();
 
 #ifndef XP_MACOSX
     updateEditUIVisibility();
 #endif
+
+    // Hacky: update the PopupNotifications' object's reference to the iconBox,
+    // if it already exists, since it may have changed if the URL bar was
+    // added/removed.
+    if (!__lookupGetter__("PopupNotifications"))
+      PopupNotifications.iconBox = document.getElementById("notification-popup-box");
   }
 
   PlacesToolbarHelper.customizeDone();
   BookmarksMenuButton.customizeDone();
 
   // The url bar splitter state is dependent on whether stop/reload
   // and the location bar are combined, so we need this ordering
   CombinedStopReload.init();
--- a/browser/base/content/test/Makefile.in
+++ b/browser/base/content/test/Makefile.in
@@ -165,16 +165,17 @@ endif
                  browser_bug598923.js \
                  browser_bug599325.js \
                  browser_bug609700.js \
                  browser_bug616836.js \
                  browser_bug623893.js \
                  browser_bug624734.js \
                  browser_contextSearchTabPosition.js \
                  browser_ctrlTab.js \
+                 browser_customize_popupNotification.js \
                  browser_disablechrome.js \
                  browser_discovery.js \
                  browser_duplicateIDs.js \
                  browser_gestureSupport.js \
                  browser_getshortcutoruri.js \
                  browser_hide_removing.js \
                  browser_inspector_initialization.js \
                  browser_inspector_treeSelection.js \
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/browser_customize_popupNotification.js
@@ -0,0 +1,29 @@
+/*
+Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/
+*/
+function test() {
+  waitForExplicitFinish();
+
+  var newWin = openDialog(location, "", "chrome,all,dialog=no", "about:blank");
+  registerCleanupFunction(function () {
+    newWin.close();
+  });
+  newWin.addEventListener("load", function test_win_onLoad() {
+    // Remove the URL bar
+    newWin.gURLBar.parentNode.removeChild(newWin.gURLBar);
+
+    waitForFocus(function () {
+      let PN = newWin.PopupNotifications;
+      try {
+        let notification = PN.show(newWin.gBrowser.selectedBrowser, "some-notification", "Some message");
+        ok(notification, "showed the notification");
+        ok(PN.isPanelOpen, "panel is open");
+        is(PN.panel.anchorNode, newWin.gBrowser.selectedTab, "notification is correctly anchored to the tab");
+      } catch (ex) {
+        ok(false, "threw exception: " + ex);
+      }
+      finish();
+    }, newWin);
+  }, false);
+}
--- a/browser/base/content/test/browser_popupNotification.js
+++ b/browser/base/content/test/browser_popupNotification.js
@@ -53,16 +53,17 @@ function cleanUp() {
   for (var topic in gActiveObservers)
     Services.obs.removeObserver(gActiveObservers[topic], topic);
   for (var eventName in gActiveListeners)
     PopupNotifications.panel.removeEventListener(eventName, gActiveListeners[eventName], false);
 }
 
 var gActiveListeners = {};
 var gActiveObservers = {};
+var gShownState = {};
 
 function runNextTest() {
   let nextTest = tests[gTestIndex];
 
   function goNext() {
     if (++gTestIndex == tests.length)
       executeSoon(finish);
     else
@@ -86,26 +87,32 @@ function runNextTest() {
     addObserver("backgroundShow");
   } else if (nextTest.updateNotShowing) {
     addObserver("updateNotShowing");
   } else {
     doOnPopupEvent("popupshowing", function () {
       info("[Test #" + gTestIndex + "] popup showing");
     });
     doOnPopupEvent("popupshown", function () {
+      gShownState[gTestIndex] = true;
       info("[Test #" + gTestIndex + "] popup shown");
       nextTest.onShown(this);
     });
 
     // We allow multiple onHidden functions to be defined in an array.  They're
     // called in the order they appear.
     let onHiddenArray = nextTest.onHidden instanceof Array ?
                         nextTest.onHidden :
                         [nextTest.onHidden];
     doOnPopupEvent("popuphidden", function () {
+      if (!gShownState[gTestIndex]) {
+        // This is expected to happen for test 9, so let's not treat it as a failure.
+        info("Popup from test " + gTestIndex + " was hidden before its popupshown fired");
+      }
+
       let onHidden = onHiddenArray.shift();
       info("[Test #" + gTestIndex + "] popup hidden (" + onHiddenArray.length + " hides remaining)");
       onHidden.call(nextTest, this);
       if (!onHiddenArray.length)
         goNext();
     }, onHiddenArray.length);
     info("[Test #" + gTestIndex + "] added listeners; panel state: " + PopupNotifications.isPanelOpen);
   }
--- a/toolkit/content/PopupNotifications.jsm
+++ b/toolkit/content/PopupNotifications.jsm
@@ -63,16 +63,19 @@ 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)
+      return null;
+
     let anchorElement = null;
     if (this.anchorID)
       anchorElement = this.owner.iconBox.querySelector("#"+this.anchorID);
 
     if (!anchorElement)
       anchorElement = this.owner.iconBox;
 
     return anchorElement;
@@ -94,49 +97,59 @@ Notification.prototype = {
  *        unhidden when notifications are hidden or shown. It should be the
  *        parent of anchor elements whose IDs are passed to show().
  *        It is used as a fallback popup anchor if notifications specify
  *        invalid or non-existent anchor IDs.
  */
 function PopupNotifications(tabbrowser, panel, iconBox) {
   if (!(tabbrowser instanceof Ci.nsIDOMXULElement))
     throw "Invalid tabbrowser";
-  if (!(iconBox instanceof Ci.nsIDOMXULElement))
+  if (iconBox && !(iconBox instanceof Ci.nsIDOMXULElement))
     throw "Invalid iconBox";
   if (!(panel instanceof Ci.nsIDOMXULElement))
     throw "Invalid panel";
 
   this.window = tabbrowser.ownerDocument.defaultView;
   this.panel = panel;
+  this.tabbrowser = tabbrowser;
+
+  this._onIconBoxCommand = this._onIconBoxCommand.bind(this);
   this.iconBox = iconBox;
-  this.tabbrowser = tabbrowser;
+
+  this.panel.addEventListener("popuphidden", this._onPopupHidden.bind(this), true);
 
   let self = this;
-  this.iconBox.addEventListener("click", function (event) {
-    self._onIconBoxCommand(event);
-  }, false);
-  this.iconBox.addEventListener("keypress", function (event) {
-    self._onIconBoxCommand(event);
-  }, false);
-  this.panel.addEventListener("popuphidden", function (event) {
-    self._onPopupHidden(event);
-  }, true);
-
   function updateFromListeners() {
     // setTimeout(..., 0) needed, otherwise openPopup from "activate" event
     // handler results in the popup being hidden again for some reason...
     self.window.setTimeout(function () {
       self._update();
     }, 0);
   }
   this.window.addEventListener("activate", updateFromListeners, true);
   this.tabbrowser.tabContainer.addEventListener("TabSelect", updateFromListeners, true);
 }
 
 PopupNotifications.prototype = {
+  set iconBox(iconBox) {
+    // Remove the listeners on the old iconBox, if needed
+    if (this._iconBox) {
+      this._iconBox.removeEventListener("click", this._onIconBoxCommand, false);
+      this._iconBox.removeEventListener("keypress", this._onIconBoxCommand, false);
+    }
+    this._iconBox = iconBox;
+    if (iconBox) {
+      iconBox.addEventListener("click", this._onIconBoxCommand, false);
+      iconBox.addEventListener("keypress", this._onIconBoxCommand, false);
+    }
+  },
+  get iconBox() {
+    return this._iconBox;
+  },
+
   /**
    * 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.
    *
@@ -414,22 +427,27 @@ PopupNotifications.prototype = {
     if (this.isPanelOpen && this._currentAnchorElement == anchorElement)
       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, use the tab as the anchor. We only ever
-    // show notifications for the current browser, so we can just use the
-    // current tab.
-    let bo = anchorElement.boxObject;
-    if (bo.height == 0 && bo.width == 0)
-      anchorElement = this.tabbrowser.selectedTab;
+    // 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.panel.openPopup(anchorElement, "bottomcenter topleft");
     notificationsToShow.forEach(function (n) {
       this._fireCallback(n, "shown");
     }, this);
   },
@@ -442,18 +460,20 @@ PopupNotifications.prototype = {
     let anchorElement, notificationsToShow = [];
     let haveNotifications = this._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;
 
-      this.iconBox.hidden = false;
-      this.iconBox.setAttribute("anchorid", anchorElement.id);
+      if (this.iconBox) {
+        this.iconBox.hidden = false;
+        this.iconBox.setAttribute("anchorid", anchorElement.id);
+      }
 
       // Also filter out notifications that have been dismissed.
       notificationsToShow = this._currentNotifications.filter(function (n) {
         return !n.dismissed && n.anchorElement == anchorElement &&
                !n.options.neverShow;
       });
     }