--- 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");
}