diff options
Diffstat (limited to 'dom/notification/test/mochitest')
10 files changed, 799 insertions, 0 deletions
diff --git a/dom/notification/test/mochitest/MockServices.js b/dom/notification/test/mochitest/MockServices.js new file mode 100644 index 0000000000..4b2526c26a --- /dev/null +++ b/dom/notification/test/mochitest/MockServices.js @@ -0,0 +1,176 @@ +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.toml b/dom/notification/test/mochitest/mochitest.toml new file mode 100644 index 0000000000..19320326fc --- /dev/null +++ b/dom/notification/test/mochitest/mochitest.toml @@ -0,0 +1,30 @@ +[DEFAULT] + +support-files = [ + "blank.html", + "create_notification.html", + "MockServices.js", + "NotificationTest.js", +] + +["test_notification_basics.html"] +skip-if = ["xorigin"] # Bug 1792790 + +["test_notification_crossorigin_iframe.html"] +scheme = "https" +# This test needs to be run on HTTP (not HTTPS). + +["test_notification_insecure_context.html"] +skip-if = [ + "http3", + "http2", +] + +["test_notification_permissions.html"] +scheme = "https" + +["test_notification_tag.html"] +skip-if = [ + "http3", + "http2", +] 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..0abfce3722 --- /dev/null +++ b/dom/notification/test/mochitest/test_notification_crossorigin_iframe.html @@ -0,0 +1,67 @@ +<!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.org/tests/dom/notification/test/mochitest/blank.html"; + + (async function runTest() { + await SpecialPowers.pushPrefEnv({"set": [ + ["notification.prompt.testing", true], + ["notification.prompt.testing.allow", 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..bb64db64a9 --- /dev/null +++ b/dom/notification/test/mochitest/test_notification_insecure_context.html @@ -0,0 +1,42 @@ +<!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"); + + 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..0290fb9c87 --- /dev/null +++ b/dom/notification/test/mochitest/test_notification_permissions.html @@ -0,0 +1,68 @@ +<!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: [ + // 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> |