Bug 1300755 - Notification anchors are not hidden when the user types in the location bar. r=past draft
authorPaolo Amadini <paolo.mozmail@amadzone.org>
Thu, 15 Dec 2016 14:39:30 +0100
changeset 449876 01e65782d6cab60482dc269f908227c1c0598063
parent 448717 f46f85dcfbc2b3098ea758825d18be6fab33cbc6
child 539624 f103a578138feb41951630048e37d814323e0d0a
push id38715
push userpaolo.mozmail@amadzone.org
push dateThu, 15 Dec 2016 13:47:55 +0000
reviewerspast
bugs1300755
milestone53.0a1
Bug 1300755 - Notification anchors are not hidden when the user types in the location bar. r=past MozReview-Commit-ID: 5xY87Jnekv7
browser/base/content/browser.css
browser/base/content/browser.js
browser/base/content/test/popupNotifications/browser.ini
browser/base/content/test/popupNotifications/browser_displayURI.js
browser/base/content/test/popupNotifications/browser_popupNotification.js
browser/base/content/test/popupNotifications/browser_popupNotification_2.js
browser/base/content/test/popupNotifications/browser_popupNotification_3.js
browser/base/content/test/popupNotifications/browser_popupNotification_4.js
browser/base/content/test/popupNotifications/browser_popupNotification_5.js
browser/base/content/test/popupNotifications/browser_popupNotification_checkbox.js
browser/base/content/test/popupNotifications/browser_popupNotification_no_anchors.js
browser/base/content/test/popupNotifications/browser_reshow_in_background.js
browser/base/content/test/popupNotifications/head.js
toolkit/modules/PopupNotifications.jsm
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -560,28 +560,26 @@ toolbar:not(#TabsToolbar) > #personal-bo
 
 #urlbar[pageproxystate="invalid"] > #urlbar-icons > .urlbar-icon,
 #urlbar[pageproxystate="invalid"][focused="true"] > #urlbar-go-button ~ toolbarbutton,
 #urlbar[pageproxystate="valid"] > #urlbar-go-button,
 #urlbar:not([focused="true"]) > #urlbar-go-button {
   visibility: collapse;
 }
 
+#urlbar[pageproxystate="invalid"] > #identity-box > #blocked-permissions-container,
+#urlbar[pageproxystate="invalid"] > #identity-box > #notification-popup-box,
 #urlbar[pageproxystate="invalid"] > #identity-box > #identity-icon-labels {
   visibility: collapse;
 }
 
 #urlbar[pageproxystate="invalid"] > #identity-box {
   pointer-events: none;
 }
 
-#urlbar[pageproxystate="invalid"] > #identity-box > #notification-popup-box {
-  pointer-events: auto;
-}
-
 #identity-icon-labels {
   max-width: 18em;
 }
 @media (max-width: 700px) {
   #urlbar-container {
     min-width: 45ch;
   }
   #identity-icon-labels {
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2363,17 +2363,32 @@ function BrowserPageInfo(documentURL, in
     }
   }
 
   // We didn't find a matching window, so open a new one.
   return openDialog("chrome://browser/content/pageinfo/pageInfo.xul", "",
                     "chrome,toolbar,dialog=no,resizable", args);
 }
 
-function URLBarSetURI(aURI) {
+/**
+ * Sets the URI to display in the location bar.
+ *
+ * @param aURI [optional]
+ *        nsIURI to set. If this is unspecified, the current URI will be used.
+ * @param aOptions [optional]
+ *        An object with the following properties:
+ *        {
+ *          isForLocationChange:
+ *            Set to true to indicate that the function was invoked to respond
+ *            to a location change event, rather than to reset the current URI
+ *            value. This is useful to avoid calling PopupNotifications.jsm
+ *            multiple times.
+ *        }
+ */
+function URLBarSetURI(aURI, aOptions = {}) {
   var value = gBrowser.userTypedValue;
   var valid = false;
 
   if (value == null) {
     let uri = aURI || gBrowser.currentURI;
     // Strip off "wyciwyg://" and passwords for the location bar
     try {
       uri = Services.uriFixup.createExposableURI(uri);
@@ -2396,17 +2411,17 @@ function URLBarSetURI(aURI) {
       }
     }
 
     valid = !isBlankPageURL(uri.spec);
   }
 
   gURLBar.value = value;
   gURLBar.valueIsTyped = !valid;
-  SetPageProxyState(valid ? "valid" : "invalid");
+  SetPageProxyState(valid ? "valid" : "invalid", aOptions);
 }
 
 function losslessDecodeURI(aURI) {
   let scheme = aURI.scheme;
   if (scheme == "moz-action")
     throw new Error("losslessDecodeURI should never get a moz-action URI");
 
   var value = aURI.spec;
@@ -2495,31 +2510,59 @@ function UpdateUrlbarSearchSplitterState
 }
 
 function UpdatePageProxyState()
 {
   if (gURLBar && gURLBar.value != gLastValidURLStr)
     SetPageProxyState("invalid");
 }
 
-function SetPageProxyState(aState)
+/**
+ * Updates the user interface to indicate whether the URI in the location bar is
+ * different than the loaded page, because it's being edited or because a search
+ * result is currently selected and is displayed in the location bar.
+ *
+ * @param aState
+ *        The string "valid" indicates that the security indicators and other
+ *        related user interface elments should be shown because the URI in the
+ *        location bar matches the loaded page. The string "invalid" indicates
+ *        that the URI in the location bar is different than the loaded page.
+ * @param aOptions [optional]
+ *        An object with the following properties:
+ *        {
+ *          isForLocationChange:
+ *            Set to true to indicate that the function was invoked to respond
+ *            to a location change event. This is useful to avoid calling
+ *            PopupNotifications.jsm multiple times.
+ *        }
+ */
+function SetPageProxyState(aState, aOptions = {})
 {
   if (!gURLBar)
     return;
 
   gURLBar.setAttribute("pageproxystate", aState);
 
   // the page proxy state is set to valid via OnLocationChange, which
   // gets called when we switch tabs.
   if (aState == "valid") {
     gLastValidURLStr = gURLBar.value;
     gURLBar.addEventListener("input", UpdatePageProxyState, false);
   } else if (aState == "invalid") {
     gURLBar.removeEventListener("input", UpdatePageProxyState, false);
   }
+
+  // Only need to call anchorVisibilityChange if the PopupNotifications object
+  // for this window has already been initialized (i.e. its getter no
+  // longer exists). If this is the result of a locations change, then we will
+  // already invoke PopupNotifications.locationChange separately.
+  if (!Object.getOwnPropertyDescriptor(window, "PopupNotifications").get &&
+      !aOptions.isForLocationChange) {
+    PopupNotifications.anchorVisibilityChange();
+  }
 }
 
 function PageProxyClickHandler(aEvent)
 {
   if (aEvent.button == 1 && gPrefService.getBoolPref("middlemouse.paste"))
     middleMousePaste(aEvent);
 }
 
@@ -4503,17 +4546,17 @@ var XULBrowserWindow = {
       if ((location == "about:blank" && checkEmptyPageOrigin()) ||
           location == "") {  // Second condition is for new tabs, otherwise
                              // reload function is enabled until tab is refreshed.
         this.reloadCommand.setAttribute("disabled", "true");
       } else {
         this.reloadCommand.removeAttribute("disabled");
       }
 
-      URLBarSetURI(aLocationURI);
+      URLBarSetURI(aLocationURI, { isForLocationChange: true });
 
       BookmarkingUI.onLocationChange();
 
       gIdentityHandler.onLocationChange();
 
       SocialUI.updateState();
 
       UITour.onLocationChange(location);
--- a/browser/base/content/test/popupNotifications/browser.ini
+++ b/browser/base/content/test/popupNotifications/browser.ini
@@ -11,10 +11,12 @@ skip-if = (os == "linux" && (debug || as
 [browser_popupNotification_3.js]
 skip-if = (os == "linux" && (debug || asan))
 [browser_popupNotification_4.js]
 skip-if = (os == "linux" && (debug || asan))
 [browser_popupNotification_5.js]
 skip-if = (os == "linux" && (debug || asan))
 [browser_popupNotification_checkbox.js]
 skip-if = (os == "linux" && (debug || asan))
+[browser_popupNotification_no_anchors.js]
+skip-if = (os == "linux" && (debug || asan))
 [browser_reshow_in_background.js]
 skip-if = (os == "linux" && (debug || asan))
--- a/browser/base/content/test/popupNotifications/browser_displayURI.js
+++ b/browser/base/content/test/popupNotifications/browser_displayURI.js
@@ -3,21 +3,17 @@
  * consumers e.g. geolocation.
 */
 
 add_task(function* test_displayURI() {
   yield BrowserTestUtils.withNewTab({
     gBrowser,
     url: "https://test1.example.com/",
   }, function*(browser) {
-    let popupShownPromise = new Promise((resolve, reject) => {
-      onPopupEvent("popupshown", function() {
-        resolve(this);
-      });
-    });
+    let popupShownPromise = waitForNotificationPanel();
     yield ContentTask.spawn(browser, null, function*() {
       content.navigator.geolocation.getCurrentPosition(function(pos) {
         // Do nothing
       });
     });
     let panel = yield popupShownPromise;
     let notification = panel.children[0];
     let body = document.getAnonymousElementByAttribute(notification,
--- a/browser/base/content/test/popupNotifications/browser_popupNotification.js
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification.js
@@ -8,17 +8,16 @@ var wrongBrowserNotification;
 
 function test() {
   waitForExplicitFinish();
 
   ok(PopupNotifications, "PopupNotifications object exists");
   ok(PopupNotifications.panel, "PopupNotifications panel exists");
 
   setup();
-  goNext();
 }
 
 var tests = [
   { id: "Test#1",
     run: function() {
       this.notifyObj = new BasicNotification(this.id);
       showNotification(this.notifyObj);
     },
@@ -105,17 +104,18 @@ var tests = [
       this.notification.remove();
       ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
     }
   },
   // test opening a notification for a background browser
   // Note: test 4 to 6 share a tab.
   { id: "Test#4",
     run: function* () {
-      let tab = gBrowser.addTab("about:blank");
+      let tab = gBrowser.addTab("http://example.com/");
+      yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
       isnot(gBrowser.selectedTab, tab, "new tab isn't selected");
       wrongBrowserNotificationObject.browser = gBrowser.getBrowserForTab(tab);
       let promiseTopic = promiseTopicObserved("PopupNotifications-backgroundShow");
       wrongBrowserNotification = showNotification(wrongBrowserNotificationObject);
       yield promiseTopic;
       is(PopupNotifications.isPanelOpen, false, "panel isn't open");
       ok(!wrongBrowserNotificationObject.mainActionClicked, "main action wasn't clicked");
       ok(!wrongBrowserNotificationObject.secondaryActionClicked, "secondary action wasn't clicked");
--- a/browser/base/content/test/popupNotifications/browser_popupNotification_2.js
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_2.js
@@ -4,17 +4,16 @@
 
 function test() {
   waitForExplicitFinish();
 
   ok(PopupNotifications, "PopupNotifications object exists");
   ok(PopupNotifications.panel, "PopupNotifications panel exists");
 
   setup();
-  goNext();
 }
 
 var tests = [
   // Test optional params
   { id: "Test#1",
     run: function() {
       this.notifyObj = new BasicNotification(this.id);
       this.notifyObj.secondaryActions = undefined;
@@ -53,45 +52,43 @@ var tests = [
          "geo anchor should not be visible after removal");
     }
   },
 
   // Test that persistence allows the notification to persist across reloads
   { id: "Test#3",
     run: function* () {
       this.oldSelectedTab = gBrowser.selectedTab;
-      gBrowser.selectedTab = gBrowser.addTab("about:blank");
-      yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
+      yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
       this.notifyObj = new BasicNotification(this.id);
       this.notifyObj.addOptions({
         persistence: 2
       });
       this.notification = showNotification(this.notifyObj);
     },
     onShown: function* (popup) {
       this.complete = false;
       yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.org/");
-      yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/")
+      yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
       // Next load will remove the notification
       this.complete = true;
       yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.org/");
     },
     onHidden: function(popup) {
       ok(this.complete, "Should only have hidden the notification after 3 page loads");
       ok(this.notifyObj.removedCallbackTriggered, "removal callback triggered");
       gBrowser.removeTab(gBrowser.selectedTab);
       gBrowser.selectedTab = this.oldSelectedTab;
     }
   },
   // Test that a timeout allows the notification to persist across reloads
   { id: "Test#4",
     run: function* () {
       this.oldSelectedTab = gBrowser.selectedTab;
-      gBrowser.selectedTab = gBrowser.addTab("about:blank");
-      yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
+      yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
       this.notifyObj = new BasicNotification(this.id);
       // Set a timeout of 10 minutes that should never be hit
       this.notifyObj.addOptions({
         timeout: Date.now() + 600000
       });
       this.notification = showNotification(this.notifyObj);
     },
     onShown: function* (popup) {
@@ -110,18 +107,17 @@ var tests = [
       gBrowser.selectedTab = this.oldSelectedTab;
     }
   },
   // Test that setting persistWhileVisible allows a visible notification to
   // persist across location changes
   { id: "Test#5",
     run: function* () {
       this.oldSelectedTab = gBrowser.selectedTab;
-      gBrowser.selectedTab = gBrowser.addTab("about:blank");
-      yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
+      yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
       this.notifyObj = new BasicNotification(this.id);
       this.notifyObj.addOptions({
         persistWhileVisible: true
       });
       this.notification = showNotification(this.notifyObj);
     },
     onShown: function* (popup) {
       this.complete = false;
@@ -218,33 +214,9 @@ var tests = [
     },
     onHidden: function(popup) {
       ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered");
       this.notification.remove();
       ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
       window.locationbar.visible = true;
     }
   },
-  // Test that dismissed popupnotifications can be opened on about:blank
-  // (where the rest of the identity block is disabled)
-  { id: "Test#11",
-    run: function() {
-      this.oldSelectedTab = gBrowser.selectedTab;
-      gBrowser.selectedTab = gBrowser.addTab("about:blank");
-
-      this.notifyObj = new BasicNotification(this.id);
-      this.notifyObj.anchorID = "geo-notification-icon";
-      this.notifyObj.addOptions({dismissed: true});
-      this.notification = showNotification(this.notifyObj);
-
-      EventUtils.synthesizeMouse(document.getElementById("geo-notification-icon"), 0, 0, {});
-    },
-    onShown: function(popup) {
-      checkPopup(popup, this.notifyObj);
-      dismissNotification(popup);
-    },
-    onHidden: function(popup) {
-      this.notification.remove();
-      gBrowser.removeTab(gBrowser.selectedTab);
-      gBrowser.selectedTab = this.oldSelectedTab;
-    }
-  }
 ];
--- a/browser/base/content/test/popupNotifications/browser_popupNotification_3.js
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_3.js
@@ -4,17 +4,16 @@
 
 function test() {
   waitForExplicitFinish();
 
   ok(PopupNotifications, "PopupNotifications object exists");
   ok(PopupNotifications.panel, "PopupNotifications panel exists");
 
   setup();
-  goNext();
 }
 
 var tests = [
   // Test notification is removed when dismissed if removeOnDismissal is true
   { id: "Test#1",
     run: function() {
       this.notifyObj = new BasicNotification(this.id);
       this.notifyObj.addOptions({
@@ -61,25 +60,25 @@ var tests = [
       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
   { id: "Test#3",
-    run: function() {
+    run: function* () {
       // show the notification on old tab.
       this.notifyObjOld = new BasicNotification(this.id);
       this.notifyObjOld.anchorID = "default-notification-icon";
       this.notificationOld = showNotification(this.notifyObjOld);
 
       // switch tab
       this.oldSelectedTab = gBrowser.selectedTab;
-      gBrowser.selectedTab = gBrowser.addTab("about:blank");
+      yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
 
       // show the notification on new tab.
       this.notifyObjNew = new BasicNotification(this.id);
       this.notifyObjNew.anchorID = "geo-notification-icon";
       this.notificationNew = showNotification(this.notifyObjNew);
     },
     onShown: function(popup) {
       checkPopup(popup, this.notifyObjNew);
@@ -165,20 +164,17 @@ var tests = [
         gBrowser.selectedBrowser.reload();
       });
     }
   },
   // location change in background tab removes notification
   { id: "Test#7",
     run: function* () {
       let oldSelectedTab = gBrowser.selectedTab;
-      let newTab = gBrowser.addTab("about:blank");
-      gBrowser.selectedTab = newTab;
-
-      yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
+      let newTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
       gBrowser.selectedTab = oldSelectedTab;
       let browser = gBrowser.getBrowserForTab(newTab);
 
       let notifyObj = new BasicNotification(this.id);
       notifyObj.browser = browser;
       notifyObj.options.eventCallback = function(eventName) {
         if (eventName == "removed") {
           ok(true, "Notification removed in background tab after reloading");
@@ -194,19 +190,17 @@ var tests = [
       });
     }
   },
   // Popup notification anchor shouldn't disappear when a notification with the same ID is re-added in a background tab
   { id: "Test#8",
     run: function* () {
       yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
       let originalTab = gBrowser.selectedTab;
-      let bgTab = gBrowser.addTab("about:blank");
-      gBrowser.selectedTab = bgTab;
-      yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
+      let bgTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
       let anchor = document.createElement("box");
       anchor.id = "test26-anchor";
       anchor.className = "notification-anchor-icon";
       PopupNotifications.iconBox.appendChild(anchor);
 
       gBrowser.selectedTab = originalTab;
 
       let fgNotifyObj = new BasicNotification(this.id);
--- a/browser/base/content/test/popupNotifications/browser_popupNotification_4.js
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_4.js
@@ -4,38 +4,37 @@
 
 function test() {
   waitForExplicitFinish();
 
   ok(PopupNotifications, "PopupNotifications object exists");
   ok(PopupNotifications.panel, "PopupNotifications panel exists");
 
   setup();
-  goNext();
 }
 
 var tests = [
   // Popup Notifications main actions should catch exceptions from callbacks
   { id: "Test#1",
     run: function() {
-      this.testNotif = new ErrorNotification();
+      this.testNotif = new ErrorNotification(this.id);
       showNotification(this.testNotif);
     },
     onShown: function(popup) {
       checkPopup(popup, this.testNotif);
       triggerMainCommand(popup);
     },
     onHidden: function(popup) {
       ok(this.testNotif.mainActionClicked, "main action has been triggered");
     }
   },
   // Popup Notifications secondary actions should catch exceptions from callbacks
   { id: "Test#2",
     run: function() {
-      this.testNotif = new ErrorNotification();
+      this.testNotif = new ErrorNotification(this.id);
       showNotification(this.testNotif);
     },
     onShown: function(popup) {
       checkPopup(popup, this.testNotif);
       triggerSecondaryCommand(popup, 0);
     },
     onHidden: function(popup) {
       ok(this.testNotif.secondaryActionClicked, "secondary action has been triggered");
@@ -92,75 +91,79 @@ var tests = [
       // which could be a problem if we ever decided to deep-copy.
       checkPopup(popup, this.notifyObj);
       triggerMainCommand(popup);
     },
     onHidden: function() { }
   },
   // Moving a tab to a new window should remove non-swappable notifications.
   { id: "Test#5",
-    run: function() {
-      gBrowser.selectedTab = gBrowser.addTab("about:blank");
+    run: function* () {
+      yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
+
       let notifyObj = new BasicNotification(this.id);
+
+      let shown = waitForNotificationPanel();
       showNotification(notifyObj);
-      let win = gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
-      whenDelayedStartupFinished(win, function() {
-        let anchor = win.document.getElementById("default-notification-icon");
-        win.PopupNotifications._reshowNotifications(anchor);
-        ok(win.PopupNotifications.panel.childNodes.length == 0,
-           "no notification displayed in new window");
-        ok(notifyObj.swappingCallbackTriggered, "the swapping callback was triggered");
-        ok(notifyObj.removedCallbackTriggered, "the removed callback was triggered");
-        win.close();
-        goNext();
-      });
+      yield shown;
+
+      let promiseWin = BrowserTestUtils.waitForNewWindow();
+      gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
+      let win = yield promiseWin;
+
+      let anchor = win.document.getElementById("default-notification-icon");
+      win.PopupNotifications._reshowNotifications(anchor);
+      ok(win.PopupNotifications.panel.childNodes.length == 0,
+         "no notification displayed in new window");
+      ok(notifyObj.swappingCallbackTriggered, "the swapping callback was triggered");
+      ok(notifyObj.removedCallbackTriggered, "the removed callback was triggered");
+
+      yield BrowserTestUtils.closeWindow(win);
+      yield waitForWindowReadyForPopupNotifications(window);
+
+      goNext();
     }
   },
   // Moving a tab to a new window should preserve swappable notifications.
   { id: "Test#6",
     run: function* () {
-      let originalBrowser = gBrowser.selectedBrowser;
-      let originalWindow = window;
-
-      gBrowser.selectedTab = gBrowser.addTab("about:blank");
+      yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
       let notifyObj = new BasicNotification(this.id);
       let originalCallback = notifyObj.options.eventCallback;
       notifyObj.options.eventCallback = function(eventName) {
         originalCallback(eventName);
         return eventName == "swapping";
       };
 
+      let shown = waitForNotificationPanel();
       let notification = showNotification(notifyObj);
-      let win = gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
-      yield whenDelayedStartupFinished(win);
+      yield shown;
+
+      let promiseWin = BrowserTestUtils.waitForNewWindow();
+      gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
+      let win = yield promiseWin;
+      yield waitForWindowReadyForPopupNotifications(win);
 
       yield new Promise(resolve => {
         let callback = notification.options.eventCallback;
         notification.options.eventCallback = function(eventName) {
           callback(eventName);
           if (eventName == "shown") {
             resolve();
           }
         };
         info("Showing the notification again");
         notification.reshow();
       });
 
       checkPopup(win.PopupNotifications.panel, notifyObj);
       ok(notifyObj.swappingCallbackTriggered, "the swapping callback was triggered");
+
       yield BrowserTestUtils.closeWindow(win);
-
-      // These are the same checks that PopupNotifications.jsm makes before it
-      // allows a notification to open. Do not go to the next test until we are
-      // sure that its attempt to display a notification will not fail.
-      yield BrowserTestUtils.waitForCondition(() => originalBrowser.docShellIsActive,
-                                              "The browser should be active");
-      let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
-      yield BrowserTestUtils.waitForCondition(() => fm.activeWindow == originalWindow,
-                                              "The window should be active")
+      yield waitForWindowReadyForPopupNotifications(window);
 
       goNext();
     }
   },
   // the main action callback can keep the notification.
   { id: "Test#8",
     run: function() {
       this.notifyObj = new BasicNotification(this.id);
--- a/browser/base/content/test/popupNotifications/browser_popupNotification_5.js
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_5.js
@@ -4,17 +4,16 @@
 
 function test() {
   waitForExplicitFinish();
 
   ok(PopupNotifications, "PopupNotifications object exists");
   ok(PopupNotifications.panel, "PopupNotifications panel exists");
 
   setup();
-  goNext();
 }
 
 var gNotification;
 
 var tests = [
   // panel updates should fire the showing and shown callbacks again.
   { id: "Test#1",
     run: function() {
@@ -60,36 +59,39 @@ var tests = [
       checkPopup(popup, this.notifyObj2);
       this.notification1.remove();
       this.notification2.remove();
     },
     onHidden: function(popup) { }
   },
   // The anchor icon should be shown for notifications in background windows.
   { id: "Test#3",
-    run: function() {
+    run: function* () {
       let notifyObj = new BasicNotification(this.id);
       notifyObj.options.dismissed = true;
-      let win = gBrowser.replaceTabWithWindow(gBrowser.addTab("about:blank"));
-      whenDelayedStartupFinished(win, function() {
-        showNotification(notifyObj);
-        let anchor = document.getElementById("default-notification-icon");
-        is(anchor.getAttribute("showing"), "true", "the anchor is shown");
-        win.close();
-        goNext();
-      });
+
+      let win = yield BrowserTestUtils.openNewBrowserWindow();
+
+      // Open the notification in the original window, now in the background.
+      showNotification(notifyObj);
+      let anchor = document.getElementById("default-notification-icon");
+      is(anchor.getAttribute("showing"), "true", "the anchor is shown");
+
+      yield BrowserTestUtils.closeWindow(win);
+      yield waitForWindowReadyForPopupNotifications(window);
+
+      goNext();
     }
   },
   // Test that persistent doesn't allow the notification to persist after
   // navigation.
   { id: "Test#4",
     run: function* () {
       this.oldSelectedTab = gBrowser.selectedTab;
-      gBrowser.selectedTab = gBrowser.addTab("about:blank");
-      yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
+      yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
       this.notifyObj = new BasicNotification(this.id);
       this.notifyObj.addOptions({
         persistent: true
       });
       this.notification = showNotification(this.notifyObj);
     },
     onShown: function* (popup) {
       this.complete = false;
@@ -110,18 +112,17 @@ var tests = [
       gBrowser.selectedTab = this.oldSelectedTab;
     }
   },
   // Test that persistent allows the notification to persist until explicitly
   // dismissed.
   { id: "Test#5",
     run: function* () {
       this.oldSelectedTab = gBrowser.selectedTab;
-      gBrowser.selectedTab = gBrowser.addTab("about:blank");
-      yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
+      yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
       this.notifyObj = new BasicNotification(this.id);
       this.notifyObj.addOptions({
         persistent: true
       });
       this.notification = showNotification(this.notifyObj);
     },
     onShown: function* (popup) {
       this.complete = false;
@@ -147,19 +148,17 @@ var tests = [
   { id: "Test#6a",
     run: function* () {
       this.notifyObj = new BasicNotification(this.id);
       this.notifyObj.options.persistent = true;
       gNotification = showNotification(this.notifyObj);
     },
     onShown: function* (popup) {
       this.oldSelectedTab = gBrowser.selectedTab;
-      gBrowser.selectedTab = gBrowser.addTab("about:blank");
-      info("Waiting for the new tab to load.");
-      yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
+      yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
     },
     onHidden: function(popup) {
       ok(true, "Should have hidden the notification after tab switch");
       gBrowser.removeTab(gBrowser.selectedTab);
       gBrowser.selectedTab = this.oldSelectedTab;
     }
   },
   // Second part of the previous test that compensates for the limitation in
@@ -174,37 +173,47 @@ var tests = [
       goNext();
     }
   },
   // Test that persistent panels are still open after switching to another
   // window and back.
   { id: "Test#7",
     run: function* () {
       this.oldSelectedTab = gBrowser.selectedTab;
-      gBrowser.selectedTab = gBrowser.addTab("about:blank");
+      yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
+
+      let shown = waitForNotificationPanel();
       let notifyObj = new BasicNotification(this.id);
       notifyObj.options.persistent = true;
       this.notification = showNotification(notifyObj);
-      let win = gBrowser.replaceTabWithWindow(gBrowser.addTab("about:blank"));
-      whenDelayedStartupFinished(win, () => {
-        ok(notifyObj.shownCallbackTriggered, "Should have triggered the shown callback");
-        let anchor = win.document.getElementById("default-notification-icon");
-        win.PopupNotifications._reshowNotifications(anchor);
-        ok(win.PopupNotifications.panel.childNodes.length == 0,
-           "no notification displayed in new window");
-        ok(PopupNotifications.isPanelOpen, "Should be still showing the popup in the first window");
-        win.close();
-        let id = PopupNotifications.panel.firstChild.getAttribute("popupid");
-        ok(id.endsWith("Test#7"), "Should have found the notification from Test7");
-        ok(PopupNotifications.isPanelOpen, "Should have shown the popup again after getting back to the window");
-        this.notification.remove();
-        gBrowser.removeTab(gBrowser.selectedTab);
-        gBrowser.selectedTab = this.oldSelectedTab;
-        goNext();
-      });
+      yield shown;
+
+      ok(notifyObj.shownCallbackTriggered, "Should have triggered the shown callback");
+
+      yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
+      let promiseWin = BrowserTestUtils.waitForNewWindow();
+      gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
+      let win = yield promiseWin;
+
+      let anchor = win.document.getElementById("default-notification-icon");
+      win.PopupNotifications._reshowNotifications(anchor);
+      ok(win.PopupNotifications.panel.childNodes.length == 0,
+         "no notification displayed in new window");
+
+      yield BrowserTestUtils.closeWindow(win);
+      yield waitForWindowReadyForPopupNotifications(window);
+
+      let id = PopupNotifications.panel.firstChild.getAttribute("popupid");
+      ok(id.endsWith("Test#7"), "Should have found the notification from Test7");
+      ok(PopupNotifications.isPanelOpen, "Should have shown the popup again after getting back to the window");
+      this.notification.remove();
+      gBrowser.removeTab(gBrowser.selectedTab);
+      gBrowser.selectedTab = this.oldSelectedTab;
+
+      goNext();
     }
   },
   // Test that only the first persistent notification is shown on update
   { id: "Test#8",
     run: function() {
       this.notifyObj1 = new BasicNotification(this.id);
       this.notifyObj1.id += "_1";
       this.notifyObj1.anchorID = "default-notification-icon";
--- a/browser/base/content/test/popupNotifications/browser_popupNotification_checkbox.js
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_checkbox.js
@@ -4,17 +4,16 @@
 
 function test() {
   waitForExplicitFinish();
 
   ok(PopupNotifications, "PopupNotifications object exists");
   ok(PopupNotifications.panel, "PopupNotifications panel exists");
 
   setup();
-  goNext();
 }
 
 function checkCheckbox(checkbox, label, checked = false, hidden = false) {
   is(checkbox.label, label, "Checkbox should have the correct label");
   is(checkbox.hidden, hidden, "Checkbox should be shown");
   is(checkbox.checked, checked, "Checkbox should be checked by default");
 }
 
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_no_anchors.js
@@ -0,0 +1,153 @@
+/* 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/. */
+
+function test() {
+  waitForExplicitFinish();
+
+  ok(PopupNotifications, "PopupNotifications object exists");
+  ok(PopupNotifications.panel, "PopupNotifications panel exists");
+
+  setup();
+}
+
+var tests = [
+  // Test that popupnotifications are anchored to the identity icon on
+  // about:blank, where anchor icons are hidden.
+  { id: "Test#1",
+    run: function* () {
+      this.oldSelectedTab = gBrowser.selectedTab;
+      yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
+
+      this.notifyObj = new BasicNotification(this.id);
+      this.notifyObj.anchorID = "geo-notification-icon";
+      this.notification = showNotification(this.notifyObj);
+    },
+    onShown: function(popup) {
+      checkPopup(popup, this.notifyObj);
+      is(document.getElementById("geo-notification-icon").boxObject.width, 0,
+         "geo anchor shouldn't be visible");
+      is(popup.anchorNode.id, "identity-icon",
+         "notification anchored to identity icon");
+      dismissNotification(popup);
+    },
+    onHidden: function(popup) {
+      this.notification.remove();
+      gBrowser.removeTab(gBrowser.selectedTab);
+      gBrowser.selectedTab = this.oldSelectedTab;
+    }
+  },
+  // Test that popupnotifications are anchored to the identity icon after
+  // navigation to about:blank.
+  { id: "Test#2",
+    run: function* () {
+      this.oldSelectedTab = gBrowser.selectedTab;
+      yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
+
+      this.notifyObj = new BasicNotification(this.id);
+      this.notifyObj.anchorID = "geo-notification-icon";
+      this.notifyObj.addOptions({
+        persistence: 1,
+      });
+      this.notification = showNotification(this.notifyObj);
+    },
+    onShown: function* (popup) {
+      yield promiseTabLoadEvent(gBrowser.selectedTab, "about:blank");
+
+      checkPopup(popup, this.notifyObj);
+      is(document.getElementById("geo-notification-icon").boxObject.width, 0,
+         "geo anchor shouldn't be visible");
+      is(popup.anchorNode.id, "identity-icon",
+         "notification anchored to identity icon");
+      dismissNotification(popup);
+    },
+    onHidden: function(popup) {
+      this.notification.remove();
+      gBrowser.removeTab(gBrowser.selectedTab);
+      gBrowser.selectedTab = this.oldSelectedTab;
+    }
+  },
+  // Test that dismissed popupnotifications cannot be opened on about:blank, but
+  // can be opened after navigation.
+  { id: "Test#3",
+    run: function* () {
+      this.oldSelectedTab = gBrowser.selectedTab;
+      yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
+
+      this.notifyObj = new BasicNotification(this.id);
+      this.notifyObj.anchorID = "geo-notification-icon";
+      this.notifyObj.addOptions({
+        dismissed: true,
+        persistence: 1,
+      });
+      this.notification = showNotification(this.notifyObj);
+
+      is(document.getElementById("geo-notification-icon").boxObject.width, 0,
+         "geo anchor shouldn't be visible");
+
+      yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
+
+      isnot(document.getElementById("geo-notification-icon").boxObject.width, 0,
+            "geo anchor should be visible");
+
+      EventUtils.synthesizeMouse(document.getElementById("geo-notification-icon"), 0, 0, {});
+    },
+    onShown: function(popup) {
+      checkPopup(popup, this.notifyObj);
+      dismissNotification(popup);
+    },
+    onHidden: function(popup) {
+      this.notification.remove();
+      gBrowser.removeTab(gBrowser.selectedTab);
+      gBrowser.selectedTab = this.oldSelectedTab;
+    }
+  },
+  // Test that popupnotifications are anchored to the identity icon while
+  // editing the URL in the location bar, and restored to their anchors when the
+  // URL is reverted.
+  { id: "Test#4",
+    run: function* () {
+      this.oldSelectedTab = gBrowser.selectedTab;
+      yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
+
+      let shownInitially = waitForNotificationPanel();
+      this.notifyObj = new BasicNotification(this.id);
+      this.notifyObj.anchorID = "geo-notification-icon";
+      this.notifyObj.addOptions({
+        persistent: true,
+      });
+      this.notification = showNotification(this.notifyObj);
+      yield shownInitially;
+
+      checkPopup(PopupNotifications.panel, this.notifyObj);
+
+      let shownAgain = waitForNotificationPanel();
+      // This will cause the popup to hide and show again.
+      gURLBar.select();
+      EventUtils.synthesizeKey("*", {});
+      // Keep the URL bar empty, so we don't show the awesomebar.
+      EventUtils.synthesizeKey("VK_BACK_SPACE", {});
+      yield shownAgain;
+
+      is(document.getElementById("geo-notification-icon").boxObject.width, 0,
+         "geo anchor shouldn't be visible");
+      is(PopupNotifications.panel.anchorNode.id, "identity-icon",
+         "notification anchored to identity icon");
+
+      let shownLastTime = waitForNotificationPanel();
+      // This will cause the popup to hide and show again.
+      EventUtils.synthesizeKey("VK_ESCAPE", {});
+      yield shownLastTime;
+
+      checkPopup(PopupNotifications.panel, this.notifyObj);
+
+      let hidden = new Promise(resolve => onPopupEvent("popuphidden", resolve));
+      this.notification.remove();
+      gBrowser.removeTab(gBrowser.selectedTab);
+      gBrowser.selectedTab = this.oldSelectedTab;
+      yield hidden;
+
+      goNext();
+    }
+  },
+];
--- a/browser/base/content/test/popupNotifications/browser_reshow_in_background.js
+++ b/browser/base/content/test/popupNotifications/browser_reshow_in_background.js
@@ -4,18 +4,21 @@
  * Tests that when PopupNotifications for background tabs are reshown, they
  * don't show up in the foreground tab, but only in the background tab that
  * they belong to.
  */
 add_task(function* test_background_notifications_dont_reshow_in_foreground() {
   // Our initial tab will be A. Let's open two more tabs B and C, but keep
   // A selected. Then, we'll trigger a PopupNotification in C, and then make
   // it reshow.
-  let tabB = gBrowser.addTab("about:blank");
-  let tabC = gBrowser.addTab("about:blank");
+  let tabB = gBrowser.addTab("http://example.com/");
+  yield BrowserTestUtils.browserLoaded(tabB.linkedBrowser);
+
+  let tabC = gBrowser.addTab("http://example.com/");
+  yield BrowserTestUtils.browserLoaded(tabC.linkedBrowser);
 
   let seenEvents = [];
 
   let options = {
     dismissed: false,
     eventCallback(popupEvent) {
       seenEvents.push(popupEvent);
     },
--- a/browser/base/content/test/popupNotifications/head.js
+++ b/browser/base/content/test/popupNotifications/head.js
@@ -2,31 +2,16 @@ Components.utils.import("resource://gre/
 
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
   "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
   "resource://gre/modules/PlacesUtils.jsm");
 
-function whenDelayedStartupFinished(aWindow, aCallback) {
-  return new Promise(resolve => {
-    info("Waiting for delayed startup to finish");
-    Services.obs.addObserver(function observer(aSubject, aTopic) {
-      if (aWindow == aSubject) {
-        Services.obs.removeObserver(observer, aTopic);
-        if (aCallback) {
-          executeSoon(aCallback);
-        }
-        resolve();
-      }
-    }, "browser-delayed-startup-finished", false);
-  });
-}
-
 /**
  * Allows waiting for an observer notification once.
  *
  * @param topic
  *        Notification topic to observe.
  *
  * @return {Promise}
  * @resolves The array [subject, data] from the observed notification.
@@ -38,16 +23,33 @@ function promiseTopicObserved(topic)
   info("Waiting for observer topic " + topic);
   Services.obs.addObserver(function PTO_observe(obsSubject, obsTopic, obsData) {
     Services.obs.removeObserver(PTO_observe, obsTopic);
     deferred.resolve([obsSubject, obsData]);
   }, topic, false);
   return deferred.promise;
 }
 
+/**
+ * Called after opening a new window or switching windows, this will wait until
+ * we are sure that an attempt to display a notification will not fail.
+ */
+function* waitForWindowReadyForPopupNotifications(win)
+{
+  // These are the same checks that PopupNotifications.jsm makes before it
+  // allows a notification to open.
+  yield BrowserTestUtils.waitForCondition(
+    () => win.gBrowser.selectedBrowser.docShellIsActive,
+    "The browser should be active"
+  );
+  yield BrowserTestUtils.waitForCondition(
+    () => Services.focus.activeWindow == win,
+    "The window should be active"
+  );
+}
 
 /**
  * Waits for a load (or custom) event to finish in a given tab. If provided
  * load an uri into the tab.
  *
  * @param tab
  *        The tab to load into.
  * @param [optional] url
@@ -65,20 +67,20 @@ function promiseTabLoadEvent(tab, url)
   }
 
   return BrowserTestUtils.browserLoaded(browser, false, url);
 }
 
 const PREF_SECURITY_DELAY_INITIAL = Services.prefs.getIntPref("security.notification_enable_delay");
 
 function setup() {
-  // Disable transitions as they slow the test down and we want to click the
-  // mouse buttons in a predictable location.
-
+  BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/")
+                  .then(goNext);
   registerCleanupFunction(() => {
+    gBrowser.removeTab(gBrowser.selectedTab);
     PopupNotifications.buttonDelay = PREF_SECURITY_DELAY_INITIAL;
   });
 }
 
 function goNext() {
   executeSoon(() => executeSoon(Task.async(runNextTest)));
 }
 
@@ -168,29 +170,29 @@ function BasicNotification(testId) {
   };
 }
 
 BasicNotification.prototype.addOptions = function(options) {
   for (let [name, value] of Object.entries(options))
     this.options[name] = value;
 };
 
-function ErrorNotification() {
+function ErrorNotification(testId) {
+  BasicNotification.call(this, testId);
   this.mainAction.callback = () => {
     this.mainActionClicked = true;
     throw new Error("Oops!");
   };
   this.secondaryActions[0].callback = () => {
     this.secondaryActionClicked = true;
     throw new Error("Oops!");
   };
 }
 
-ErrorNotification.prototype = new BasicNotification();
-ErrorNotification.prototype.constructor = ErrorNotification;
+ErrorNotification.prototype = BasicNotification.prototype;
 
 function checkPopup(popup, notifyObj) {
   info("Checking notification " + notifyObj.id);
 
   ok(notifyObj.showingCallbackTriggered, "showing callback was triggered");
   ok(notifyObj.shownCallbackTriggered, "shown callback was triggered");
 
   let notifications = popup.childNodes;
@@ -252,16 +254,24 @@ function onPopupEvent(eventName, callbac
     PopupNotifications.panel.removeEventListener(eventName, listener, false);
     gActiveListeners.delete(listener);
     executeSoon(() => callback.call(PopupNotifications.panel));
   }
   gActiveListeners.set(listener, eventName);
   PopupNotifications.panel.addEventListener(eventName, listener, false);
 }
 
+function waitForNotificationPanel() {
+  return new Promise(resolve => {
+    onPopupEvent("popupshown", function() {
+      resolve(this);
+    });
+  });
+}
+
 function triggerMainCommand(popup) {
   let notifications = popup.childNodes;
   ok(notifications.length > 0, "at least one notification displayed");
   let notification = notifications[0];
   info("Triggering main command for notification " + notification.id);
   EventUtils.synthesizeMouseAtCenter(notification.button, {});
 }
 
--- a/toolkit/modules/PopupNotifications.jsm
+++ b/toolkit/modules/PopupNotifications.jsm
@@ -500,16 +500,29 @@ PopupNotifications.prototype = {
       // _update will handle both the tabs iconBox and non-tab permission
       // anchors.
       this._update(notifications, this._getAnchorsForNotifications(notifications,
         getAnchorFromBrowser(aBrowser)));
     }
   },
 
   /**
+   * Called by the consumer to indicate that the visibility of the notification
+   * anchors may have changed, but the location has not changed. This may result
+   * in the "showing" and "shown" events for visible notifications to be
+   * invoked even if the anchor has not changed.
+   */
+  anchorVisibilityChange: function() {
+    let notifications =
+      this._getNotificationsForBrowser(this.tabbrowser.selectedBrowser);
+    this._update(notifications, this._getAnchorsForNotifications(notifications,
+      getAnchorFromBrowser(this.tabbrowser.selectedBrowser)));
+  },
+
+  /**
    * Removes a Notification.
    * @param notification
    *        The Notification object to remove.
    */
   remove: function PopupNotifications_remove(notification) {
     this._remove(notification);
 
     if (this._isActiveBrowser(notification.browser)) {
@@ -826,16 +839,31 @@ PopupNotifications.prototype = {
       return !dismiss;
     });
     if (!notificationsToShow.length)
       return;
     let notificationIds = notificationsToShow.map(n => n.id);
 
     this._refreshPanel(notificationsToShow);
 
+    // If the anchor element is hidden or null, fall back to the identity icon.
+    if (!anchorElement || (anchorElement.boxObject.height == 0 &&
+                           anchorElement.boxObject.width == 0)) {
+      anchorElement = this.window.document.getElementById("identity-icon");
+
+      // If the identity icon is not available in this window, or maybe the
+      // entire location bar is hidden for any reason, use the tab as the
+      // anchor. We only ever show notifications for the current browser, so we
+      // can just use the current tab.
+      if (!anchorElement || (anchorElement.boxObject.height == 0 &&
+                             anchorElement.boxObject.width == 0)) {
+        anchorElement = this.tabbrowser.selectedTab;
+      }
+    }
+
     if (this.isPanelOpen && this._currentAnchorElement == anchorElement) {
       notificationsToShow.forEach(function(n) {
         this._fireCallback(n, NOTIFICATION_EVENT_SHOWN);
       }, this);
       // Let tests know that the panel was updated and what notifications it was
       // updated with so that tests can wait for the correct notifications to be
       // added.
       let event = new this.window.CustomEvent("PanelUpdated",
@@ -843,28 +871,16 @@ PopupNotifications.prototype = {
       this.panel.dispatchEvent(event);
       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().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;
 
       if (notificationsToShow.some(n => n.options.persistent)) {
         this.panel.setAttribute("noautohide", "true");
       } else {
         this.panel.removeAttribute("noautohide");
       }