From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- dom/notification/test/browser/browser.ini | 2 + .../test/browser/browser_permission_dismiss.js | 163 ++++++++++ dom/notification/test/browser/notification.html | 11 + dom/notification/test/chrome/chrome.ini | 1 + .../test_notification_system_principal.xhtml | 82 +++++ dom/notification/test/mochitest/MockServices.js | 177 +++++++++++ .../test/mochitest/NotificationTest.js | 102 +++++++ dom/notification/test/mochitest/blank.html | 4 + .../test/mochitest/create_notification.html | 16 + dom/notification/test/mochitest/mochitest.ini | 22 ++ .../test/mochitest/test_notification_basics.html | 125 ++++++++ .../test_notification_crossorigin_iframe.html | 68 +++++ .../test_notification_insecure_context.html | 47 +++ .../mochitest/test_notification_permissions.html | 70 +++++ .../test/mochitest/test_notification_tag.html | 169 ++++++++++ dom/notification/test/unit/head_notificationdb.js | 61 ++++ dom/notification/test/unit/test_notificationdb.js | 340 +++++++++++++++++++++ .../test/unit/test_notificationdb_bug1024090.js | 59 ++++ .../test/unit/test_notificationdb_migration.js | 129 ++++++++ dom/notification/test/unit/xpcshell.ini | 7 + 20 files changed, 1655 insertions(+) create mode 100644 dom/notification/test/browser/browser.ini create mode 100644 dom/notification/test/browser/browser_permission_dismiss.js create mode 100644 dom/notification/test/browser/notification.html create mode 100644 dom/notification/test/chrome/chrome.ini create mode 100644 dom/notification/test/chrome/test_notification_system_principal.xhtml create mode 100644 dom/notification/test/mochitest/MockServices.js create mode 100644 dom/notification/test/mochitest/NotificationTest.js create mode 100644 dom/notification/test/mochitest/blank.html create mode 100644 dom/notification/test/mochitest/create_notification.html create mode 100644 dom/notification/test/mochitest/mochitest.ini create mode 100644 dom/notification/test/mochitest/test_notification_basics.html create mode 100644 dom/notification/test/mochitest/test_notification_crossorigin_iframe.html create mode 100644 dom/notification/test/mochitest/test_notification_insecure_context.html create mode 100644 dom/notification/test/mochitest/test_notification_permissions.html create mode 100644 dom/notification/test/mochitest/test_notification_tag.html create mode 100644 dom/notification/test/unit/head_notificationdb.js create mode 100644 dom/notification/test/unit/test_notificationdb.js create mode 100644 dom/notification/test/unit/test_notificationdb_bug1024090.js create mode 100644 dom/notification/test/unit/test_notificationdb_migration.js create mode 100644 dom/notification/test/unit/xpcshell.ini (limited to 'dom/notification/test') diff --git a/dom/notification/test/browser/browser.ini b/dom/notification/test/browser/browser.ini new file mode 100644 index 0000000000..8a357c1a15 --- /dev/null +++ b/dom/notification/test/browser/browser.ini @@ -0,0 +1,2 @@ +[browser_permission_dismiss.js] +support-files = notification.html diff --git a/dom/notification/test/browser/browser_permission_dismiss.js b/dom/notification/test/browser/browser_permission_dismiss.js new file mode 100644 index 0000000000..2c936c4e89 --- /dev/null +++ b/dom/notification/test/browser/browser_permission_dismiss.js @@ -0,0 +1,163 @@ +"use strict"; + +const { PermissionTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PermissionTestUtils.sys.mjs" +); + +const ORIGIN_URI = Services.io.newURI("https://example.com"); +const PERMISSION_NAME = "desktop-notification"; +const PROMPT_ALLOW_BUTTON = -1; +const PROMPT_NOT_NOW_BUTTON = 0; +const PROMPT_NEVER_BUTTON = 1; +const TEST_URL = + "https://example.com/browser/dom/notification/test/browser/notification.html"; + +/** + * Clicks the specified web-notifications prompt button. + * + * @param {Number} aButtonIndex Number indicating which button to click. + * See the constants in this file. + * @note modified from toolkit/components/passwordmgr/test/browser/head.js + */ +function clickDoorhangerButton(aButtonIndex) { + let popup = PopupNotifications.getNotification("web-notifications"); + let notifications = popup.owner.panel.childNodes; + ok(notifications.length, "at least one notification displayed"); + ok(true, notifications.length + " notification(s)"); + let notification = notifications[0]; + + if (aButtonIndex == PROMPT_ALLOW_BUTTON) { + ok(true, "Triggering main action (allow the permission)"); + notification.button.doCommand(); + } else if (aButtonIndex == PROMPT_NEVER_BUTTON) { + ok(true, "Triggering secondary action (deny the permission permanently)"); + notification.menupopup.querySelector("menuitem").doCommand(); + } else { + ok(true, "Triggering secondary action (deny the permission temporarily)"); + notification.secondaryButton.doCommand(); + } +} + +/** + * Opens a tab which calls `Notification.requestPermission()` with a callback + * argument, calls the `task` function while the permission prompt is open, + * and verifies that the expected permission is set. + * + * @param {Function} task Task function to run to interact with the prompt. + * @param {String} permission Expected permission value. + * @return {Promise} resolving when the task function is done and the tab + * closes. + */ +function tabWithRequest(task, permission) { + PermissionTestUtils.remove(ORIGIN_URI, PERMISSION_NAME); + + return BrowserTestUtils.withNewTab( + { + gBrowser, + url: TEST_URL, + }, + async function (browser) { + let requestPromise = SpecialPowers.spawn( + browser, + [ + { + permission, + }, + ], + async function ({ permission }) { + function requestCallback(perm) { + is( + perm, + permission, + "Should call the legacy callback with the permission state" + ); + } + let perm = await content.window.Notification.requestPermission( + requestCallback + ); + is( + perm, + permission, + "Should resolve the promise with the permission state" + ); + } + ); + + await BrowserTestUtils.waitForEvent( + PopupNotifications.panel, + "popupshown" + ); + await task(); + await requestPromise; + } + ); +} + +add_setup(async function () { + Services.prefs.setBoolPref( + "dom.webnotifications.requireuserinteraction", + false + ); + Services.prefs.setBoolPref( + "permissions.desktop-notification.notNow.enabled", + true + ); + SimpleTest.registerCleanupFunction(() => { + Services.prefs.clearUserPref("dom.webnotifications.requireuserinteraction"); + Services.prefs.clearUserPref( + "permissions.desktop-notification.notNow.enabled" + ); + PermissionTestUtils.remove(ORIGIN_URI, PERMISSION_NAME); + }); +}); + +add_task(async function test_requestPermission_granted() { + await tabWithRequest(function () { + clickDoorhangerButton(PROMPT_ALLOW_BUTTON); + }, "granted"); + + ok( + !PopupNotifications.getNotification("web-notifications"), + "Should remove the doorhanger notification icon if granted" + ); + + is( + PermissionTestUtils.testPermission(ORIGIN_URI, PERMISSION_NAME), + Services.perms.ALLOW_ACTION, + "Check permission in perm. manager" + ); +}); + +add_task(async function test_requestPermission_denied_temporarily() { + await tabWithRequest(function () { + clickDoorhangerButton(PROMPT_NOT_NOW_BUTTON); + }, "default"); + + ok( + !PopupNotifications.getNotification("web-notifications"), + "Should remove the doorhanger notification icon if denied" + ); + + is( + PermissionTestUtils.testPermission(ORIGIN_URI, PERMISSION_NAME), + Services.perms.UNKNOWN_ACTION, + "Check permission in perm. manager" + ); +}); + +add_task(async function test_requestPermission_denied_permanently() { + await tabWithRequest(async function () { + await clickDoorhangerButton(PROMPT_NEVER_BUTTON); + }, "denied"); + + ok( + !PopupNotifications.getNotification("web-notifications"), + "Should remove the doorhanger notification icon if denied" + ); + + is( + PermissionTestUtils.testPermission(ORIGIN_URI, PERMISSION_NAME), + Services.perms.DENY_ACTION, + "Check permission in perm. manager" + ); +}); diff --git a/dom/notification/test/browser/notification.html b/dom/notification/test/browser/notification.html new file mode 100644 index 0000000000..0ceeb8ea46 --- /dev/null +++ b/dom/notification/test/browser/notification.html @@ -0,0 +1,11 @@ + + + + + Notifications test + + + + + + diff --git a/dom/notification/test/chrome/chrome.ini b/dom/notification/test/chrome/chrome.ini new file mode 100644 index 0000000000..78f68ab8e8 --- /dev/null +++ b/dom/notification/test/chrome/chrome.ini @@ -0,0 +1 @@ +[test_notification_system_principal.xhtml] \ No newline at end of file diff --git a/dom/notification/test/chrome/test_notification_system_principal.xhtml b/dom/notification/test/chrome/test_notification_system_principal.xhtml new file mode 100644 index 0000000000..27891caeed --- /dev/null +++ b/dom/notification/test/chrome/test_notification_system_principal.xhtml @@ -0,0 +1,82 @@ + + + + + + + diff --git a/dom/notification/test/mochitest/MockServices.js b/dom/notification/test/mochitest/MockServices.js new file mode 100644 index 0000000000..0f50bcf5bf --- /dev/null +++ b/dom/notification/test/mochitest/MockServices.js @@ -0,0 +1,177 @@ +/* eslint-disable mozilla/use-chromeutils-generateqi */ +var MockServices = (function () { + "use strict"; + + const MOCK_ALERTS_CID = SpecialPowers.wrap(SpecialPowers.Components).ID( + "{48068bc2-40ab-4904-8afd-4cdfb3a385f3}" + ); + const ALERTS_SERVICE_CONTRACT_ID = "@mozilla.org/alerts-service;1"; + + const MOCK_SYSTEM_ALERTS_CID = SpecialPowers.wrap( + SpecialPowers.Components + ).ID("{e86d888c-e41b-4b78-9104-2f2742a532de}"); + const SYSTEM_ALERTS_SERVICE_CONTRACT_ID = + "@mozilla.org/system-alerts-service;1"; + + var registrar = SpecialPowers.wrap( + SpecialPowers.Components + ).manager.QueryInterface(SpecialPowers.Ci.nsIComponentRegistrar); + + var activeAlertNotifications = Object.create(null); + + var activeAppNotifications = Object.create(null); + + window.addEventListener("mock-notification-close-event", function (e) { + for (var alertName in activeAlertNotifications) { + var notif = activeAlertNotifications[alertName]; + if (notif.title === e.detail.title) { + notif.listener.observe(null, "alertfinished", null); + delete activeAlertNotifications[alertName]; + delete activeAppNotifications[alertName]; + return; + } + } + }); + + var mockAlertsService = { + showPersistentNotification(persistentData, alert, alertListener) { + this.showAlert(alert, alertListener); + }, + + showAlert(alert, alertListener) { + var listener = SpecialPowers.wrap(alertListener); + activeAlertNotifications[alert.name] = { + listener, + cookie: alert.cookie, + title: alert.title, + }; + + // fake async alert show event + if (listener) { + setTimeout(function () { + listener.observe(null, "alertshow", alert.cookie); + }, 100); + setTimeout(function () { + listener.observe(null, "alertclickcallback", alert.cookie); + }, 100); + } + }, + + showAlertNotification( + imageUrl, + title, + text, + textClickable, + cookie, + alertListener, + name + ) { + this.showAlert( + { + name, + cookie, + title, + }, + alertListener + ); + }, + + closeAlert(name) { + var alertNotification = activeAlertNotifications[name]; + if (alertNotification) { + if (alertNotification.listener) { + alertNotification.listener.observe( + null, + "alertfinished", + alertNotification.cookie + ); + } + delete activeAlertNotifications[name]; + } + + var appNotification = activeAppNotifications[name]; + if (appNotification) { + delete activeAppNotifications[name]; + } + }, + + QueryInterface(aIID) { + if ( + SpecialPowers.wrap(aIID).equals(SpecialPowers.Ci.nsISupports) || + SpecialPowers.wrap(aIID).equals(SpecialPowers.Ci.nsIAlertsService) + ) { + return this; + } + throw SpecialPowers.Components.results.NS_ERROR_NO_INTERFACE; + }, + + createInstance(aIID) { + return this.QueryInterface(aIID); + }, + }; + mockAlertsService = SpecialPowers.wrapCallbackObject(mockAlertsService); + + // MockServices API + return { + register() { + try { + this.originalAlertsCID = registrar.contractIDToCID( + ALERTS_SERVICE_CONTRACT_ID + ); + } catch (ex) { + this.originalAlertsCID = null; + } + try { + this.originalSystemAlertsCID = registrar.contractIDToCID( + SYSTEM_ALERTS_SERVICE_CONTRACT_ID + ); + } catch (ex) { + this.originalSystemAlertsCID = null; + } + + registrar.registerFactory( + MOCK_ALERTS_CID, + "alerts service", + ALERTS_SERVICE_CONTRACT_ID, + mockAlertsService + ); + + registrar.registerFactory( + MOCK_SYSTEM_ALERTS_CID, + "system alerts service", + SYSTEM_ALERTS_SERVICE_CONTRACT_ID, + mockAlertsService + ); + }, + + unregister() { + registrar.unregisterFactory(MOCK_ALERTS_CID, mockAlertsService); + registrar.unregisterFactory(MOCK_SYSTEM_ALERTS_CID, mockAlertsService); + + // Passing `null` for the factory re-maps the contract ID to the + // entry for its original CID. + + if (this.originalAlertsCID) { + registrar.registerFactory( + this.originalAlertsCID, + "alerts service", + ALERTS_SERVICE_CONTRACT_ID, + null + ); + } + + if (this.originalSystemAlertsCID) { + registrar.registerFactory( + this.originalSystemAlertsCID, + "system alerts service", + SYSTEM_ALERTS_SERVICE_CONTRACT_ID, + null + ); + } + }, + + activeAlertNotifications, + + activeAppNotifications, + }; +})(); diff --git a/dom/notification/test/mochitest/NotificationTest.js b/dom/notification/test/mochitest/NotificationTest.js new file mode 100644 index 0000000000..400ff56253 --- /dev/null +++ b/dom/notification/test/mochitest/NotificationTest.js @@ -0,0 +1,102 @@ +var NotificationTest = (function () { + "use strict"; + + function info(msg, name) { + SimpleTest.info("::Notification Tests::" + (name || ""), msg); + } + + function setup_testing_env() { + SimpleTest.waitForExplicitFinish(); + // turn on testing pref (used by notification.cpp, and mock the alerts + return SpecialPowers.setBoolPref("notification.prompt.testing", true); + } + + async function teardown_testing_env() { + await SpecialPowers.clearUserPref("notification.prompt.testing"); + await SpecialPowers.clearUserPref("notification.prompt.testing.allow"); + + SimpleTest.finish(); + } + + function executeTests(tests, callback) { + // context is `this` object in test functions + // it can be used to track data between tests + var context = {}; + + (function executeRemainingTests(remainingTests) { + if (!remainingTests.length) { + callback(); + return; + } + + var nextTest = remainingTests.shift(); + var finishTest = executeRemainingTests.bind(null, remainingTests); + var startTest = nextTest.call.bind(nextTest, context, finishTest); + + try { + startTest(); + // if no callback was defined for test function, + // we must manually invoke finish to continue + if (nextTest.length === 0) { + finishTest(); + } + } catch (e) { + ok(false, "Test threw exception!"); + finishTest(); + } + })(tests); + } + + // NotificationTest API + return { + run(tests, callback) { + let ready = setup_testing_env(); + + addLoadEvent(async function () { + await ready; + executeTests(tests, function () { + teardown_testing_env(); + callback && callback(); + }); + }); + }, + + allowNotifications() { + return SpecialPowers.setBoolPref( + "notification.prompt.testing.allow", + true + ); + }, + + denyNotifications() { + return SpecialPowers.setBoolPref( + "notification.prompt.testing.allow", + false + ); + }, + + clickNotification(notification) { + // TODO: how?? + }, + + fireCloseEvent(title) { + window.dispatchEvent( + new CustomEvent("mock-notification-close-event", { + detail: { + title, + }, + }) + ); + }, + + info, + + payload: { + body: "Body", + tag: "fakeTag", + icon: "icon.jpg", + lang: "en-US", + dir: "ltr", + }, + }; +})(); diff --git a/dom/notification/test/mochitest/blank.html b/dom/notification/test/mochitest/blank.html new file mode 100644 index 0000000000..1f9324523a --- /dev/null +++ b/dom/notification/test/mochitest/blank.html @@ -0,0 +1,4 @@ + + + + diff --git a/dom/notification/test/mochitest/create_notification.html b/dom/notification/test/mochitest/create_notification.html new file mode 100644 index 0000000000..b0387e4ffb --- /dev/null +++ b/dom/notification/test/mochitest/create_notification.html @@ -0,0 +1,16 @@ + + + + Create a notification + + + + + diff --git a/dom/notification/test/mochitest/mochitest.ini b/dom/notification/test/mochitest/mochitest.ini new file mode 100644 index 0000000000..e2e84b575a --- /dev/null +++ b/dom/notification/test/mochitest/mochitest.ini @@ -0,0 +1,22 @@ +[DEFAULT] + +support-files = + blank.html + create_notification.html + MockServices.js + NotificationTest.js +skip-if = toolkit == 'android' # Bug 1531097 + +[test_notification_basics.html] +skip-if = xorigin # Bug 1792790 +[test_notification_crossorigin_iframe.html] +# This test needs to be run on HTTP (not HTTPS). +[test_notification_insecure_context.html] +fail-if = xorigin +skip-if = + http3 +[test_notification_permissions.html] +scheme = https +[test_notification_tag.html] +skip-if = + http3 diff --git a/dom/notification/test/mochitest/test_notification_basics.html b/dom/notification/test/mochitest/test_notification_basics.html new file mode 100644 index 0000000000..3dde839a96 --- /dev/null +++ b/dom/notification/test/mochitest/test_notification_basics.html @@ -0,0 +1,125 @@ + + + + Notification Basics + + + + + + +

+ +

+
+
+
diff --git a/dom/notification/test/mochitest/test_notification_crossorigin_iframe.html b/dom/notification/test/mochitest/test_notification_crossorigin_iframe.html
new file mode 100644
index 0000000000..dfdec3b1bd
--- /dev/null
+++ b/dom/notification/test/mochitest/test_notification_crossorigin_iframe.html
@@ -0,0 +1,68 @@
+
+
+
+
+  Notification permission in cross-origin iframes
+  
+  
+
+
+  

+ +
+  
+  
+ + diff --git a/dom/notification/test/mochitest/test_notification_insecure_context.html b/dom/notification/test/mochitest/test_notification_insecure_context.html new file mode 100644 index 0000000000..cd3a22bc7f --- /dev/null +++ b/dom/notification/test/mochitest/test_notification_insecure_context.html @@ -0,0 +1,47 @@ + + + + + Notification permission in insecure context + + + + +

+ +
+  
+  
+ + diff --git a/dom/notification/test/mochitest/test_notification_permissions.html b/dom/notification/test/mochitest/test_notification_permissions.html new file mode 100644 index 0000000000..f074ccf58e --- /dev/null +++ b/dom/notification/test/mochitest/test_notification_permissions.html @@ -0,0 +1,70 @@ + + + + + Notification permissions and permissions API + + + + +

+ +
+
+  
+ + diff --git a/dom/notification/test/mochitest/test_notification_tag.html b/dom/notification/test/mochitest/test_notification_tag.html new file mode 100644 index 0000000000..f4fc72bbe3 --- /dev/null +++ b/dom/notification/test/mochitest/test_notification_tag.html @@ -0,0 +1,169 @@ + + + + + Bug 782211 + + + + +Bug 782211 +

+ +
+
+ + + diff --git a/dom/notification/test/unit/head_notificationdb.js b/dom/notification/test/unit/head_notificationdb.js new file mode 100644 index 0000000000..1b23d88729 --- /dev/null +++ b/dom/notification/test/unit/head_notificationdb.js @@ -0,0 +1,61 @@ +"use strict"; + +var { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +function getNotificationObject(app, id, tag, includeScope) { + const origin = `https://${app}.gaiamobile.org/`; + return { + origin, + id, + title: app + "Notification:" + Date.now(), + dir: "auto", + lang: "", + body: app + " notification body", + tag: tag || "", + icon: "icon.png", + serviceWorkerRegistrationScope: includeScope ? origin : undefined, + }; +} + +var systemNotification = getNotificationObject( + "system", + "{2bc883bf-2809-4432-b0f4-f54e10372764}" +); + +var calendarNotification = getNotificationObject( + "calendar", + "{d8d11299-a58e-429b-9a9a-57c562982fbf}" +); + +// Helper to start the NotificationDB +function startNotificationDB() { + ChromeUtils.importESModule("resource://gre/modules/NotificationDB.sys.mjs"); +} + +// Helper function to add a listener, send message and treat the reply +function addAndSend(msg, reply, callback, payload, runNext = true) { + let handler = { + receiveMessage(message) { + if (message.name === reply) { + Services.cpmm.removeMessageListener(reply, handler); + callback(message); + if (runNext) { + run_next_test(); + } + } + }, + }; + Services.cpmm.addMessageListener(reply, handler); + Services.cpmm.sendAsyncMessage(msg, payload); +} + +// helper fonction, comparing two notifications +function compareNotification(notif1, notif2) { + // retrieved notification should be the second one sent + for (let prop in notif1) { + // compare each property + Assert.equal(notif1[prop], notif2[prop]); + } +} diff --git a/dom/notification/test/unit/test_notificationdb.js b/dom/notification/test/unit/test_notificationdb.js new file mode 100644 index 0000000000..33397f87f3 --- /dev/null +++ b/dom/notification/test/unit/test_notificationdb.js @@ -0,0 +1,340 @@ +"use strict"; + +function run_test() { + do_get_profile(); + startNotificationDB(); + run_next_test(); +} + +// Get one notification, none exists +add_test(function test_get_none() { + let requestID = 0; + let msgReply = "Notification:GetAll:Return:OK"; + let msgHandler = function (message) { + Assert.equal(requestID, message.data.requestID); + Assert.equal(0, message.data.notifications.length); + }; + + addAndSend("Notification:GetAll", msgReply, msgHandler, { + origin: systemNotification.origin, + requestID, + }); +}); + +// Store one notification +add_test(function test_send_one() { + let requestID = 1; + let msgReply = "Notification:Save:Return:OK"; + let msgHandler = function (message) { + Assert.equal(requestID, message.data.requestID); + }; + + addAndSend("Notification:Save", msgReply, msgHandler, { + origin: systemNotification.origin, + notification: systemNotification, + requestID, + }); +}); + +// Get one notification, one exists +add_test(function test_get_one() { + let requestID = 2; + let msgReply = "Notification:GetAll:Return:OK"; + let msgHandler = function (message) { + Assert.equal(requestID, message.data.requestID); + Assert.equal(1, message.data.notifications.length); + // compare the content + compareNotification(systemNotification, message.data.notifications[0]); + }; + + addAndSend("Notification:GetAll", msgReply, msgHandler, { + origin: systemNotification.origin, + requestID, + }); +}); + +// Delete one notification +add_test(function test_delete_one() { + let requestID = 3; + let msgReply = "Notification:Delete:Return:OK"; + let msgHandler = function (message) { + Assert.equal(requestID, message.data.requestID); + }; + + addAndSend("Notification:Delete", msgReply, msgHandler, { + origin: systemNotification.origin, + id: systemNotification.id, + requestID, + }); +}); + +// Get one notification, none exists +add_test(function test_get_none_again() { + let requestID = 4; + let msgReply = "Notification:GetAll:Return:OK"; + let msgHandler = function (message) { + Assert.equal(requestID, message.data.requestID); + Assert.equal(0, message.data.notifications.length); + }; + + addAndSend("Notification:GetAll", msgReply, msgHandler, { + origin: systemNotification.origin, + requestID, + }); +}); + +// Delete one notification that do not exists anymore +add_test(function test_delete_one_nonexistent() { + let requestID = 5; + let msgReply = "Notification:Delete:Return:OK"; + let msgHandler = function (message) { + Assert.equal(requestID, message.data.requestID); + }; + + addAndSend("Notification:Delete", msgReply, msgHandler, { + origin: systemNotification.origin, + id: systemNotification.id, + requestID, + }); +}); + +// Store two notifications with the same id +add_test(function test_send_two_get_one() { + let requestID = 6; + let calls = 0; + + let msgGetReply = "Notification:GetAll:Return:OK"; + let msgGetHandler = function (message) { + Assert.equal(requestID + 2, message.data.requestID); + Assert.equal(1, message.data.notifications.length); + // compare the content + compareNotification(systemNotification, message.data.notifications[0]); + }; + + let msgSaveReply = "Notification:Save:Return:OK"; + let msgSaveHandler = function (message) { + calls += 1; + if (calls === 2) { + addAndSend("Notification:GetAll", msgGetReply, msgGetHandler, { + origin: systemNotification.origin, + requestID: requestID + 2, + }); + } + }; + + addAndSend( + "Notification:Save", + msgSaveReply, + msgSaveHandler, + { + origin: systemNotification.origin, + notification: systemNotification, + requestID, + }, + false + ); + + addAndSend( + "Notification:Save", + msgSaveReply, + msgSaveHandler, + { + origin: systemNotification.origin, + notification: systemNotification, + requestID: requestID + 1, + }, + false + ); +}); + +// Delete previous notification +add_test(function test_delete_previous() { + let requestID = 8; + let msgReply = "Notification:Delete:Return:OK"; + let msgHandler = function (message) { + Assert.equal(requestID, message.data.requestID); + }; + + addAndSend("Notification:Delete", msgReply, msgHandler, { + origin: systemNotification.origin, + id: systemNotification.id, + requestID, + }); +}); + +// Store two notifications from same origin with the same tag +add_test(function test_send_two_get_one() { + let requestID = 10; + let tag = "voicemail"; + + let systemNotification1 = getNotificationObject( + "system", + "{f271f9ee-3955-4c10-b1f2-af552fb270ee}", + tag + ); + let systemNotification2 = getNotificationObject( + "system", + "{8ef9a628-f0f4-44b4-820d-c117573c33e3}", + tag + ); + + let msgGetReply = "Notification:GetAll:Return:OK"; + let msgGetNotifHandler = { + receiveMessage(message) { + if (message.name === msgGetReply) { + Services.cpmm.removeMessageListener(msgGetReply, msgGetNotifHandler); + let notifications = message.data.notifications; + // same tag, so replaced + Assert.equal(1, notifications.length); + // compare the content + compareNotification(systemNotification2, notifications[0]); + run_next_test(); + } + }, + }; + + Services.cpmm.addMessageListener(msgGetReply, msgGetNotifHandler); + + let msgSaveReply = "Notification:Save:Return:OK"; + let msgSaveCalls = 0; + let msgSaveHandler = function (message) { + msgSaveCalls++; + // Once both request have been sent, trigger getall + if (msgSaveCalls === 2) { + Services.cpmm.sendAsyncMessage("Notification:GetAll", { + origin: systemNotification1.origin, + requestID: message.data.requestID + 2, // 12, 13 + }); + } + }; + + addAndSend( + "Notification:Save", + msgSaveReply, + msgSaveHandler, + { + origin: systemNotification1.origin, + notification: systemNotification1, + requestID, // 10 + }, + false + ); + + addAndSend( + "Notification:Save", + msgSaveReply, + msgSaveHandler, + { + origin: systemNotification2.origin, + notification: systemNotification2, + requestID: requestID + 1, // 11 + }, + false + ); +}); + +// Delete previous notification +add_test(function test_delete_previous() { + let requestID = 15; + let msgReply = "Notification:Delete:Return:OK"; + let msgHandler = function (message) { + Assert.equal(requestID, message.data.requestID); + }; + + addAndSend("Notification:Delete", msgReply, msgHandler, { + origin: systemNotification.origin, + id: "{8ef9a628-f0f4-44b4-820d-c117573c33e3}", + requestID, + }); +}); + +// Store two notifications from two origins with the same tag +add_test(function test_send_two_get_two() { + let requestID = 20; + let tag = "voicemail"; + + let systemNotification1 = systemNotification; + systemNotification1.tag = tag; + + let calendarNotification2 = calendarNotification; + calendarNotification2.tag = tag; + + let msgGetReply = "Notification:GetAll:Return:OK"; + let msgGetCalls = 0; + let msgGetHandler = { + receiveMessage(message) { + if (message.name === msgGetReply) { + msgGetCalls++; + let notifications = message.data.notifications; + + // one notification per origin + Assert.equal(1, notifications.length); + + // first call should be system notification + if (msgGetCalls === 1) { + compareNotification(systemNotification1, notifications[0]); + } + + // second and last call should be calendar notification + if (msgGetCalls === 2) { + Services.cpmm.removeMessageListener(msgGetReply, msgGetHandler); + compareNotification(calendarNotification2, notifications[0]); + run_next_test(); + } + } + }, + }; + Services.cpmm.addMessageListener(msgGetReply, msgGetHandler); + + let msgSaveReply = "Notification:Save:Return:OK"; + let msgSaveCalls = 0; + let msgSaveHandler = { + receiveMessage(message) { + if (message.name === msgSaveReply) { + msgSaveCalls++; + if (msgSaveCalls === 2) { + Services.cpmm.removeMessageListener(msgSaveReply, msgSaveHandler); + + // Trigger getall for each origin + Services.cpmm.sendAsyncMessage("Notification:GetAll", { + origin: systemNotification1.origin, + requestID: message.data.requestID + 1, // 22 + }); + + Services.cpmm.sendAsyncMessage("Notification:GetAll", { + origin: calendarNotification2.origin, + requestID: message.data.requestID + 2, // 23 + }); + } + } + }, + }; + Services.cpmm.addMessageListener(msgSaveReply, msgSaveHandler); + + Services.cpmm.sendAsyncMessage("Notification:Save", { + origin: systemNotification1.origin, + notification: systemNotification1, + requestID, // 20 + }); + + Services.cpmm.sendAsyncMessage("Notification:Save", { + origin: calendarNotification2.origin, + notification: calendarNotification2, + requestID: requestID + 1, // 21 + }); +}); + +// Cleanup previous notification +add_test(function test_delete_previous() { + let requestID = 25; + let msgReply = "Notification:Delete:Return:OK"; + let msgHandler = function (message) { + Assert.equal(requestID, message.data.requestID); + }; + + addAndSend("Notification:Delete", msgReply, msgHandler, { + origin: systemNotification.origin, + id: "{2bc883bf-2809-4432-b0f4-f54e10372764}", + requestID, + }); +}); diff --git a/dom/notification/test/unit/test_notificationdb_bug1024090.js b/dom/notification/test/unit/test_notificationdb_bug1024090.js new file mode 100644 index 0000000000..37dee0a639 --- /dev/null +++ b/dom/notification/test/unit/test_notificationdb_bug1024090.js @@ -0,0 +1,59 @@ +"use strict"; + +function run_test() { + do_get_profile(); + run_next_test(); +} + +// For bug 1024090: test edge case of notificationstore.json +add_test(function test_bug1024090_purge() { + const NOTIFICATION_STORE_PATH = PathUtils.join( + PathUtils.profileDir, + "notificationstore" + ); + let cleanup = IOUtils.remove(NOTIFICATION_STORE_PATH, { recursive: true }); + cleanup + .then( + function onSuccess() { + ok(true, "Notification database cleaned."); + }, + function onError(reason) { + ok(false, "Notification database error when cleaning: " + reason); + } + ) + .then(function next() { + info("Cleanup steps completed: " + NOTIFICATION_STORE_PATH); + startNotificationDB(); + run_next_test(); + }); +}); + +// Store one notification +add_test(function test_bug1024090_send_one() { + let requestID = 1; + let msgReply = "Notification:Save:Return:OK"; + let msgHandler = function (message) { + equal(requestID, message.data.requestID, "Checking requestID"); + }; + + addAndSend("Notification:Save", msgReply, msgHandler, { + origin: systemNotification.origin, + notification: systemNotification, + requestID, + }); +}); + +// Get one notification, one exists +add_test(function test_bug1024090_get_one() { + let requestID = 2; + let msgReply = "Notification:GetAll:Return:OK"; + let msgHandler = function (message) { + equal(requestID, message.data.requestID, "Checking requestID"); + equal(1, message.data.notifications.length, "One notification stored"); + }; + + addAndSend("Notification:GetAll", msgReply, msgHandler, { + origin: systemNotification.origin, + requestID, + }); +}); diff --git a/dom/notification/test/unit/test_notificationdb_migration.js b/dom/notification/test/unit/test_notificationdb_migration.js new file mode 100644 index 0000000000..2b38e8d43a --- /dev/null +++ b/dom/notification/test/unit/test_notificationdb_migration.js @@ -0,0 +1,129 @@ +"use strict"; + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); + +const fooNotification = getNotificationObject( + "foo", + "a4f1d54a-98b7-4231-9120-5afc26545bad", + null, + true +); +const barNotification = getNotificationObject( + "bar", + "a4f1d54a-98b7-4231-9120-5afc26545bad", + "baz", + true +); +const msg = "Notification:GetAll"; +const msgReply = "Notification:GetAll:Return:OK"; + +do_get_profile(); +const OLD_STORE_PATH = PathUtils.join( + PathUtils.profileDir, + "notificationstore.json" +); + +let nextRequestID = 0; + +// Create the old datastore and populate it with data before we initialize +// the notification database so it has data to migrate. This is a setup step, +// not a test, but it seems like we need to do it in a test function +// rather than in run_test() because the test runner doesn't handle async steps +// in run_test(). +add_task( + { + skip_if: () => !AppConstants.MOZ_NEW_NOTIFICATION_STORE, + }, + async function test_create_old_datastore() { + const notifications = { + [fooNotification.origin]: { + [fooNotification.id]: fooNotification, + }, + [barNotification.origin]: { + [barNotification.id]: barNotification, + }, + }; + + await IOUtils.writeJSON(OLD_STORE_PATH, notifications); + + startNotificationDB(); + } +); + +add_test( + { + skip_if: () => !AppConstants.MOZ_NEW_NOTIFICATION_STORE, + }, + function test_get_system_notification() { + const requestID = nextRequestID++; + const msgHandler = function (message) { + Assert.equal(requestID, message.data.requestID); + Assert.equal(0, message.data.notifications.length); + }; + + addAndSend(msg, msgReply, msgHandler, { + origin: systemNotification.origin, + requestID, + }); + } +); + +add_test( + { + skip_if: () => !AppConstants.MOZ_NEW_NOTIFICATION_STORE, + }, + function test_get_foo_notification() { + const requestID = nextRequestID++; + const msgHandler = function (message) { + Assert.equal(requestID, message.data.requestID); + Assert.equal(1, message.data.notifications.length); + Assert.deepEqual( + fooNotification, + message.data.notifications[0], + "Notification data migrated" + ); + }; + + addAndSend(msg, msgReply, msgHandler, { + origin: fooNotification.origin, + requestID, + }); + } +); + +add_test( + { + skip_if: () => !AppConstants.MOZ_NEW_NOTIFICATION_STORE, + }, + function test_get_bar_notification() { + const requestID = nextRequestID++; + const msgHandler = function (message) { + Assert.equal(requestID, message.data.requestID); + Assert.equal(1, message.data.notifications.length); + Assert.deepEqual( + barNotification, + message.data.notifications[0], + "Notification data migrated" + ); + }; + + addAndSend(msg, msgReply, msgHandler, { + origin: barNotification.origin, + requestID, + }); + } +); + +add_task( + { + skip_if: () => !AppConstants.MOZ_NEW_NOTIFICATION_STORE, + }, + async function test_old_datastore_deleted() { + Assert.ok( + !(await IOUtils.exists(OLD_STORE_PATH)), + "old datastore no longer exists" + ); + } +); diff --git a/dom/notification/test/unit/xpcshell.ini b/dom/notification/test/unit/xpcshell.ini new file mode 100644 index 0000000000..7b43951aa1 --- /dev/null +++ b/dom/notification/test/unit/xpcshell.ini @@ -0,0 +1,7 @@ +[DEFAULT] +head = head_notificationdb.js +skip-if = toolkit == 'android' + +[test_notificationdb.js] +[test_notificationdb_bug1024090.js] +[test_notificationdb_migration.js] -- cgit v1.2.3