diff options
Diffstat (limited to 'browser/base/content/test/popupNotifications')
11 files changed, 1761 insertions, 0 deletions
diff --git a/browser/base/content/test/popupNotifications/.eslintrc.js b/browser/base/content/test/popupNotifications/.eslintrc.js new file mode 100644 index 0000000000..7c80211924 --- /dev/null +++ b/browser/base/content/test/popupNotifications/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/mochitest/browser.eslintrc.js" + ] +}; diff --git a/browser/base/content/test/popupNotifications/browser.ini b/browser/base/content/test/popupNotifications/browser.ini new file mode 100644 index 0000000000..83bb7c5174 --- /dev/null +++ b/browser/base/content/test/popupNotifications/browser.ini @@ -0,0 +1,18 @@ +[DEFAULT] +support-files = + head.js + +[browser_displayURI.js] +skip-if = (os == "linux" && (debug || asan)) +[browser_popupNotification.js] +skip-if = (os == "linux" && (debug || asan)) +[browser_popupNotification_2.js] +skip-if = (os == "linux" && (debug || asan)) +[browser_popupNotification_3.js] +skip-if = (os == "linux" && (debug || asan)) +[browser_popupNotification_4.js] +skip-if = (os == "linux" && (debug || asan)) +[browser_popupNotification_checkbox.js] +skip-if = (os == "linux" && (debug || asan)) +[browser_reshow_in_background.js] +skip-if = (os == "linux" && (debug || asan)) diff --git a/browser/base/content/test/popupNotifications/browser_displayURI.js b/browser/base/content/test/popupNotifications/browser_displayURI.js new file mode 100644 index 0000000000..48222be197 --- /dev/null +++ b/browser/base/content/test/popupNotifications/browser_displayURI.js @@ -0,0 +1,28 @@ +/* + * Make sure that the origin is shown for ContentPermissionPrompt + * 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); + }); + }); + 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, + "class", + "popup-notification-body"); + ok(body.innerHTML.includes("example.com"), "Check that at least the eTLD+1 is present in the markup"); + }); +}); diff --git a/browser/base/content/test/popupNotifications/browser_popupNotification.js b/browser/base/content/test/popupNotifications/browser_popupNotification.js new file mode 100644 index 0000000000..6be3e4205a --- /dev/null +++ b/browser/base/content/test/popupNotifications/browser_popupNotification.js @@ -0,0 +1,203 @@ +/* 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/. */ + +// These are shared between test #4 to #5 +var wrongBrowserNotificationObject = new BasicNotification("wrongBrowser"); +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); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + triggerMainCommand(popup); + }, + onHidden: function (popup) { + ok(this.notifyObj.mainActionClicked, "mainAction was clicked"); + ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered"); + ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered"); + } + }, + { id: "Test#2", + run: function () { + this.notifyObj = new BasicNotification(this.id); + showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + triggerSecondaryCommand(popup, 0); + }, + onHidden: function (popup) { + ok(this.notifyObj.secondaryActionClicked, "secondaryAction was clicked"); + ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered"); + ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered"); + } + }, + { id: "Test#3", + run: function () { + this.notifyObj = new BasicNotification(this.id); + this.notification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + dismissNotification(popup); + }, + onHidden: function (popup) { + ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered"); + 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"); + 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"); + ok(!wrongBrowserNotificationObject.dismissalCallbackTriggered, "dismissal callback wasn't called"); + goNext(); + } + }, + // now select that browser and test to see that the notification appeared + { id: "Test#5", + run: function () { + this.oldSelectedTab = gBrowser.selectedTab; + gBrowser.selectedTab = gBrowser.tabs[gBrowser.tabs.length - 1]; + }, + onShown: function (popup) { + checkPopup(popup, wrongBrowserNotificationObject); + is(PopupNotifications.isPanelOpen, true, "isPanelOpen getter doesn't lie"); + + // switch back to the old browser + gBrowser.selectedTab = this.oldSelectedTab; + }, + onHidden: function (popup) { + // actually remove the notification to prevent it from reappearing + ok(wrongBrowserNotificationObject.dismissalCallbackTriggered, "dismissal callback triggered due to tab switch"); + wrongBrowserNotification.remove(); + ok(wrongBrowserNotificationObject.removedCallbackTriggered, "removed callback triggered"); + wrongBrowserNotification = null; + } + }, + // test that the removed notification isn't shown on browser re-select + { id: "Test#6", + run: function* () { + let promiseTopic = promiseTopicObserved("PopupNotifications-updateNotShowing"); + gBrowser.selectedTab = gBrowser.tabs[gBrowser.tabs.length - 1]; + yield promiseTopic; + is(PopupNotifications.isPanelOpen, false, "panel isn't open"); + gBrowser.removeTab(gBrowser.selectedTab); + goNext(); + } + }, + // Test that two notifications with the same ID result in a single displayed + // notification. + { id: "Test#7", + run: function () { + this.notifyObj = new BasicNotification(this.id); + // Show the same notification twice + this.notification1 = showNotification(this.notifyObj); + this.notification2 = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + this.notification2.remove(); + }, + onHidden: function (popup) { + ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered"); + ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered"); + } + }, + // Test that two notifications with different IDs are displayed + { id: "Test#8", + run: function () { + this.testNotif1 = new BasicNotification(this.id); + this.testNotif1.message += " 1"; + showNotification(this.testNotif1); + this.testNotif2 = new BasicNotification(this.id); + this.testNotif2.message += " 2"; + this.testNotif2.id += "-2"; + showNotification(this.testNotif2); + }, + onShown: function (popup) { + is(popup.childNodes.length, 2, "two notifications are shown"); + // Trigger the main command for the first notification, and the secondary + // for the second. Need to do mainCommand first since the secondaryCommand + // triggering is async. + triggerMainCommand(popup); + is(popup.childNodes.length, 1, "only one notification left"); + triggerSecondaryCommand(popup, 0); + }, + onHidden: function (popup) { + ok(this.testNotif1.mainActionClicked, "main action #1 was clicked"); + ok(!this.testNotif1.secondaryActionClicked, "secondary action #1 wasn't clicked"); + ok(!this.testNotif1.dismissalCallbackTriggered, "dismissal callback #1 wasn't called"); + + ok(!this.testNotif2.mainActionClicked, "main action #2 wasn't clicked"); + ok(this.testNotif2.secondaryActionClicked, "secondary action #2 was clicked"); + ok(!this.testNotif2.dismissalCallbackTriggered, "dismissal callback #2 wasn't called"); + } + }, + // Test notification without mainAction + { id: "Test#9", + run: function () { + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.mainAction = null; + this.notification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + dismissNotification(popup); + }, + onHidden: function (popup) { + this.notification.remove(); + } + }, + // Test two notifications with different anchors + { id: "Test#10", + run: function () { + this.notifyObj = new BasicNotification(this.id); + this.firstNotification = showNotification(this.notifyObj); + this.notifyObj2 = new BasicNotification(this.id); + this.notifyObj2.id += "-2"; + this.notifyObj2.anchorID = "addons-notification-icon"; + // Second showNotification() overrides the first + this.secondNotification = showNotification(this.notifyObj2); + }, + onShown: function (popup) { + // This also checks that only one element is shown. + checkPopup(popup, this.notifyObj2); + is(document.getElementById("geo-notification-icon").boxObject.width, 0, + "geo anchor shouldn't be visible"); + dismissNotification(popup); + }, + onHidden: function (popup) { + // Remove the notifications + this.firstNotification.remove(); + this.secondNotification.remove(); + ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered"); + ok(this.notifyObj2.removedCallbackTriggered, "removed callback triggered"); + } + } +]; diff --git a/browser/base/content/test/popupNotifications/browser_popupNotification_2.js b/browser/base/content/test/popupNotifications/browser_popupNotification_2.js new file mode 100644 index 0000000000..d770988959 --- /dev/null +++ b/browser/base/content/test/popupNotifications/browser_popupNotification_2.js @@ -0,0 +1,266 @@ +/* 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(); + goNext(); +} + +var tests = [ + // Test optional params + { id: "Test#1", + run: function () { + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.secondaryActions = undefined; + this.notification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + dismissNotification(popup); + }, + onHidden: function (popup) { + ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered"); + this.notification.remove(); + ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered"); + } + }, + // Test that icons appear + { id: "Test#2", + run: function () { + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.id = "geolocation"; + this.notifyObj.anchorID = "geo-notification-icon"; + this.notification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + isnot(document.getElementById("geo-notification-icon").boxObject.width, 0, + "geo anchor should be visible"); + dismissNotification(popup); + }, + onHidden: function (popup) { + let icon = document.getElementById("geo-notification-icon"); + isnot(icon.boxObject.width, 0, + "geo anchor should be visible after dismissal"); + this.notification.remove(); + is(icon.boxObject.width, 0, + "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/"); + 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/") + // 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/"); + 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) { + this.complete = false; + yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.org/"); + yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/"); + // Next load will hide the notification + this.notification.options.timeout = Date.now() - 1; + this.complete = true; + yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.org/"); + }, + onHidden: function (popup) { + ok(this.complete, "Should only have hidden the notification after the timeout was passed"); + this.notification.remove(); + gBrowser.removeTab(gBrowser.selectedTab); + 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/"); + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.addOptions({ + persistWhileVisible: true + }); + 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/"); + // Notification should persist across location changes + this.complete = true; + dismissNotification(popup); + }, + onHidden: function (popup) { + ok(this.complete, "Should only have hidden the notification after it was dismissed"); + this.notification.remove(); + gBrowser.removeTab(gBrowser.selectedTab); + gBrowser.selectedTab = this.oldSelectedTab; + } + }, + + // Test that nested icon nodes correctly activate popups + { id: "Test#6", + run: function() { + // Add a temporary box as the anchor with a button + this.box = document.createElement("box"); + PopupNotifications.iconBox.appendChild(this.box); + + let button = document.createElement("button"); + button.setAttribute("label", "Please click me!"); + this.box.appendChild(button); + + // The notification should open up on the box + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.anchorID = this.box.id = "nested-box"; + this.notifyObj.addOptions({dismissed: true}); + this.notification = showNotification(this.notifyObj); + + // This test places a normal button in the notification area, which has + // standard GTK styling and dimensions. Due to the clip-path, this button + // gets clipped off, which makes it necessary to synthesize the mouse click + // a little bit downward. To be safe, I adjusted the x-offset with the same + // amount. + EventUtils.synthesizeMouse(button, 4, 4, {}); + }, + onShown: function(popup) { + checkPopup(popup, this.notifyObj); + dismissNotification(popup); + }, + onHidden: function(popup) { + this.notification.remove(); + this.box.parentNode.removeChild(this.box); + } + }, + // Test that popupnotifications without popups have anchor icons shown + { id: "Test#7", + run: function* () { + let notifyObj = new BasicNotification(this.id); + notifyObj.anchorID = "geo-notification-icon"; + notifyObj.addOptions({neverShow: true}); + let promiseTopic = promiseTopicObserved("PopupNotifications-updateNotShowing"); + showNotification(notifyObj); + yield promiseTopic; + isnot(document.getElementById("geo-notification-icon").boxObject.width, 0, + "geo anchor should be visible"); + goNext(); + } + }, + // Test notification "Not Now" menu item + { id: "Test#8", + run: function () { + this.notifyObj = new BasicNotification(this.id); + this.notification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + triggerSecondaryCommand(popup, 1); + }, + onHidden: function (popup) { + ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered"); + this.notification.remove(); + ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered"); + } + }, + // Test notification close button + { id: "Test#9", + run: function () { + this.notifyObj = new BasicNotification(this.id); + this.notification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + let notification = popup.childNodes[0]; + EventUtils.synthesizeMouseAtCenter(notification.closebutton, {}); + }, + onHidden: function (popup) { + ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered"); + this.notification.remove(); + ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered"); + } + }, + // Test notification when chrome is hidden + { id: "Test#10", + run: function () { + window.locationbar.visible = false; + this.notifyObj = new BasicNotification(this.id); + this.notification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + is(popup.anchorNode.className, "tabbrowser-tab", "notification anchored to tab"); + dismissNotification(popup); + }, + 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; + } + } +]; diff --git a/browser/base/content/test/popupNotifications/browser_popupNotification_3.js b/browser/base/content/test/popupNotifications/browser_popupNotification_3.js new file mode 100644 index 0000000000..33ec3f7145 --- /dev/null +++ b/browser/base/content/test/popupNotifications/browser_popupNotification_3.js @@ -0,0 +1,305 @@ +/* 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(); + 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({ + removeOnDismissal: true + }); + this.notification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + dismissNotification(popup); + }, + onHidden: function (popup) { + ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered"); + ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered"); + } + }, + // Test multiple notification icons are shown + { id: "Test#2", + run: function () { + this.notifyObj1 = new BasicNotification(this.id); + this.notifyObj1.id += "_1"; + this.notifyObj1.anchorID = "default-notification-icon"; + this.notification1 = showNotification(this.notifyObj1); + + this.notifyObj2 = new BasicNotification(this.id); + this.notifyObj2.id += "_2"; + this.notifyObj2.anchorID = "geo-notification-icon"; + this.notification2 = showNotification(this.notifyObj2); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj2); + + // check notifyObj1 anchor icon is showing + isnot(document.getElementById("default-notification-icon").boxObject.width, 0, + "default anchor should be visible"); + // check notifyObj2 anchor icon is showing + isnot(document.getElementById("geo-notification-icon").boxObject.width, 0, + "geo anchor should be visible"); + + dismissNotification(popup); + }, + onHidden: function (popup) { + this.notification1.remove(); + ok(this.notifyObj1.removedCallbackTriggered, "removed callback triggered"); + + this.notification2.remove(); + ok(this.notifyObj2.removedCallbackTriggered, "removed callback triggered"); + } + }, + // Test that multiple notification icons are removed when switching tabs + { id: "Test#3", + 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"); + + // 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); + + // check notifyObjOld anchor icon is removed + is(document.getElementById("default-notification-icon").boxObject.width, 0, + "default anchor shouldn't be visible"); + // check notifyObjNew anchor icon is showing + isnot(document.getElementById("geo-notification-icon").boxObject.width, 0, + "geo anchor should be visible"); + + dismissNotification(popup); + }, + onHidden: function (popup) { + this.notificationNew.remove(); + gBrowser.removeTab(gBrowser.selectedTab); + + gBrowser.selectedTab = this.oldSelectedTab; + this.notificationOld.remove(); + } + }, + // test security delay - too early + { id: "Test#4", + run: function () { + // Set the security delay to 100s + PopupNotifications.buttonDelay = 100000; + + this.notifyObj = new BasicNotification(this.id); + showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + triggerMainCommand(popup); + + // Wait to see if the main command worked + executeSoon(function delayedDismissal() { + dismissNotification(popup); + }); + + }, + onHidden: function (popup) { + ok(!this.notifyObj.mainActionClicked, "mainAction was not clicked because it was too soon"); + ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback was triggered"); + } + }, + // test security delay - after delay + { id: "Test#5", + run: function () { + // Set the security delay to 10ms + PopupNotifications.buttonDelay = 10; + + this.notifyObj = new BasicNotification(this.id); + showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + + // Wait until after the delay to trigger the main action + setTimeout(function delayedDismissal() { + triggerMainCommand(popup); + }, 500); + + }, + onHidden: function (popup) { + ok(this.notifyObj.mainActionClicked, "mainAction was clicked after the delay"); + ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback was not triggered"); + PopupNotifications.buttonDelay = PREF_SECURITY_DELAY_INITIAL; + } + }, + // reload removes notification + { id: "Test#6", + run: function* () { + yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/"); + let notifyObj = new BasicNotification(this.id); + notifyObj.options.eventCallback = function (eventName) { + if (eventName == "removed") { + ok(true, "Notification removed in background tab after reloading"); + goNext(); + } + }; + showNotification(notifyObj); + executeSoon(function () { + 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/"); + 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"); + executeSoon(function () { + gBrowser.removeTab(newTab); + goNext(); + }); + } + }; + showNotification(notifyObj); + executeSoon(function () { + browser.reload(); + }); + } + }, + // 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 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); + fgNotifyObj.anchorID = anchor.id; + fgNotifyObj.options.dismissed = true; + let fgNotification = showNotification(fgNotifyObj); + + let bgNotifyObj = new BasicNotification(this.id); + bgNotifyObj.anchorID = anchor.id; + bgNotifyObj.browser = gBrowser.getBrowserForTab(bgTab); + // show the notification in the background tab ... + let bgNotification = showNotification(bgNotifyObj); + // ... and re-show it + bgNotification = showNotification(bgNotifyObj); + + ok(fgNotification.id, "notification has id"); + is(fgNotification.id, bgNotification.id, "notification ids are the same"); + is(anchor.getAttribute("showing"), "true", "anchor still showing"); + + fgNotification.remove(); + gBrowser.removeTab(bgTab); + goNext(); + } + }, + // location change in an embedded frame should not remove a notification + { id: "Test#9", + run: function* () { + yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html;charset=utf8,<iframe%20id='iframe'%20src='http://example.com/'>"); + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.options.eventCallback = function (eventName) { + if (eventName == "removed") { + ok(false, "Notification removed from browser when subframe navigated"); + } + }; + showNotification(this.notifyObj); + }, + onShown: function (popup) { + let self = this; + let progressListener = { + onLocationChange: function onLocationChange() { + gBrowser.removeProgressListener(progressListener); + + executeSoon(() => { + let notification = PopupNotifications.getNotification(self.notifyObj.id, + self.notifyObj.browser); + ok(notification != null, "Notification remained when subframe navigated"); + self.notifyObj.options.eventCallback = undefined; + + notification.remove(); + }); + }, + }; + + info("Adding progress listener and performing navigation"); + gBrowser.addProgressListener(progressListener); + ContentTask.spawn(gBrowser.selectedBrowser, null, function() { + content.document.getElementById("iframe") + .setAttribute("src", "http://example.org/"); + }); + }, + onHidden: function () {} + }, + // Popup Notifications should catch exceptions from callbacks + { id: "Test#10", + run: function () { + this.testNotif1 = new BasicNotification(this.id); + this.testNotif1.message += " 1"; + this.notification1 = showNotification(this.testNotif1); + this.testNotif1.options.eventCallback = function (eventName) { + info("notifyObj1.options.eventCallback: " + eventName); + if (eventName == "dismissed") { + throw new Error("Oops 1!"); + } + }; + + this.testNotif2 = new BasicNotification(this.id); + this.testNotif2.message += " 2"; + this.testNotif2.id += "-2"; + this.testNotif2.options.eventCallback = function (eventName) { + info("notifyObj2.options.eventCallback: " + eventName); + if (eventName == "dismissed") { + throw new Error("Oops 2!"); + } + }; + this.notification2 = showNotification(this.testNotif2); + }, + onShown: function (popup) { + is(popup.childNodes.length, 2, "two notifications are shown"); + dismissNotification(popup); + }, + onHidden: function () { + this.notification1.remove(); + this.notification2.remove(); + } + } +]; diff --git a/browser/base/content/test/popupNotifications/browser_popupNotification_4.js b/browser/base/content/test/popupNotifications/browser_popupNotification_4.js new file mode 100644 index 0000000000..750ad82fd2 --- /dev/null +++ b/browser/base/content/test/popupNotifications/browser_popupNotification_4.js @@ -0,0 +1,294 @@ +/* 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(); + goNext(); +} + +var tests = [ + // Popup Notifications main actions should catch exceptions from callbacks + { id: "Test#1", + run: function () { + this.testNotif = new ErrorNotification(); + 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(); + 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"); + } + }, + // Existing popup notification shouldn't disappear when adding a dismissed notification + { id: "Test#3", + run: function () { + this.notifyObj1 = new BasicNotification(this.id); + this.notifyObj1.id += "_1"; + this.notifyObj1.anchorID = "default-notification-icon"; + this.notification1 = showNotification(this.notifyObj1); + }, + onShown: function (popup) { + // Now show a dismissed notification, and check that it doesn't clobber + // the showing one. + this.notifyObj2 = new BasicNotification(this.id); + this.notifyObj2.id += "_2"; + this.notifyObj2.anchorID = "geo-notification-icon"; + this.notifyObj2.options.dismissed = true; + this.notification2 = showNotification(this.notifyObj2); + + checkPopup(popup, this.notifyObj1); + + // check that both anchor icons are showing + is(document.getElementById("default-notification-icon").getAttribute("showing"), "true", + "notification1 anchor should be visible"); + is(document.getElementById("geo-notification-icon").getAttribute("showing"), "true", + "notification2 anchor should be visible"); + + dismissNotification(popup); + }, + onHidden: function(popup) { + this.notification1.remove(); + this.notification2.remove(); + } + }, + // Showing should be able to modify the popup data + { id: "Test#4", + run: function() { + this.notifyObj = new BasicNotification(this.id); + let normalCallback = this.notifyObj.options.eventCallback; + this.notifyObj.options.eventCallback = function (eventName) { + if (eventName == "showing") { + this.mainAction.label = "Alternate Label"; + } + normalCallback.call(this, eventName); + }; + showNotification(this.notifyObj); + }, + onShown: function(popup) { + // checkPopup checks for the matching label. Note that this assumes that + // this.notifyObj.mainAction is the same as notification.mainAction, + // 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"); + let notifyObj = new BasicNotification(this.id); + 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(); + }); + } + }, + // 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"); + let notifyObj = new BasicNotification(this.id); + let originalCallback = notifyObj.options.eventCallback; + notifyObj.options.eventCallback = function (eventName) { + originalCallback(eventName); + return eventName == "swapping"; + }; + + let notification = showNotification(notifyObj); + let win = gBrowser.replaceTabWithWindow(gBrowser.selectedTab); + yield whenDelayedStartupFinished(win); + + yield new Promise(resolve => { + let originalCallback = notification.options.eventCallback; + notification.options.eventCallback = function (eventName) { + originalCallback(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") + + goNext(); + } + }, + // the hideNotNow option + { id: "Test#7", + run: function () { + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.options.hideNotNow = true; + this.notifyObj.mainAction.dismiss = true; + this.notification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + // checkPopup verifies that the Not Now item is hidden, and that no separator is added. + checkPopup(popup, this.notifyObj); + triggerMainCommand(popup); + }, + onHidden: function (popup) { + this.notification.remove(); + } + }, + // the main action callback can keep the notification. + { id: "Test#8", + run: function () { + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.mainAction.dismiss = true; + this.notification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + triggerMainCommand(popup); + }, + onHidden: function (popup) { + ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback was triggered"); + ok(!this.notifyObj.removedCallbackTriggered, "removed callback wasn't triggered"); + this.notification.remove(); + } + }, + // a secondary action callback can keep the notification. + { id: "Test#9", + run: function () { + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.secondaryActions[0].dismiss = true; + this.notification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + triggerSecondaryCommand(popup, 0); + }, + onHidden: function (popup) { + ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback was triggered"); + ok(!this.notifyObj.removedCallbackTriggered, "removed callback wasn't triggered"); + this.notification.remove(); + } + }, + // returning true in the showing callback should dismiss the notification. + { id: "Test#10", + run: function() { + let notifyObj = new BasicNotification(this.id); + let originalCallback = notifyObj.options.eventCallback; + notifyObj.options.eventCallback = function (eventName) { + originalCallback(eventName); + return eventName == "showing"; + }; + + let notification = showNotification(notifyObj); + ok(notifyObj.showingCallbackTriggered, "the showing callback was triggered"); + ok(!notifyObj.shownCallbackTriggered, "the shown callback wasn't triggered"); + notification.remove(); + goNext(); + } + }, + // panel updates should fire the showing and shown callbacks again. + { id: "Test#11", + run: function() { + this.notifyObj = new BasicNotification(this.id); + this.notification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + + this.notifyObj.showingCallbackTriggered = false; + this.notifyObj.shownCallbackTriggered = false; + + // Force an update of the panel. This is typically called + // automatically when receiving 'activate' or 'TabSelect' events, + // but from a setTimeout, which is inconvenient for the test. + PopupNotifications._update(); + + checkPopup(popup, this.notifyObj); + + this.notification.remove(); + }, + onHidden: function() { } + }, + // A first dismissed notification shouldn't stop _update from showing a second notification + { id: "Test#12", + run: function () { + this.notifyObj1 = new BasicNotification(this.id); + this.notifyObj1.id += "_1"; + this.notifyObj1.anchorID = "default-notification-icon"; + this.notifyObj1.options.dismissed = true; + this.notification1 = showNotification(this.notifyObj1); + + this.notifyObj2 = new BasicNotification(this.id); + this.notifyObj2.id += "_2"; + this.notifyObj2.anchorID = "geo-notification-icon"; + this.notifyObj2.options.dismissed = true; + this.notification2 = showNotification(this.notifyObj2); + + this.notification2.dismissed = false; + PopupNotifications._update(); + }, + onShown: function (popup) { + 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#13", + 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(); + }); + } + } +]; diff --git a/browser/base/content/test/popupNotifications/browser_popupNotification_checkbox.js b/browser/base/content/test/popupNotifications/browser_popupNotification_checkbox.js new file mode 100644 index 0000000000..bcc51fcd7f --- /dev/null +++ b/browser/base/content/test/popupNotifications/browser_popupNotification_checkbox.js @@ -0,0 +1,211 @@ +/* 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(); + 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"); +} + +function checkMainAction(notification, disabled=false) { + let mainAction = notification.button; + let warningLabel = document.getAnonymousElementByAttribute(notification, "class", "popup-notification-warning"); + is(warningLabel.hidden, !disabled, "Warning label should be shown"); + is(mainAction.disabled, disabled, "MainAction should be disabled"); +} + +function promiseElementVisible(element) { + // HTMLElement.offsetParent is null when the element is not visisble + // (or if the element has |position: fixed|). See: + // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent + return BrowserTestUtils.waitForCondition(() => element.offsetParent !== null, + "Waiting for element to be visible"); +} + +var gNotification; + +var tests = [ + // Test that passing the checkbox field shows the checkbox. + { id: "show_checkbox", + run: function () { + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.options.checkbox = { + label: "This is a checkbox", + }; + gNotification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + let notification = popup.childNodes[0]; + checkCheckbox(notification.checkbox, "This is a checkbox"); + triggerMainCommand(popup); + }, + onHidden: function () { } + }, + + // Test checkbox being checked by default + { id: "checkbox_checked", + run: function () { + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.options.checkbox = { + label: "Check this", + checked: true, + }; + gNotification = showNotification(this.notifyObj); + }, + onShown: function (popup) { + checkPopup(popup, this.notifyObj); + let notification = popup.childNodes[0]; + checkCheckbox(notification.checkbox, "Check this", true); + triggerMainCommand(popup); + }, + onHidden: function () { } + }, + + // Test checkbox passing the checkbox state on mainAction + { id: "checkbox_passCheckboxChecked_mainAction", + run: function () { + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.mainAction.callback = ({checkboxChecked}) => this.mainActionChecked = checkboxChecked; + this.notifyObj.options.checkbox = { + label: "This is a checkbox", + }; + gNotification = showNotification(this.notifyObj); + }, + onShown: function* (popup) { + checkPopup(popup, this.notifyObj); + let notification = popup.childNodes[0]; + let checkbox = notification.checkbox; + checkCheckbox(checkbox, "This is a checkbox"); + yield promiseElementVisible(checkbox); + EventUtils.synthesizeMouseAtCenter(checkbox, {}); + checkCheckbox(checkbox, "This is a checkbox", true); + triggerMainCommand(popup); + }, + onHidden: function () { + is(this.mainActionChecked, true, "mainAction callback is passed the correct checkbox value"); + } + }, + + // Test checkbox passing the checkbox state on secondaryAction + { id: "checkbox_passCheckboxChecked_secondaryAction", + run: function () { + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.secondaryActions = [{ + label: "Test Secondary", + accessKey: "T", + callback: ({checkboxChecked}) => this.secondaryActionChecked = checkboxChecked, + }]; + this.notifyObj.options.checkbox = { + label: "This is a checkbox", + }; + gNotification = showNotification(this.notifyObj); + }, + onShown: function* (popup) { + checkPopup(popup, this.notifyObj); + let notification = popup.childNodes[0]; + let checkbox = notification.checkbox; + checkCheckbox(checkbox, "This is a checkbox"); + yield promiseElementVisible(checkbox); + EventUtils.synthesizeMouseAtCenter(checkbox, {}); + checkCheckbox(checkbox, "This is a checkbox", true); + triggerSecondaryCommand(popup, 0); + }, + onHidden: function () { + is(this.secondaryActionChecked, true, "secondaryAction callback is passed the correct checkbox value"); + } + }, + + // Test checkbox preserving its state through re-opening the doorhanger + { id: "checkbox_reopen", + run: function () { + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.options.checkbox = { + label: "This is a checkbox", + checkedState: { + disableMainAction: true, + warningLabel: "Testing disable", + }, + }; + gNotification = showNotification(this.notifyObj); + }, + onShown: function* (popup) { + checkPopup(popup, this.notifyObj); + let notification = popup.childNodes[0]; + let checkbox = notification.checkbox; + checkCheckbox(checkbox, "This is a checkbox"); + yield promiseElementVisible(checkbox); + EventUtils.synthesizeMouseAtCenter(checkbox, {}); + dismissNotification(popup); + }, + onHidden: function* (popup) { + let icon = document.getElementById("default-notification-icon"); + let shown = waitForNotificationPanel(); + EventUtils.synthesizeMouseAtCenter(icon, {}); + yield shown; + let notification = popup.childNodes[0]; + let checkbox = notification.checkbox; + checkCheckbox(checkbox, "This is a checkbox", true); + checkMainAction(notification, true); + gNotification.remove(); + } + }, +]; + +// Test checkbox disabling the main action in different combinations +["checkedState", "uncheckedState"].forEach(function (state) { + [true, false].forEach(function (checked) { + tests.push( + { id: `checkbox_disableMainAction_${state}_${checked ? 'checked' : 'unchecked'}`, + run: function () { + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.options.checkbox = { + label: "This is a checkbox", + checked: checked, + [state]: { + disableMainAction: true, + warningLabel: "Testing disable", + }, + }; + gNotification = showNotification(this.notifyObj); + }, + onShown: function* (popup) { + checkPopup(popup, this.notifyObj); + let notification = popup.childNodes[0]; + let checkbox = notification.checkbox; + let disabled = (state === "checkedState" && checked) || + (state === "uncheckedState" && !checked); + + checkCheckbox(checkbox, "This is a checkbox", checked); + checkMainAction(notification, disabled); + yield promiseElementVisible(checkbox); + EventUtils.synthesizeMouseAtCenter(checkbox, {}); + checkCheckbox(checkbox, "This is a checkbox", !checked); + checkMainAction(notification, !disabled); + EventUtils.synthesizeMouseAtCenter(checkbox, {}); + checkCheckbox(checkbox, "This is a checkbox", checked); + checkMainAction(notification, disabled); + + // Unblock the main command if it's currently disabled. + if (disabled) { + EventUtils.synthesizeMouseAtCenter(checkbox, {}); + } + triggerMainCommand(popup); + }, + onHidden: function () { } + } + ); + }); +}); + diff --git a/browser/base/content/test/popupNotifications/browser_popupNotification_keyboard.js b/browser/base/content/test/popupNotifications/browser_popupNotification_keyboard.js new file mode 100644 index 0000000000..0f5b57cede --- /dev/null +++ b/browser/base/content/test/popupNotifications/browser_popupNotification_keyboard.js @@ -0,0 +1,74 @@ +/* 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 for persistent notifications, + // the secondary action is triggered by pressing the escape key. + { id: "Test#1", + run() { + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.options.persistent = true; + showNotification(this.notifyObj); + }, + onShown(popup) { + checkPopup(popup, this.notifyObj); + EventUtils.synthesizeKey("VK_ESCAPE", {}); + }, + onHidden(popup) { + ok(!this.notifyObj.mainActionClicked, "mainAction was not clicked"); + ok(this.notifyObj.secondaryActionClicked, "secondaryAction was clicked"); + ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered"); + ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered"); + } + }, + // Test that for non-persistent notifications, the escape key dismisses the notification. + { id: "Test#2", + *run() { + yield waitForWindowReadyForPopupNotifications(window); + this.notifyObj = new BasicNotification(this.id); + this.notification = showNotification(this.notifyObj); + }, + onShown(popup) { + checkPopup(popup, this.notifyObj); + EventUtils.synthesizeKey("VK_ESCAPE", {}); + }, + onHidden(popup) { + ok(!this.notifyObj.mainActionClicked, "mainAction was not clicked"); + ok(!this.notifyObj.secondaryActionClicked, "secondaryAction was not clicked"); + ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered"); + ok(!this.notifyObj.removedCallbackTriggered, "removed callback was not triggered"); + this.notification.remove(); + } + }, + // Test that the space key on an anchor element focuses an active notification + { id: "Test#3", + *run() { + this.notifyObj = new BasicNotification(this.id); + this.notifyObj.anchorID = "geo-notification-icon"; + this.notifyObj.addOptions({ + persistent: true + }); + this.notification = showNotification(this.notifyObj); + }, + *onShown(popup) { + checkPopup(popup, this.notifyObj); + let anchor = document.getElementById(this.notifyObj.anchorID); + anchor.focus(); + is(document.activeElement, anchor); + EventUtils.synthesizeKey(" ", {}); + is(document.activeElement, popup.childNodes[0].button); + this.notification.remove(); + }, + onHidden(popup) { } + }, +]; diff --git a/browser/base/content/test/popupNotifications/browser_reshow_in_background.js b/browser/base/content/test/popupNotifications/browser_reshow_in_background.js new file mode 100644 index 0000000000..6f415f62e6 --- /dev/null +++ b/browser/base/content/test/popupNotifications/browser_reshow_in_background.js @@ -0,0 +1,52 @@ +"use strict"; + +/** + * 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 seenEvents = []; + + let options = { + dismissed: false, + eventCallback(popupEvent) { + seenEvents.push(popupEvent); + }, + }; + + let notification = + PopupNotifications.show(tabC.linkedBrowser, "test-notification", + "", "plugins-notification-icon", + null, null, options); + Assert.deepEqual(seenEvents, [], "Should have seen no events yet."); + + yield BrowserTestUtils.switchTab(gBrowser, tabB); + Assert.deepEqual(seenEvents, [], "Should have seen no events yet."); + + notification.reshow(); + Assert.deepEqual(seenEvents, [], "Should have seen no events yet."); + + let panelShown = + BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown"); + yield BrowserTestUtils.switchTab(gBrowser, tabC); + yield panelShown; + + Assert.equal(seenEvents.length, 2, "Should have seen two events."); + Assert.equal(seenEvents[0], "showing", "Should have said popup was showing."); + Assert.equal(seenEvents[1], "shown", "Should have said popup was shown."); + + let panelHidden = + BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden"); + PopupNotifications.remove(notification); + yield panelHidden; + + yield BrowserTestUtils.removeTab(tabB); + yield BrowserTestUtils.removeTab(tabC); +}); diff --git a/browser/base/content/test/popupNotifications/head.js b/browser/base/content/test/popupNotifications/head.js new file mode 100644 index 0000000000..4a803d6af4 --- /dev/null +++ b/browser/base/content/test/popupNotifications/head.js @@ -0,0 +1,303 @@ +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +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. + * @rejects Never. + */ +function promiseTopicObserved(topic) +{ + let deferred = Promise.defer(); + info("Waiting for observer topic " + topic); + Services.obs.addObserver(function PTO_observe(subject, topic, data) { + Services.obs.removeObserver(PTO_observe, topic); + deferred.resolve([subject, data]); + }, topic, false); + return deferred.promise; +} + + +/** + * 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 + * The url to load, or the current url. + * @return {Promise} resolved when the event is handled. + * @resolves to the received event + * @rejects if a valid load event is not received within a meaningful interval + */ +function promiseTabLoadEvent(tab, url) +{ + let browser = tab.linkedBrowser; + + if (url) { + browser.loadURI(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. + + registerCleanupFunction(() => { + PopupNotifications.buttonDelay = PREF_SECURITY_DELAY_INITIAL; + }); +} + +function goNext() { + executeSoon(() => executeSoon(Task.async(runNextTest))); +} + +function* runNextTest() { + if (tests.length == 0) { + executeSoon(finish); + return; + } + + let nextTest = tests.shift(); + if (nextTest.onShown) { + let shownState = false; + onPopupEvent("popupshowing", function () { + info("[" + nextTest.id + "] popup showing"); + }); + onPopupEvent("popupshown", function () { + shownState = true; + info("[" + nextTest.id + "] popup shown"); + Task.spawn(() => nextTest.onShown(this)) + .then(undefined, ex => Assert.ok(false, "onShown failed: " + ex)); + }); + onPopupEvent("popuphidden", function () { + info("[" + nextTest.id + "] popup hidden"); + Task.spawn(() => nextTest.onHidden(this)) + .then(() => goNext(), ex => Assert.ok(false, "onHidden failed: " + ex)); + }, () => shownState); + info("[" + nextTest.id + "] added listeners; panel is open: " + PopupNotifications.isPanelOpen); + } + + info("[" + nextTest.id + "] running test"); + yield nextTest.run(); +} + +function showNotification(notifyObj) { + info("Showing notification " + notifyObj.id); + return PopupNotifications.show(notifyObj.browser, + notifyObj.id, + notifyObj.message, + notifyObj.anchorID, + notifyObj.mainAction, + notifyObj.secondaryActions, + notifyObj.options); +} + +function dismissNotification(popup) { + info("Dismissing notification " + popup.childNodes[0].id); + executeSoon(() => EventUtils.synthesizeKey("VK_ESCAPE", {})); +} + +function BasicNotification(testId) { + this.browser = gBrowser.selectedBrowser; + this.id = "test-notification-" + testId; + this.message = "This is popup notification for " + testId; + this.anchorID = null; + this.mainAction = { + label: "Main Action", + accessKey: "M", + callback: () => this.mainActionClicked = true + }; + this.secondaryActions = [ + { + label: "Secondary Action", + accessKey: "S", + callback: () => this.secondaryActionClicked = true + } + ]; + this.options = { + eventCallback: eventName => { + switch (eventName) { + case "dismissed": + this.dismissalCallbackTriggered = true; + break; + case "showing": + this.showingCallbackTriggered = true; + break; + case "shown": + this.shownCallbackTriggered = true; + break; + case "removed": + this.removedCallbackTriggered = true; + break; + case "swapping": + this.swappingCallbackTriggered = true; + break; + } + } + }; +} + +BasicNotification.prototype.addOptions = function(options) { + for (let [name, value] of Object.entries(options)) + this.options[name] = value; +}; + +function ErrorNotification() { + 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; + +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; + is(notifications.length, 1, "one notification displayed"); + let notification = notifications[0]; + if (!notification) + return; + let icon = document.getAnonymousElementByAttribute(notification, "class", + "popup-notification-icon"); + if (notifyObj.id == "geolocation") { + isnot(icon.boxObject.width, 0, "icon for geo displayed"); + ok(popup.anchorNode.classList.contains("notification-anchor-icon"), + "notification anchored to icon"); + } + is(notification.getAttribute("label"), notifyObj.message, "message matches"); + is(notification.id, notifyObj.id + "-notification", "id matches"); + if (notifyObj.mainAction) { + is(notification.getAttribute("buttonlabel"), notifyObj.mainAction.label, + "main action label matches"); + is(notification.getAttribute("buttonaccesskey"), + notifyObj.mainAction.accessKey, "main action accesskey matches"); + } + let actualSecondaryActions = + Array.filter(notification.childNodes, child => child.nodeName == "menuitem"); + let secondaryActions = notifyObj.secondaryActions || []; + let actualSecondaryActionsCount = actualSecondaryActions.length; + if (notifyObj.options.hideNotNow) { + is(notification.getAttribute("hidenotnow"), "true", "'Not Now' item hidden"); + if (secondaryActions.length) + is(notification.lastChild.tagName, "menuitem", "no menuseparator"); + } + else if (secondaryActions.length) { + is(notification.lastChild.tagName, "menuseparator", "menuseparator exists"); + } + is(actualSecondaryActionsCount, secondaryActions.length, + actualSecondaryActions.length + " secondary actions"); + secondaryActions.forEach(function (a, i) { + is(actualSecondaryActions[i].getAttribute("label"), a.label, + "label for secondary action " + i + " matches"); + is(actualSecondaryActions[i].getAttribute("accesskey"), a.accessKey, + "accessKey for secondary action " + i + " matches"); + }); +} + +XPCOMUtils.defineLazyGetter(this, "gActiveListeners", () => { + let listeners = new Map(); + registerCleanupFunction(() => { + for (let [listener, eventName] of listeners) { + PopupNotifications.panel.removeEventListener(eventName, listener, false); + } + }); + return listeners; +}); + +function onPopupEvent(eventName, callback, condition) { + let listener = event => { + if (event.target != PopupNotifications.panel || + (condition && !condition())) + return; + 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); + // 20, 10 so that the inner button is hit + EventUtils.synthesizeMouse(notification.button, 20, 10, {}); +} + +function triggerSecondaryCommand(popup, index) { + let notifications = popup.childNodes; + ok(notifications.length > 0, "at least one notification displayed"); + let notification = notifications[0]; + info("Triggering secondary command for notification " + notification.id); + // Cancel the arrow panel slide-in transition (bug 767133) such that + // it won't interfere with us interacting with the dropdown. + document.getAnonymousNodes(popup)[0].style.transition = "none"; + + notification.button.focus(); + + popup.addEventListener("popupshown", function handle() { + popup.removeEventListener("popupshown", handle, false); + info("Command popup open for notification " + notification.id); + // Press down until the desired command is selected + for (let i = 0; i <= index; i++) { + EventUtils.synthesizeKey("VK_DOWN", {}); + } + // Activate + EventUtils.synthesizeKey("VK_RETURN", {}); + }, false); + + // One down event to open the popup + info("Open the popup to trigger secondary command for notification " + notification.id); + EventUtils.synthesizeKey("VK_DOWN", { altKey: !navigator.platform.includes("Mac") }); +} |