416 lines
13 KiB
JavaScript
416 lines
13 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
|
|
|
export var PopupAndRedirectBlockerObserver = {
|
|
/**
|
|
* 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,
|
|
|
|
handleEvent(aEvent) {
|
|
switch (aEvent.type) {
|
|
case "DOMUpdateBlockedPopups":
|
|
this.onDOMUpdateBlockedPopupsAndRedirect(aEvent);
|
|
break;
|
|
case "DOMUpdateBlockedRedirect":
|
|
this.onDOMUpdateBlockedPopupsAndRedirect(aEvent);
|
|
break;
|
|
case "command":
|
|
this.onCommand(aEvent);
|
|
break;
|
|
case "popupshowing":
|
|
this.onPopupShowing(aEvent);
|
|
break;
|
|
case "popuphiding":
|
|
this.onPopupHiding(aEvent);
|
|
break;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Handles a "DOMUpdateBlockedPopups" or "DOMUpdateBlockedRedirect" event
|
|
* received from the JSWindowActorParent.
|
|
*
|
|
* @param {*} aEvent
|
|
*/
|
|
onDOMUpdateBlockedPopupsAndRedirect(aEvent) {
|
|
const window = aEvent.originalTarget.documentGlobal;
|
|
const { gBrowser, gPermissionPanel } = window;
|
|
if (aEvent.originalTarget != gBrowser.selectedBrowser) {
|
|
return;
|
|
}
|
|
|
|
gPermissionPanel.refreshPermissionIcons();
|
|
|
|
const popupCount =
|
|
gBrowser.selectedBrowser.popupAndRedirectBlocker.getBlockedPopupCount();
|
|
const isRedirectBlocked =
|
|
gBrowser.selectedBrowser.popupAndRedirectBlocker.isRedirectBlocked();
|
|
if (!popupCount && !isRedirectBlocked) {
|
|
this.hideNotification(gBrowser);
|
|
return;
|
|
}
|
|
|
|
if (Services.prefs.getBoolPref("privacy.popups.showBrowserMessage")) {
|
|
this.ensureInitializedForWindow(window);
|
|
this.showBrowserMessage(gBrowser, popupCount, isRedirectBlocked);
|
|
}
|
|
},
|
|
|
|
hideNotification(aBrowser) {
|
|
const notificationBox = aBrowser.getNotificationBox();
|
|
const notification =
|
|
notificationBox.getNotificationWithValue("popup-blocked");
|
|
if (notification) {
|
|
notificationBox.removeNotification(notification);
|
|
}
|
|
},
|
|
|
|
ensureInitializedForWindow(aWindow) {
|
|
const popup = aWindow.document.getElementById("blockedPopupOptions");
|
|
// Make sure we don't add the same event handlers multiple times.
|
|
if (popup.getAttribute("initialized")) {
|
|
return;
|
|
}
|
|
|
|
popup.setAttribute("initialized", true);
|
|
popup.addEventListener("command", this);
|
|
popup.addEventListener("popupshowing", this);
|
|
popup.addEventListener("popuphiding", this);
|
|
},
|
|
|
|
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
|
|
? "popup-warning-exceeded-with-redirect-message"
|
|
: "popup-warning-exceeded-message";
|
|
}
|
|
|
|
return aIsRedirectBlocked
|
|
? "redirect-warning-with-popup-message"
|
|
: "popup-warning-message";
|
|
})();
|
|
const label = {
|
|
"l10n-id": l10nId,
|
|
"l10n-args": {
|
|
popupCount: aPopupCount,
|
|
},
|
|
};
|
|
const notificationBox = aBrowser.getNotificationBox();
|
|
const notification = this.mNotificationPromise
|
|
? await this.mNotificationPromise
|
|
: notificationBox.getNotificationWithValue("popup-blocked");
|
|
if (notification) {
|
|
notification.label = label;
|
|
return;
|
|
}
|
|
|
|
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, eventCallback },
|
|
[
|
|
{
|
|
"l10n-id": "popup-warning-button",
|
|
popup: "blockedPopupOptions",
|
|
callback: null,
|
|
},
|
|
]
|
|
);
|
|
await this.mNotificationPromise;
|
|
this.mNotificationPromise = null;
|
|
},
|
|
|
|
/**
|
|
* Event handler that is triggered when a user clicks on the "options"
|
|
* button in the notification which opens a popup.
|
|
*
|
|
* @param {*} aEvent
|
|
*/
|
|
async onPopupShowing(aEvent) {
|
|
const window = aEvent.originalTarget.documentGlobal;
|
|
const { gBrowser, document } = window;
|
|
|
|
// We get `uriHost` from the principal whenever possible and fall
|
|
// back to the `spec` for special pages without a host, e.g. "about:".
|
|
const browser = gBrowser.selectedBrowser;
|
|
const uriOrPrincipal = browser.isContentPrincipal
|
|
? browser.contentPrincipal
|
|
: browser.currentURI;
|
|
const uriHost = uriOrPrincipal.asciiHost
|
|
? uriOrPrincipal.displayHost
|
|
: uriOrPrincipal.spec;
|
|
|
|
// "Allow pop-ups for site..."
|
|
const blockedPopupAllowSite = document.getElementById(
|
|
"blockedPopupAllowSite"
|
|
);
|
|
if (Services.prefs.prefIsLocked("dom.disable_open_during_load")) {
|
|
blockedPopupAllowSite.setAttribute("hidden", "true");
|
|
} else {
|
|
blockedPopupAllowSite.removeAttribute("hidden");
|
|
document.l10n.setAttributes(
|
|
blockedPopupAllowSite,
|
|
"popups-infobar-allow2",
|
|
{ uriHost }
|
|
);
|
|
}
|
|
|
|
// "Dont show this message when..."
|
|
const blockedPopupDontShowMessage = document.getElementById(
|
|
"blockedPopupDontShowMessage"
|
|
);
|
|
blockedPopupDontShowMessage.removeAttribute("checked");
|
|
|
|
gBrowser.selectedBrowser.popupAndRedirectBlocker
|
|
.getBlockedRedirect()
|
|
.then(blockedRedirect => {
|
|
this.onPopupShowingBlockedRedirect(blockedRedirect, window);
|
|
});
|
|
gBrowser.selectedBrowser.popupAndRedirectBlocker
|
|
.getBlockedPopups()
|
|
.then(blockedPopups => {
|
|
this.onPopupShowingBlockedPopups(blockedPopups, window);
|
|
});
|
|
},
|
|
|
|
onPopupShowingBlockedRedirect(aBlockedRedirect, aWindow) {
|
|
const { gBrowser, document } = aWindow;
|
|
const browser = gBrowser.selectedBrowser;
|
|
|
|
const blockedRedirectSeparator = document.getElementById(
|
|
"blockedRedirectSeparator"
|
|
);
|
|
blockedRedirectSeparator.hidden = !aBlockedRedirect;
|
|
|
|
if (!aBlockedRedirect) {
|
|
return;
|
|
}
|
|
|
|
// We may end up in a race condition and to avoid showing duplicate
|
|
// items, make sure the list is actually empty.
|
|
const nextElement = blockedRedirectSeparator.nextElementSibling;
|
|
if (nextElement?.hasAttribute("redirectInnerWindowId")) {
|
|
return;
|
|
}
|
|
|
|
const menuitem = document.createXULElement("menuitem");
|
|
document.l10n.setAttributes(menuitem, "popup-trigger-redirect-menuitem", {
|
|
redirectURI: aBlockedRedirect.redirectURISpec,
|
|
});
|
|
menuitem.setAttribute("redirectURISpec", aBlockedRedirect.redirectURISpec);
|
|
// Store the source inner window id, so we can check if the document
|
|
// that triggered the redirect is still the same.
|
|
menuitem.setAttribute(
|
|
"redirectInnerWindowId",
|
|
aBlockedRedirect.innerWindowId
|
|
);
|
|
// Store the browser for the current tab. The active tab may change,
|
|
// so we keep a reference to it.
|
|
menuitem.browser = browser;
|
|
// Same reason as source inner window id, we compare it with the one
|
|
// of the browsing context at the time of unblocking.
|
|
menuitem.browsingContext = aBlockedRedirect.browsingContext;
|
|
|
|
blockedRedirectSeparator.after(menuitem);
|
|
},
|
|
|
|
onPopupShowingBlockedPopups(aBlockedPopups, aWindow) {
|
|
const { gBrowser, document } = aWindow;
|
|
const browser = gBrowser.selectedBrowser;
|
|
|
|
const blockedPopupsSeparator = document.getElementById(
|
|
"blockedPopupsSeparator"
|
|
);
|
|
blockedPopupsSeparator.hidden = !aBlockedPopups.length;
|
|
|
|
if (!aBlockedPopups.length) {
|
|
return;
|
|
}
|
|
|
|
// We may end up in a race condition and to avoid showing duplicate
|
|
// items, make sure the list is actually empty.
|
|
const nextElement = blockedPopupsSeparator.nextElementSibling;
|
|
if (nextElement?.hasAttribute("popupInnerWindowId")) {
|
|
return;
|
|
}
|
|
|
|
for (let i = 0; i < aBlockedPopups.length; ++i) {
|
|
const blockedPopup = aBlockedPopups[i];
|
|
|
|
const menuitem = document.createXULElement("menuitem");
|
|
document.l10n.setAttributes(menuitem, "popup-show-popup-menuitem", {
|
|
popupURI: blockedPopup.popupWindowURISpec,
|
|
});
|
|
// The report index is the index into the blocked popup list
|
|
// maintained by the window where this popup was blocked.
|
|
menuitem.setAttribute("popupReportIndex", blockedPopup.reportIndex);
|
|
// Store the source inner window id, so we can check if the document
|
|
// that triggered the popup is still the same.
|
|
menuitem.setAttribute("popupInnerWindowId", blockedPopup.innerWindowId);
|
|
// Store the browser for the current tab. The active tab may change,
|
|
// so we keep a reference to it.
|
|
menuitem.browser = browser;
|
|
// Same reason as source inner window id, we compare it with the one
|
|
// of the browsing context at the time of unblocking.
|
|
menuitem.browsingContext = blockedPopup.browsingContext;
|
|
|
|
blockedPopupsSeparator.after(menuitem);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Event handler that is triggered when the "options" popup of the
|
|
* notification closes.
|
|
*
|
|
* @param {*} aEvent
|
|
*/
|
|
onPopupHiding(aEvent) {
|
|
const window = aEvent.originalTarget.documentGlobal;
|
|
const { document } = window;
|
|
|
|
// Remove the blocked redirect, if any.
|
|
const blockedRedirectSeparator = document.getElementById(
|
|
"blockedRedirectSeparator"
|
|
);
|
|
let item = blockedRedirectSeparator.nextElementSibling;
|
|
if (item?.hasAttribute("redirectInnerWindowId")) {
|
|
item.remove();
|
|
}
|
|
|
|
// Remove the blocked popups, if any.
|
|
const blockedPopupsSeparator = document.getElementById(
|
|
"blockedPopupsSeparator"
|
|
);
|
|
let next = null;
|
|
for (
|
|
item = blockedPopupsSeparator.nextElementSibling;
|
|
item?.hasAttribute("popupInnerWindowId");
|
|
item = next
|
|
) {
|
|
next = item.nextElementSibling;
|
|
item.remove();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Event handler that is triggered when a user clicks on one of the
|
|
* fields in the "options" popup of the notification.
|
|
*
|
|
* @param {*} aEvent
|
|
*/
|
|
onCommand(aEvent) {
|
|
if (aEvent.target.hasAttribute("popupReportIndex")) {
|
|
this.showBlockedPopup(aEvent);
|
|
return;
|
|
}
|
|
|
|
if (aEvent.target.hasAttribute("redirectURISpec")) {
|
|
this.navigateToBlockedRedirect(aEvent);
|
|
return;
|
|
}
|
|
|
|
switch (aEvent.target.id) {
|
|
case "blockedPopupAllowSite":
|
|
this.toggleAllowPopupsForSite(aEvent);
|
|
break;
|
|
case "blockedPopupEdit":
|
|
this.editPopupSettings(aEvent);
|
|
break;
|
|
case "blockedPopupDontShowMessage":
|
|
this.dontShowMessage(aEvent);
|
|
break;
|
|
}
|
|
},
|
|
|
|
showBlockedPopup(aEvent) {
|
|
const { browser, browsingContext } = aEvent.target;
|
|
const innerWindowId = aEvent.target.getAttribute("popupInnerWindowId");
|
|
const reportIndex = aEvent.target.getAttribute("popupReportIndex");
|
|
|
|
browser.popupAndRedirectBlocker.unblockPopup(
|
|
browsingContext,
|
|
innerWindowId,
|
|
reportIndex
|
|
);
|
|
},
|
|
|
|
navigateToBlockedRedirect(aEvent) {
|
|
const { browser, browsingContext } = aEvent.target;
|
|
const innerWindowId = aEvent.target.getAttribute("redirectInnerWindowId");
|
|
const redirectURISpec = aEvent.target.getAttribute("redirectURISpec");
|
|
|
|
browser.popupAndRedirectBlocker.unblockRedirect(
|
|
browsingContext,
|
|
innerWindowId,
|
|
redirectURISpec
|
|
);
|
|
},
|
|
|
|
async toggleAllowPopupsForSite(aEvent) {
|
|
const window = aEvent.originalTarget.documentGlobal;
|
|
const { gBrowser } = window;
|
|
|
|
if (Services.prefs.prefIsLocked("dom.disable_open_during_load")) {
|
|
return;
|
|
}
|
|
|
|
// The toggle should only be visible (and therefore clickable) if
|
|
// popups are currently blocked.
|
|
Services.perms.addFromPrincipal(
|
|
gBrowser.contentPrincipal,
|
|
"popup",
|
|
Services.perms.ALLOW_ACTION
|
|
);
|
|
gBrowser.getNotificationBox().removeCurrentNotification();
|
|
|
|
// The order is important here. We want to unblock all popups of the
|
|
// current document first and then potentially redirect somewhere
|
|
// else.
|
|
await gBrowser.selectedBrowser.popupAndRedirectBlocker.unblockAllPopups();
|
|
await gBrowser.selectedBrowser.popupAndRedirectBlocker.unblockFirstRedirect();
|
|
},
|
|
|
|
editPopupSettings(aEvent) {
|
|
const window = aEvent.originalTarget.documentGlobal;
|
|
const { openPreferences } = window;
|
|
|
|
openPreferences("privacy-permissions-block-popups");
|
|
},
|
|
|
|
dontShowMessage(aEvent) {
|
|
const window = aEvent.originalTarget.documentGlobal;
|
|
const { gBrowser } = window;
|
|
|
|
Services.prefs.setBoolPref("privacy.popups.showBrowserMessage", false);
|
|
gBrowser.getNotificationBox().removeCurrentNotification();
|
|
},
|
|
};
|
|
|
|
XPCOMUtils.defineLazyPreferenceGetter(
|
|
PopupAndRedirectBlockerObserver,
|
|
"maxReportedPopups",
|
|
"privacy.popups.maxReported"
|
|
);
|