summaryrefslogtreecommitdiffstats
path: root/dom/notification/test
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/notification/test/browser/browser.ini2
-rw-r--r--dom/notification/test/browser/browser_permission_dismiss.js163
-rw-r--r--dom/notification/test/browser/notification.html11
-rw-r--r--dom/notification/test/chrome/chrome.ini1
-rw-r--r--dom/notification/test/chrome/test_notification_system_principal.xhtml82
-rw-r--r--dom/notification/test/mochitest/MockServices.js177
-rw-r--r--dom/notification/test/mochitest/NotificationTest.js102
-rw-r--r--dom/notification/test/mochitest/blank.html4
-rw-r--r--dom/notification/test/mochitest/create_notification.html16
-rw-r--r--dom/notification/test/mochitest/mochitest.ini22
-rw-r--r--dom/notification/test/mochitest/test_notification_basics.html125
-rw-r--r--dom/notification/test/mochitest/test_notification_crossorigin_iframe.html68
-rw-r--r--dom/notification/test/mochitest/test_notification_insecure_context.html47
-rw-r--r--dom/notification/test/mochitest/test_notification_permissions.html70
-rw-r--r--dom/notification/test/mochitest/test_notification_tag.html169
-rw-r--r--dom/notification/test/unit/head_notificationdb.js61
-rw-r--r--dom/notification/test/unit/test_notificationdb.js340
-rw-r--r--dom/notification/test/unit/test_notificationdb_bug1024090.js59
-rw-r--r--dom/notification/test/unit/test_notificationdb_migration.js129
-rw-r--r--dom/notification/test/unit/xpcshell.ini7
20 files changed, 1655 insertions, 0 deletions
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 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Notifications test</title>
+ </head>
+
+ <body>
+
+ </body>
+</html>
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 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=874090
+-->
+<window title="Mozilla Bug 874090" onload="runTests()"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=874090"
+ target="_blank">Mozilla Bug 874090</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ /** Test for Bug 874090 **/
+ const MOCK_CID = Components.ID("{2a0f83c4-8818-4914-a184-f1172b4eaaa7}");
+ const ALERTS_SERVICE_CONTRACT_ID = "@mozilla.org/alerts-service;1";
+
+ var mockAlertsService = {
+ showAlert: function(alert, alertListener) {
+ ok(true, "System principal was granted permission and is able to call showAlert.");
+ unregisterMock();
+ SimpleTest.finish();
+ },
+
+ showAlertNotification: function(imageUrl, title, text, textClickable,
+ cookie, alertListener, name, dir, lang, data) {
+ this.showAlert();
+ },
+
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Ci.nsISupports) ||
+ aIID.equals(Ci.nsIAlertsService)) {
+ return this;
+ }
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ },
+
+ createInstance: function(aIID) {
+ return this.QueryInterface(aIID);
+ }
+ };
+
+ function registerMock() {
+ Components.manager.QueryInterface(Ci.nsIComponentRegistrar).
+ registerFactory(MOCK_CID, "alerts service", ALERTS_SERVICE_CONTRACT_ID, mockAlertsService);
+ }
+
+ function unregisterMock() {
+ Components.manager.QueryInterface(Ci.nsIComponentRegistrar).
+ unregisterFactory(MOCK_CID, mockAlertsService);
+ }
+
+ function runTests() {
+ registerMock();
+
+ is(Notification.permission, "granted", "System principal should be automatically granted permission.");
+
+ Notification.requestPermission(function(permission) {
+ is(permission, "granted", "System principal should be granted permission when calling requestPermission.");
+
+ if (permission == "granted") {
+ // Create a notification and make sure that it is able to call into
+ // the mock alert service to show the notification.
+ new Notification("Hello");
+ } else {
+ unregisterMock();
+ SimpleTest.finish();
+ }
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ ]]>
+ </script>
+</window>
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 @@
+<!DOCTYPE html>
+<html>
+ <body></body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<head><meta charset=utf-8>
+ <title>Create a notification</title>
+</head>
+<body>
+<script>
+
+var notification = new Notification("This is a title", {
+ body: "This is a notification body",
+ tag: "sometag",
+ });
+
+</script>
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Notification Basics</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="MockServices.js"></script>
+ <script type="text/javascript" src="NotificationTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script type="text/javascript">
+
+ var info = NotificationTest.info;
+ var options;
+
+ SimpleTest.requestFlakyTimeout("untriaged");
+
+ var steps = [
+ function() {
+ info("Test notification spec");
+ ok(Notification, "Notification constructor exists");
+ ok(Notification.permission, "Notification.permission exists");
+ ok(Notification.requestPermission, "Notification.requestPermission exists");
+ },
+
+ function() {
+ info("Test requestPermission without callback");
+ Notification.requestPermission();
+ },
+
+ async function(done) {
+ info("Test requestPermission deny");
+ function assertPermissionDenied(perm) {
+ is(perm, "denied", "Permission should be denied.");
+ is(Notification.permission, "denied", "Permission should be denied.");
+ }
+ await NotificationTest.denyNotifications();
+ Notification.requestPermission()
+ .then(assertPermissionDenied)
+ .then(_ => Notification.requestPermission(assertPermissionDenied))
+ .catch(err => {
+ ok(!err, "requestPermission should not reject promise");
+ })
+ .then(done);
+ },
+
+ async function(done) {
+ info("Test requestPermission grant");
+ function assertPermissionGranted(perm) {
+ is(perm, "granted", "Permission should be granted.");
+ is(Notification.permission, "granted", "Permission should be granted");
+ }
+ await NotificationTest.allowNotifications();
+ Notification.requestPermission()
+ .then(assertPermissionGranted)
+ .then(_ => Notification.requestPermission(assertPermissionGranted))
+ .catch(err => {
+ ok(!err, "requestPermission should not reject promise");
+ })
+ .then(done);
+ },
+
+ function(done) {
+ info("Test invalid requestPermission");
+ Notification.requestPermission({})
+ .then(_ => {
+ ok(false, "Non callable arg to requestPermission should reject promise");
+ }, err => {
+ ok(true, "Non callable arg to requestPermission should reject promise");
+ })
+ .then(done);
+ },
+
+ function(done) {
+ info("Test create notification");
+
+ options = NotificationTest.payload;
+
+ var notification = new Notification("This is a title", options);
+
+ ok(notification, "Notification exists");
+ is(notification.onclick, null, "onclick() should be null");
+ is(notification.onshow, null, "onshow() should be null");
+ is(notification.onerror, null, "onerror() should be null");
+ is(notification.onclose, null, "onclose() should be null");
+ is(typeof notification.close, "function", "close() should exist");
+
+ is(notification.dir, options.dir, "auto should get set");
+ is(notification.lang, options.lang, "lang should get set");
+ is(notification.body, options.body, "body should get set");
+ is(notification.tag, options.tag, "tag should get set");
+ is(notification.icon, options.icon, "icon should get set");
+
+ // store notification in test context
+ this.notification = notification;
+
+ notification.onshow = function() {
+ ok(true, "onshow handler should be called");
+ done();
+ };
+ },
+
+ function(done) {
+ info("Test closing a notification");
+ var notification = this.notification;
+
+ notification.onclose = function() {
+ ok(true, "onclose handler should be called");
+ done();
+ };
+
+ notification.close();
+ },
+ ];
+
+ MockServices.register();
+ NotificationTest.run(steps, function() {
+ MockServices.unregister();
+ });
+</script>
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests that Notification permissions are denied in cross-origin iframes.
+https://bugzilla.mozilla.org/show_bug.cgi?id=1560741
+-->
+<head>
+ <title>Notification permission in cross-origin iframes</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ <script class="testbody" type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ const kBlankURL = "https://example.com/tests/dom/notification/test/mochitest/blank.html";
+
+ (async function runTest() {
+ await SpecialPowers.pushPrefEnv({"set": [
+ ["notification.prompt.testing", true],
+ ["notification.prompt.testing.allow", true],
+ ["dom.webnotifications.allowinsecure", true],
+ ]});
+
+ let iframe = document.createElement("iframe");
+ iframe.src = kBlankURL;
+ document.body.appendChild(iframe);
+ await new Promise(resolve => {
+ iframe.onload = resolve;
+ });
+
+ let checkRequest = async (expectedResponse, msg) => {
+ let response = await this.content.Notification.requestPermission();
+ Assert.equal(response, expectedResponse, msg);
+ };
+
+ await SpecialPowers.spawn(iframe,
+ ["denied", "Denied permission in cross-origin iframe"],
+ checkRequest);
+
+ let checkPermission = async (expectedPermission, msg) => {
+ let permission = this.content.Notification.permission;
+ Assert.equal(permission, expectedPermission, msg);
+ };
+
+ await SpecialPowers.spawn(iframe,
+ ["denied", "Permission is denied in cross-origin iframe"],
+ checkPermission);
+
+ await SpecialPowers.pushPrefEnv({"set": [["dom.webnotifications.allowcrossoriginiframe", true]]});
+
+ await SpecialPowers.spawn(iframe,
+ ["granted", "Granted permission in cross-origin iframe with pref set"],
+ checkRequest);
+ await SpecialPowers.spawn(iframe,
+ ["granted", "Permission is granted in cross-origin iframe with pref set"],
+ checkPermission);
+
+ SimpleTest.finish();
+ })();
+ </script>
+ </pre>
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests that Notification permissions are denied in insecure context.
+https://bugzilla.mozilla.org/show_bug.cgi?id=1429432
+-->
+<head>
+ <title>Notification permission in insecure context</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ <script class="testbody" type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ // Add an allow permission for the mochitest origin to test this.
+ let script = SpecialPowers.loadChromeScript(function() {
+ /* eslint-env mozilla/chrome-script */
+ let principal = Services.scriptSecurityManager.createContentPrincipalFromOrigin("http://mochi.test:8888");
+ Services.perms.addFromPrincipal(principal, "desktop-notification", Services.perms.ALLOW_ACTION);
+ addMessageListener("destroy", function() {
+ Services.perms.removeFromPrincipal(principal, "desktop-notification");
+ });
+ });
+
+ (async function runTest() {
+ let response = await Notification.requestPermission();
+ is(response, "denied", "Denied permission in insecure context");
+
+ await SpecialPowers.pushPrefEnv({"set": [["dom.webnotifications.allowinsecure", true]]});
+
+ response = await Notification.requestPermission();
+ is(response, "granted", "Granted permission in insecure context with pref set");
+
+ script.sendAsyncMessage("destroy");
+ script.destroy();
+
+ SimpleTest.finish();
+ })();
+ </script>
+ </pre>
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests that the Notification.requestPermission and navigator.permissions.query
+return values are consistent with the stored permission.
+https://bugzilla.mozilla.org/show_bug.cgi?id=1589754
+-->
+<head>
+ <title>Notification permissions and permissions API</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+<script class="testbody">
+
+add_task(async function test_notifications_permission() {
+ await SpecialPowers.clearUserPref("notification.prompt.testing");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Set pref to exercise relevant code path for regression test.
+ ["permissions.delegation.enabled", true],
+ // Automatically dismiss the permission request when it appears.
+ ["dom.webnotifications.requireuserinteraction", true],
+ ],
+ });
+
+ async function testPermissionInWindow(win) {
+ async function checkPermission(perm, expectedResult, expectedPermission) {
+ await SpecialPowers.pushPermissions([
+ {
+ type: "desktop-notification",
+ allow: SpecialPowers.Ci.nsIPermissionManager[perm],
+ context: document,
+ },
+ ]);
+ is(
+ await win.Notification.requestPermission(),
+ expectedResult,
+ `expected requestPermission() result for permission ${perm}`
+ );
+
+ let result =
+ await win.navigator.permissions.query({ name: "notifications" });
+ is(
+ result.state,
+ expectedPermission,
+ `expected permissions API result for permission ${perm}`
+ );
+ }
+
+ await checkPermission("UNKNOWN_ACTION", "default", "prompt");
+ await checkPermission("ALLOW_ACTION", "granted", "granted");
+ await checkPermission("DENY_ACTION", "denied", "denied");
+ await checkPermission("PROMPT_ACTION", "default", "prompt");
+ }
+
+ var win = window.open("blank.html");
+ await new Promise(resolve => { win.onload = resolve; });
+ await testPermissionInWindow(win);
+ win.close();
+});
+
+</script>
+ </pre>
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=782211
+-->
+<head>
+ <title>Bug 782211</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=782211">Bug 782211</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+<script type="text/javascript">
+ /* eslint-disable mozilla/use-chromeutils-generateqi */
+
+ // The mock is not a general purpose mock, but is specific for this test.
+ // It is always registered in the parent process using LoadChromeScript by
+ // the MockAlertsService below, to allow this to work regardless of whether
+ // the frames from different origins live in the same process or in different
+ // processes (with Fission), since the default content-process alerts service
+ // relays messages to the parent process.
+ function mockServicesChromeScript() {
+ /* eslint-env mozilla/chrome-script */
+ const MOCK_CID = Components.ID("{dbe37e64-d9a3-402c-8d8a-0826c619f7ad}");
+ const ALERTS_SERVICE_CONTRACT_ID = "@mozilla.org/alerts-service;1";
+
+ var notificationsCreated = [];
+
+ const mockAlertsService = {
+ showAlert(alert, alertListener) {
+ notificationsCreated.push(alert.name);
+ if (notificationsCreated.length == 3) {
+ // notifications created by the test1 origin
+ var test1notifications = [];
+ // notifications created by the test2 origin
+ var test2notifications = [];
+ for (var i = 0; i < notificationsCreated.length; i++) {
+ var notificationName = notificationsCreated[i];
+ if (notificationName.includes("test1")) {
+ test1notifications.push(notificationsCreated[i]);
+ } else if (notificationName.includes("test2")) {
+ test2notifications.push(notificationsCreated[i]);
+ }
+ }
+
+ is(
+ test1notifications.length,
+ 2,
+ "2 notifications should be created by test1.example.org:80 origin."
+ );
+ is(
+ test1notifications[0],
+ test1notifications[1],
+ "notification names should be identical."
+ );
+ is(
+ test2notifications.length,
+ 1,
+ "1 notification should be created by test2.example.org:80 origin."
+ );
+
+ // Register original alerts service.
+ registrar.unregisterFactory(MOCK_CID, this);
+
+ sendAsyncMessage("mock-alert-service:unregistered");
+ }
+ },
+
+ showAlertNotification(
+ imageUrl,
+ title,
+ text,
+ textClickable,
+ cookie,
+ alertListener,
+ name,
+ dir,
+ lang,
+ data
+ ) {
+ this.showAlert({ name });
+ },
+
+ QueryInterface(aIID) {
+ if (aIID.equals(Ci.nsISupports) || aIID.equals(Ci.nsIAlertsService)) {
+ return this;
+ }
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ },
+
+ createInstance(aIID) {
+ return this.QueryInterface(aIID);
+ },
+ };
+
+ const registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+
+ registrar.registerFactory(
+ MOCK_CID,
+ "alerts service",
+ ALERTS_SERVICE_CONTRACT_ID,
+ mockAlertsService
+ );
+
+ const { sendAsyncMessage } = this;
+
+ sendAsyncMessage("mock-alert-service:registered");
+ }
+
+ const MockAlertsService = {
+ async register() {
+ if (this._chromeScript) {
+ throw new Error("MockAlertsService already registered");
+ }
+ this._chromeScript = SpecialPowers.loadChromeScript(
+ mockServicesChromeScript
+ );
+ await this._chromeScript.promiseOneMessage("mock-alert-service:registered");
+ },
+ async unregistered() {
+ await this._chromeScript.promiseOneMessage(
+ "mock-alert-service:unregistered"
+ );
+ },
+ };
+
+ if (window.Notification) {
+ SimpleTest.waitForExplicitFinish();
+
+ async function showNotifications() {
+ await MockAlertsService.register();
+
+ // Load two frames with the same origin that create notification with the same tag.
+ // Both pages should generate notifications with the same name, and thus the second
+ // notification should replace the first.
+ let sameDomain = window.open("http://test1.example.org:80/tests/dom/notification/test/mochitest/create_notification.html");
+ let anotherSameDomain = window.open("http://test1.example.org:80/tests/dom/notification/test/mochitest/create_notification.html");
+ // Load a frame with a different origin that creates a notification with the same tag.
+ // The notification name should be different and thus no notifications should be replaced.
+ let crossDomain = window.open("http://test2.example.org:80/tests/dom/notification/test/mochitest/create_notification.html");
+
+ await MockAlertsService.unregistered();
+
+ sameDomain.close();
+ anotherSameDomain.close();
+ crossDomain.close();
+ SimpleTest.finish();
+ }
+
+ SpecialPowers.pushPrefEnv(
+ {
+ set: [
+ ["notification.prompt.testing", true],
+ ["notification.prompt.testing.allow", true],
+ ],
+ },
+ showNotifications
+ );
+ } else {
+ ok(true, "Notifications are not enabled on the platform.");
+ }
+</script>
+</body>
+</html>
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]