Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

/* Any copyright is dedicated to the Public Domain.
"use strict";
const TEST_SECURITY_DELAY = 5000;
SimpleTest.requestCompleteLog();
/**
* 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: [
["test.wait300msAfterTabSwitch", true],
["security.notification_enable_delay", TEST_SECURITY_DELAY],
],
});
});
/**
* Tests that continuous rapid clicking during the security delay can never
* extend the wait beyond SECURITY_DELAY_EXTENSION_CAP_MULTIPLIER (20) times
* the configured delay. Without the cap (bug 2035581) every rejected click
* resets the deadline and the user can never dismiss the popup.
*
* Deterministic: we assert directly on the difference between timeShown and
* timeShownWithoutClickExtensions after a tight sync-click loop, then backdate
* timeShown past the cap and verify the next click fires. No wall-clock
* dependency, so this is robust on slow/debug builds.
*/
add_task(async function test_securityDelayHasHardCap() {
const SHORT_DELAY = 250;
const CAP_MULTIPLIER = 20;
const N_CLICKS = 30; // strictly greater than CAP_MULTIPLIER
await SpecialPowers.pushPrefEnv({
set: [["security.notification_enable_delay", SHORT_DELAY]],
});
await ensureSecurityDelayReady();
let popupShownPromise = waitForNotificationPanel();
showNotification();
await popupShownPromise;
let notification = PopupNotifications.getNotification(
"foo",
gBrowser.selectedBrowser
);
ok(notification, "Notification should be shown.");
ok(
notification.timeShownWithoutClickExtensions,
"timeShownWithoutClickExtensions anchor should be set on show."
);
// triggerSecondaryCommand -> synthesizeMouseAtCenter -> command event ->
// _onButtonEvent is all synchronous on the rejected-click path, so a tight
// loop saturates timeShown at the cap.
for (let i = 0; i < N_CLICKS; i++) {
triggerSecondaryCommand(PopupNotifications.panel, 0);
}
ok(
PopupNotifications.getNotification("foo", gBrowser.selectedBrowser),
"All clicks fell inside the delay; notification stays open."
);
let extension =
notification.timeShown - notification.timeShownWithoutClickExtensions;
Assert.lessOrEqual(
extension,
CAP_MULTIPLIER * SHORT_DELAY,
`timeShown extension (${extension}ms) is bounded by ` +
`${CAP_MULTIPLIER} * ${SHORT_DELAY}ms.`
);
// Backdate past the cap; the next click must fire and close the panel.
notification.timeShown = performance.now() - SHORT_DELAY * 2;
notification.timeShownWithoutClickExtensions = notification.timeShown;
let notificationHiddenPromise = waitForNotificationPanelHidden();
triggerSecondaryCommand(PopupNotifications.panel, 0);
await notificationHiddenPromise;
ok(
!PopupNotifications.getNotification("foo", gBrowser.selectedBrowser),
"Notification closes once timeShown is past the cap."
);
await SpecialPowers.popPrefEnv();
});
/**
* Tests that an action with disableSecurityDelay: true fires immediately,
* skipping the security delay entirely.
*/
add_task(async function test_disableSecurityDelayAction_immediate() {
await ensureSecurityDelayReady();
let popupShownPromise = waitForNotificationPanel();
PopupNotifications.show(
gBrowser.selectedBrowser,
"foo",
"Hello, World!",
"default-notification-icon",
{
label: "ok",
accessKey: "o",
callback: () => {},
},
[
{
label: "cancel",
accessKey: "c",
disableSecurityDelay: true,
callback: () => {},
},
],
{ persistent: true }
);
await popupShownPromise;
ok(
PopupNotifications.isPanelOpen,
"PopupNotification should be open after show call."
);
let notificationHiddenPromise = waitForNotificationPanelHidden();
// Click immediately, well before security.notification_enable_delay
// (set to TEST_SECURITY_DELAY = 5000 ms in add_setup).
triggerSecondaryCommand(PopupNotifications.panel, 0);
await notificationHiddenPromise;
ok(
!PopupNotifications.getNotification("foo", gBrowser.selectedBrowser),
"Action with disableSecurityDelay should fire immediately."
);
});
/**
* Regression guard: a secondary action without disableSecurityDelay still
* respects the security delay.
*/
add_task(async function test_unflaggedSecondaryActionStillRespectsDelay() {
await ensureSecurityDelayReady();
let popupShownPromise = waitForNotificationPanel();
showNotification();
await popupShownPromise;
info("Click secondary action immediately; should be blocked by delay.");
triggerSecondaryCommand(PopupNotifications.panel, 0);
await new Promise(resolve => setTimeout(resolve, 0));
let notification = PopupNotifications.getNotification(
"foo",
gBrowser.selectedBrowser
);
ok(
notification,
"Unflagged secondary action should still be blocked by the delay."
);
// Backdate timeShown / timeShownWithoutClickExtensions past the delay and
// click again.
let fakeTimeShown = TEST_SECURITY_DELAY + 500;
notification.timeShown = performance.now() - fakeTimeShown;
notification.timeShownWithoutClickExtensions = notification.timeShown;
let notificationHiddenPromise = waitForNotificationPanelHidden();
triggerSecondaryCommand(PopupNotifications.panel, 0);
await notificationHiddenPromise;
ok(
!PopupNotifications.getNotification("foo", gBrowser.selectedBrowser),
"Should dismiss once outside the delay."
);
});