Bug 1894203 - Add a helper for popup notification security delay test. r=Gijs

Differential Revision: https://phabricator.services.mozilla.com/D208989
This commit is contained in:
Paul Zuehlcke 2024-05-07 20:05:45 +00:00
parent 9a2997ade9
commit 70ffe53fab

View file

@ -44,7 +44,6 @@ add_setup(async function () {
});
});
async function ensureSecurityDelayReady() {
/**
* The security delay calculation in PopupNotification.sys.mjs is dependent on
* the monotonically increasing value of performance.now. This timestamp is
@ -60,6 +59,7 @@ async function ensureSecurityDelayReady() {
* should usually be already sufficiently high in which case this check should
* directly resolve.
*/
async function ensureSecurityDelayReady() {
await TestUtils.waitForCondition(
() => performance.now() > TEST_SECURITY_DELAY,
"Wait for performance.now() > SECURITY_DELAY",
@ -68,6 +68,116 @@ async function ensureSecurityDelayReady() {
);
}
/**
* Test helper for security delay tests which performs the following steps:
* 1. Shows a test notification.
* 2. Waits for the notification panel to be shown and checks that the initial
* security delay blocks clicks.
* 3. Waits for the security delay to expire. Clicks should now be allowed.
* 4. Executes the provided onSecurityDelayExpired function. This function
* should renew the security delay.
* 5. Tests that the security delay was renewed.
* 6. Ensures that after the security delay the notification can be closed.
*
* @param {*} options
* @param {function} options.onSecurityDelayExpired - Function to run after the
* security delay has expired. This function should trigger a renew of the
* security delay.
* @param {function} options.cleanupFn - Optional cleanup function to run after
* the test has completed.
* @returns {Promise<void>} - Resolves when the test has completed.
*/
async function runPopupNotificationSecurityDelayTest({
onSecurityDelayExpired,
cleanupFn = () => {},
}) {
await ensureSecurityDelayReady();
info("Open a notification.");
let popupShownPromise = waitForNotificationPanel();
showNotification();
await popupShownPromise;
ok(
PopupNotifications.isPanelOpen,
"PopupNotification should be open after show call."
);
// Test that the initial security delay works.
info(
"Trigger main action via button click during the initial security delay."
);
triggerMainCommand(PopupNotifications.panel);
await new Promise(resolve => setTimeout(resolve, 0));
ok(PopupNotifications.isPanelOpen, "PopupNotification should still be open.");
let 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) {
await cleanupFn();
return;
}
info("Wait for security delay to expire.");
await new Promise(resolve =>
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
setTimeout(resolve, TEST_SECURITY_DELAY + 500)
);
info("Run test specific actions which restarts the security delay.");
await onSecurityDelayExpired();
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) {
await cleanupFn();
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");
let notificationHiddenPromise = waitForNotificationPanelHidden();
triggerMainCommand(PopupNotifications.panel);
info("Wait for panel to be hidden.");
await notificationHiddenPromise;
ok(
!PopupNotifications.getNotification("foo", gBrowser.selectedBrowser),
"Should no longer see the notification."
);
info("Cleanup.");
await cleanupFn();
}
/**
* 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
@ -170,7 +280,7 @@ add_task(async function test_timeShownMultipleNotifications() {
ok(
!PopupNotifications.getNotification("foo", gBrowser.selectedBrowser),
"Should not longer see the notification."
"Should no longer see the notification."
);
});
@ -179,50 +289,8 @@ add_task(async function test_timeShownMultipleNotifications() {
* 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;
}
await runPopupNotificationSecurityDelayTest({
onSecurityDelayExpired: async () => {
let panelHiddenPromise = waitForNotificationPanelHidden();
let panelShownPromise;
@ -230,14 +298,6 @@ add_task(async function test_notificationReshowTabSwitch() {
await BrowserTestUtils.withNewTab("https://example.com", async () => {
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(
@ -249,7 +309,7 @@ add_task(async function test_notificationReshowTabSwitch() {
PopupNotifications.isPanelOpen,
"PopupNotification should be shown after tab close."
);
notification = PopupNotifications.getNotification(
let notification = PopupNotifications.getNotification(
"foo",
gBrowser.selectedBrowser
);
@ -259,47 +319,11 @@ add_task(async function test_notificationReshowTabSwitch() {
"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."
);
},
});
});
/**
@ -307,47 +331,14 @@ add_task(async function test_notificationReshowTabSwitch() {
* the PopupNotifications panel position is updated.
*/
add_task(async function test_notificationWindowMove() {
await ensureSecurityDelayReady();
info("Open a notification.");
let popupShownPromise = waitForNotificationPanel();
showNotification();
await popupShownPromise;
ok(
PopupNotifications.isPanelOpen,
"PopupNotification should be open after show call."
);
// Test that the initial security delay works.
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.");
let 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;
}
info("Wait for security delay to expire.");
await new Promise(resolve =>
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
setTimeout(resolve, TEST_SECURITY_DELAY + 500)
);
let screenX, screenY;
await runPopupNotificationSecurityDelayTest({
onSecurityDelayExpired: async () => {
info("Reposition the window");
// Remember original window position.
let { screenX, screenY } = window;
screenX = window.screenX;
screenY = window.screenY;
let promisePopupPositioned = BrowserTestUtils.waitForEvent(
PopupNotifications.panel,
@ -360,47 +351,12 @@ add_task(async function test_notificationWindowMove() {
// Wait for the panel to reposition and the PopupNotifications listener to run.
await promisePopupPositioned;
await new Promise(resolve => setTimeout(resolve, 0));
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");
let notificationHiddenPromise = waitForNotificationPanelHidden();
triggerMainCommand(PopupNotifications.panel);
info("Wait for panel to be hidden.");
await notificationHiddenPromise;
ok(
!PopupNotifications.getNotification("foo", gBrowser.selectedBrowser),
"Should not longer see the notification."
);
},
cleanupFn: async () => {
// Reset window position
window.moveTo(screenX, screenY);
},
});
});
/**
@ -563,6 +519,8 @@ add_task(async function test_notificationDuringFullScreenTransition() {
info("Wait for full screen transition end.");
await promiseFullScreenTransitionEnd;
info("Full screen transition end");
await SpecialPowers.popPrefEnv();
});
});
@ -576,46 +534,14 @@ add_task(async function test_notificationPointerLock() {
"https://example.com"
);
await ensureSecurityDelayReady();
info("Open a notification.");
let popupShownPromise = waitForNotificationPanel();
showNotification();
await popupShownPromise;
ok(
PopupNotifications.isPanelOpen,
"PopupNotification should be open after show call."
);
// Test that the initial security delay works.
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.");
let 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;
}
info("Wait for security delay to expire.");
await new Promise(resolve =>
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
setTimeout(resolve, TEST_SECURITY_DELAY + 500)
);
await runPopupNotificationSecurityDelayTest({
onSecurityDelayExpired: async () => {
info("Enter pointer lock");
let pointerLockEnterPromise = TestUtils.topicObserved("pointer-lock-entered");
// Move focus to the browser to ensure pointer lock request succeeds.
gBrowser.selectedBrowser.focus();
let pointerLockEnterPromise = TestUtils.topicObserved(
"pointer-lock-entered"
);
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => {
SpecialPowers.wrap(content.document).notifyUserGestureActivation();
await content.document.body.requestPointerLock();
@ -624,45 +550,8 @@ add_task(async function test_notificationPointerLock() {
// Wait for pointer lock to be entered and the PopupNotifications listener to run.
await pointerLockEnterPromise;
await new Promise(resolve => setTimeout(resolve, 0));
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");
let notificationHiddenPromise = waitForNotificationPanelHidden();
triggerMainCommand(PopupNotifications.panel);
info("Wait for panel to be hidden.");
await notificationHiddenPromise;
ok(
!PopupNotifications.getNotification("foo", gBrowser.selectedBrowser),
"Should not longer see the notification."
);
},
cleanupFn: async () => {
// Cleanup.
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => {
SpecialPowers.wrap(content.document).notifyUserGestureActivation();
@ -673,4 +562,6 @@ add_task(async function test_notificationPointerLock() {
"Wait for pointer lock exit."
);
BrowserTestUtils.removeTab(tab);
},
});
});