summaryrefslogtreecommitdiffstats
path: root/browser/base/content/test/popupNotifications/browser_popupNotification_security_delay.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/base/content/test/popupNotifications/browser_popupNotification_security_delay.js')
-rw-r--r--browser/base/content/test/popupNotifications/browser_popupNotification_security_delay.js296
1 files changed, 296 insertions, 0 deletions
diff --git a/browser/base/content/test/popupNotifications/browser_popupNotification_security_delay.js b/browser/base/content/test/popupNotifications/browser_popupNotification_security_delay.js
new file mode 100644
index 0000000000..515895f35a
--- /dev/null
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_security_delay.js
@@ -0,0 +1,296 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_SECURITY_DELAY = 5000;
+
+/**
+ * Shows a test PopupNotification.
+ */
+function showNotification() {
+ PopupNotifications.show(
+ gBrowser.selectedBrowser,
+ "foo",
+ "Hello, World!",
+ "default-notification-icon",
+ {
+ label: "ok",
+ accessKey: "o",
+ callback: () => {},
+ },
+ [
+ {
+ label: "cancel",
+ accessKey: "c",
+ callback: () => {},
+ },
+ ],
+ {
+ // Make test notifications persistent to ensure they are only closed
+ // explicitly by test actions and survive tab switches.
+ persistent: true,
+ }
+ );
+}
+
+add_setup(async function () {
+ // Set a longer security delay for PopupNotification actions so we can test
+ // the delay even if the test runs slowly.
+ await SpecialPowers.pushPrefEnv({
+ set: [["security.notification_enable_delay", TEST_SECURITY_DELAY]],
+ });
+});
+
+async function ensureSecurityDelayReady() {
+ /**
+ * The security delay calculation in PopupNotification.sys.mjs is dependent on
+ * the monotonically increasing value of performance.now. This timestamp is
+ * not relative to a fixed date, but to runtime.
+ * We need to wait for the value performance.now() to be larger than the
+ * security delay in order to observe the bug. Only then does the
+ * timeSinceShown check in PopupNotifications.sys.mjs lead to a timeSinceShown
+ * value that is unconditionally greater than lazy.buttonDelay for
+ * notification.timeShown = null = 0.
+ * See: https://searchfox.org/mozilla-central/rev/f32d5f3949a3f4f185122142b29f2e3ab776836e/toolkit/modules/PopupNotifications.sys.mjs#1870-1872
+ *
+ * When running in automation as part of a larger test suite performance.now()
+ * should usually be already sufficiently high in which case this check should
+ * directly resolve.
+ */
+ await TestUtils.waitForCondition(
+ () => performance.now() > TEST_SECURITY_DELAY,
+ "Wait for performance.now() > SECURITY_DELAY",
+ 500,
+ 50
+ );
+}
+
+/**
+ * Tests that when we show a second notification while the panel is open the
+ * timeShown attribute is correctly set and the security delay is enforced
+ * properly.
+ */
+add_task(async function test_timeShownMultipleNotifications() {
+ await ensureSecurityDelayReady();
+
+ ok(
+ !PopupNotifications.isPanelOpen,
+ "PopupNotification panel should not be open initially."
+ );
+
+ info("Open the first notification.");
+ let popupShownPromise = waitForNotificationPanel();
+ showNotification();
+ await popupShownPromise;
+ ok(
+ PopupNotifications.isPanelOpen,
+ "PopupNotification should be open after first show call."
+ );
+
+ is(
+ PopupNotifications._currentNotifications.length,
+ 1,
+ "There should only be one notification"
+ );
+
+ let notification = PopupNotifications.getNotification(
+ "foo",
+ gBrowser.selectedBrowser
+ );
+ is(notification?.id, "foo", "There should be a notification with id foo");
+ ok(notification.timeShown, "The notification should have timeShown set");
+
+ info(
+ "Call show again with the same notification id while the PopupNotification panel is still open."
+ );
+ showNotification();
+ ok(
+ PopupNotifications.isPanelOpen,
+ "PopupNotification should still open after second show call."
+ );
+ notification = PopupNotifications.getNotification(
+ "foo",
+ gBrowser.selectedBrowser
+ );
+ is(
+ PopupNotifications._currentNotifications.length,
+ 1,
+ "There should still only be one notification"
+ );
+
+ is(
+ notification?.id,
+ "foo",
+ "There should still be a notification with id foo"
+ );
+ ok(notification.timeShown, "The notification should have timeShown set");
+
+ let notificationHiddenPromise = waitForNotificationPanelHidden();
+
+ info("Trigger main action via button click during security delay");
+ triggerMainCommand(PopupNotifications.panel);
+
+ await new Promise(resolve => setTimeout(resolve, 0));
+
+ ok(PopupNotifications.isPanelOpen, "PopupNotification should still be open.");
+ notification = PopupNotifications.getNotification(
+ "foo",
+ gBrowser.selectedBrowser
+ );
+ ok(
+ notification,
+ "Notification should still be open because we clicked during the security delay."
+ );
+
+ // If the notification is no longer shown (test failure) skip the remaining
+ // checks.
+ if (!notification) {
+ return;
+ }
+
+ // Ensure that once the security delay has passed the notification can be
+ // closed again.
+ let fakeTimeShown = TEST_SECURITY_DELAY + 500;
+ info(`Manually set timeShown to ${fakeTimeShown}ms in the past.`);
+ notification.timeShown = performance.now() - fakeTimeShown;
+
+ info("Trigger main action via button click outside security delay");
+ triggerMainCommand(PopupNotifications.panel);
+
+ info("Wait for panel to be hidden.");
+ await notificationHiddenPromise;
+
+ ok(
+ !PopupNotifications.getNotification("foo", gBrowser.selectedBrowser),
+ "Should not longer see the notification."
+ );
+});
+
+/**
+ * Tests that when we reshow a notification after a tab switch the timeShown
+ * attribute is correctly reset and the security delay is enforced.
+ */
+add_task(async function test_notificationReshowTabSwitch() {
+ await ensureSecurityDelayReady();
+
+ ok(
+ !PopupNotifications.isPanelOpen,
+ "PopupNotification panel should not be open initially."
+ );
+
+ info("Open the first notification.");
+ let popupShownPromise = waitForNotificationPanel();
+ showNotification();
+ await popupShownPromise;
+ ok(
+ PopupNotifications.isPanelOpen,
+ "PopupNotification should be open after first show call."
+ );
+
+ let notification = PopupNotifications.getNotification(
+ "foo",
+ gBrowser.selectedBrowser
+ );
+ is(notification?.id, "foo", "There should be a notification with id foo");
+ ok(notification.timeShown, "The notification should have timeShown set");
+
+ info("Trigger main action via button click during security delay");
+ triggerMainCommand(PopupNotifications.panel);
+
+ await new Promise(resolve => setTimeout(resolve, 0));
+
+ ok(PopupNotifications.isPanelOpen, "PopupNotification should still be open.");
+ notification = PopupNotifications.getNotification(
+ "foo",
+ gBrowser.selectedBrowser
+ );
+ ok(
+ notification,
+ "Notification should still be open because we clicked during the security delay."
+ );
+
+ // If the notification is no longer shown (test failure) skip the remaining
+ // checks.
+ if (!notification) {
+ return;
+ }
+
+ let panelHiddenPromise = waitForNotificationPanelHidden();
+ let panelShownPromise;
+
+ info("Open a new tab which hides the notification panel.");
+ await BrowserTestUtils.withNewTab("https://example.com", async browser => {
+ info("Wait for panel to be hidden by tab switch.");
+ await panelHiddenPromise;
+ info(
+ "Keep the tab open until the security delay for the original notification show has expired."
+ );
+ await new Promise(resolve =>
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ setTimeout(resolve, TEST_SECURITY_DELAY + 500)
+ );
+
+ panelShownPromise = waitForNotificationPanel();
+ });
+ info(
+ "Wait for the panel to show again after the tab close. We're showing the original tab again."
+ );
+ await panelShownPromise;
+
+ ok(
+ PopupNotifications.isPanelOpen,
+ "PopupNotification should be shown after tab close."
+ );
+ notification = PopupNotifications.getNotification(
+ "foo",
+ gBrowser.selectedBrowser
+ );
+ is(
+ notification?.id,
+ "foo",
+ "There should still be a notification with id foo"
+ );
+
+ let notificationHiddenPromise = waitForNotificationPanelHidden();
+
+ info(
+ "Because we re-show the panel after tab close / switch the security delay should have reset."
+ );
+ info("Trigger main action via button click during the new security delay.");
+ triggerMainCommand(PopupNotifications.panel);
+
+ await new Promise(resolve => setTimeout(resolve, 0));
+
+ ok(PopupNotifications.isPanelOpen, "PopupNotification should still be open.");
+ notification = PopupNotifications.getNotification(
+ "foo",
+ gBrowser.selectedBrowser
+ );
+ ok(
+ notification,
+ "Notification should still be open because we clicked during the security delay."
+ );
+ // If the notification is no longer shown (test failure) skip the remaining
+ // checks.
+ if (!notification) {
+ return;
+ }
+
+ // Ensure that once the security delay has passed the notification can be
+ // closed again.
+ let fakeTimeShown = TEST_SECURITY_DELAY + 500;
+ info(`Manually set timeShown to ${fakeTimeShown}ms in the past.`);
+ notification.timeShown = performance.now() - fakeTimeShown;
+
+ info("Trigger main action via button click outside security delay");
+ triggerMainCommand(PopupNotifications.panel);
+
+ info("Wait for panel to be hidden.");
+ await notificationHiddenPromise;
+
+ ok(
+ !PopupNotifications.getNotification("foo", gBrowser.selectedBrowser),
+ "Should not longer see the notification."
+ );
+});