commit 7335419dc74e51557d757727b705952bab8bc7c9
parent f1edecc8d8b622292ad619d6542bea759df005d2
Author: moz-mdauer <mdauer@mozilla.com>
Date: Tue, 23 Dec 2025 18:30:50 +0000
Bug 2006600 - Keep track of dismissed popup-blocker notifications, r=emz
Differential Revision: https://phabricator.services.mozilla.com/D276969
Diffstat:
3 files changed, 108 insertions(+), 7 deletions(-)
diff --git a/browser/base/content/test/popups/browser_popup_blocker.js b/browser/base/content/test/popups/browser_popup_blocker.js
@@ -107,6 +107,56 @@ add_task(async function test_opening_blocked_popups_about_privatebrowsing() {
await testPopupBlockingToolbar(tab);
});
+// Bug 2006600.
+// When a notification has been dismissed by a user, it should not appear
+// again when switching to a different tab and back.
+add_task(async function test_dismissed_notification_switch_tabs() {
+ // Open the test page.
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ baseURL + "popup_blocker.html"
+ );
+
+ // Wait for the notification.
+ let notification;
+ await TestUtils.waitForCondition(
+ () =>
+ (notification = gBrowser
+ .getNotificationBox()
+ .getNotificationWithValue("popup-blocked"))
+ );
+
+ // Click dismiss button.
+ const mozButton = notification.shadowRoot.querySelector("moz-button.close");
+ mozButton.click();
+
+ // Open a new (foreground) tab and switch back.
+ const differentTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+ await BrowserTestUtils.switchTab(gBrowser, tab);
+
+ // Make sure no notification appears.
+ try {
+ await TestUtils.waitForCondition(
+ () =>
+ (notification = gBrowser
+ .getNotificationBox()
+ .getNotificationWithValue("popup-blocked")),
+ null,
+ 50,
+ 10
+ );
+ } catch (e) {
+ notification = null;
+ }
+ ok(!notification, "Notification should not reappear");
+
+ gBrowser.removeTab(tab);
+ gBrowser.removeTab(differentTab);
+});
+
async function testPopupBlockingToolbar(tab) {
let win = tab.ownerGlobal;
// Wait for the popup-blocked notification.
diff --git a/browser/modules/PopupAndRedirectBlockerObserver.sys.mjs b/browser/modules/PopupAndRedirectBlockerObserver.sys.mjs
@@ -6,11 +6,10 @@ import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
export var PopupAndRedirectBlockerObserver = {
/**
- * This is to check if we are currently in the process of appending a
- * notification.
- * `NotificationBox.appendNotification()` runs asynchronously and
- * returns a promise. While it is resolving, `NotificationBox.getNotificationWithValue()`
- * will still return null.
+ * Check if we are currently in the process of appending a notification.
+ * We can't rely on `getNotificationWithValue()`: It returns `null`
+ * while `appendNotification()` is resolving, so we keep track of the
+ * promise instead.
*/
mNotificationPromise: null,
@@ -87,6 +86,15 @@ export var PopupAndRedirectBlockerObserver = {
},
async showBrowserMessage(aBrowser, aPopupCount, aIsRedirectBlocked) {
+ const selectedBrowser = aBrowser.selectedBrowser;
+ const popupAndRedirectBlocker = selectedBrowser.popupAndRedirectBlocker;
+
+ // Check if the notification was previously shown and then dismissed
+ // by the user.
+ if (popupAndRedirectBlocker.hasBeenDismissed()) {
+ return;
+ }
+
const l10nId = (() => {
if (aPopupCount >= this.maxReportedPopups) {
return aIsRedirectBlocked
@@ -115,9 +123,13 @@ export var PopupAndRedirectBlockerObserver = {
const image = "chrome://browser/skin/notification-icons/popup.svg";
const priority = notificationBox.PRIORITY_INFO_MEDIUM;
+ const eventCallback = popupAndRedirectBlocker.eventCallback.bind(
+ popupAndRedirectBlocker
+ );
+
this.mNotificationPromise = notificationBox.appendNotification(
"popup-blocked",
- { label, image, priority },
+ { label, image, priority, eventCallback },
[
{
"l10n-id": "popup-warning-button",
diff --git a/toolkit/actors/PopupAndRedirectBlockingParent.sys.mjs b/toolkit/actors/PopupAndRedirectBlockingParent.sys.mjs
@@ -25,11 +25,20 @@ export class PopupAndRedirectBlocker {
* @type {WeakMap<WindowGlobalParent, BrowsingContext>}
*/
#mBlockedRedirects;
+ /**
+ * WeakSet of all the browser's top-level WindowGlobal objects that had
+ * their notification dismissed by the user.
+ * If it has been dismissed once, we don't want to show it again.
+ *
+ * @type {WeakSet<WindowGlobalParent>}
+ */
+ #mHasBeenDismissed;
constructor(aBrowser) {
this.#mBrowser = aBrowser;
this.#mBlockedPopupCounts = new WeakMap();
this.#mBlockedRedirects = new WeakMap();
+ this.#mHasBeenDismissed = new WeakSet();
}
getBlockedPopupCount() {
@@ -60,12 +69,40 @@ export class PopupAndRedirectBlocker {
const browserBC = this.#mBrowser.browsingContext;
const browserWG = browserBC.currentWindowGlobal;
if (!browserWG) {
- return null;
+ return false;
}
return this.#mBlockedRedirects.has(browserWG);
}
+ hasBeenDismissed() {
+ const browserBC = this.#mBrowser.browsingContext;
+ const browserWG = browserBC.currentWindowGlobal;
+ if (!browserWG) {
+ return false;
+ }
+
+ return this.#mHasBeenDismissed.has(browserWG);
+ }
+
+ /**
+ * Event callback for the notification that is shown when a popup or
+ * redirect is blocked. This is used in the observer.
+ *
+ * @param {*} reason
+ */
+ eventCallback(reason) {
+ if (reason == "dismissed") {
+ const browserBC = this.#mBrowser.browsingContext;
+ const browserWG = browserBC.currentWindowGlobal;
+ if (!browserWG) {
+ return;
+ }
+
+ this.#mHasBeenDismissed.add(browserWG);
+ }
+ }
+
async getBlockedPopups() {
const contextsToVisit = [this.#mBrowser.browsingContext];
const result = [];
@@ -223,6 +260,8 @@ export class PopupAndRedirectBlocker {
this.#mBlockedRedirects.delete(browserWG);
this.sendObserverUpdateBlockedRedirectEvent();
+
+ this.#mHasBeenDismissed.delete(browserWG);
}
}
}