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 --- .../browser_popupNotification_security_delay.js | 296 +++++++++++++++++++++ 1 file changed, 296 insertions(+) create mode 100644 browser/base/content/test/popupNotifications/browser_popupNotification_security_delay.js (limited to 'browser/base/content/test/popupNotifications/browser_popupNotification_security_delay.js') 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." + ); +}); -- cgit v1.2.3