Bug 1576917 - Port PopupBlocker to JSWindowActors to make it Fission-compatible. r=NeilDeakin

This patch was started by Alex Vamvounis <a.vamvounis@gmail.com> and finished by
Mike Conley <mconley@mozilla.com>

Differential Revision: https://phabricator.services.mozilla.com/D53075

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Mike Conley 2020-01-22 21:24:19 +00:00
Родитель ef317eb299
Коммит ea70cbeec9
13 изменённых файлов: 520 добавлений и 296 удалений

Просмотреть файл

@ -906,10 +906,7 @@ var gIdentityHandler = {
// Show blocked popup icon in the identity-box if popups are blocked
// irrespective of popup permission capability value.
if (
gBrowser.selectedBrowser.blockedPopups &&
gBrowser.selectedBrowser.blockedPopups.length
) {
if (gBrowser.selectedBrowser.popupBlocker.getBlockedPopupCount()) {
let icon = permissionAnchors.popup;
icon.setAttribute("showing", "true");
}
@ -1398,6 +1395,7 @@ var gIdentityHandler = {
}
}
let totalBlockedPopups = gBrowser.selectedBrowser.popupBlocker.getBlockedPopupCount();
let hasBlockedPopupIndicator = false;
for (let permission of permissions) {
if (permission.id == "storage-access") {
@ -1411,12 +1409,8 @@ var gIdentityHandler = {
}
this._permissionList.appendChild(item);
if (
permission.id == "popup" &&
gBrowser.selectedBrowser.blockedPopups &&
gBrowser.selectedBrowser.blockedPopups.length
) {
this._createBlockedPopupIndicator();
if (permission.id == "popup" && totalBlockedPopups) {
this._createBlockedPopupIndicator(totalBlockedPopups);
hasBlockedPopupIndicator = true;
} else if (
permission.id == "geo" &&
@ -1426,11 +1420,7 @@ var gIdentityHandler = {
}
}
if (
gBrowser.selectedBrowser.blockedPopups &&
gBrowser.selectedBrowser.blockedPopups.length &&
!hasBlockedPopupIndicator
) {
if (totalBlockedPopups && !hasBlockedPopupIndicator) {
let permission = {
id: "popup",
state: SitePermissions.getDefault("popup"),
@ -1438,7 +1428,7 @@ var gIdentityHandler = {
};
let item = this._createPermissionItem(permission);
this._permissionList.appendChild(item);
this._createBlockedPopupIndicator();
this._createBlockedPopupIndicator(totalBlockedPopups);
}
// Show a placeholder text if there's no permission and no reload hint.
@ -1757,7 +1747,7 @@ var gIdentityHandler = {
.appendChild(indicator);
},
_createBlockedPopupIndicator() {
_createBlockedPopupIndicator(aTotalBlockedPopups) {
let indicator = document.createXULElement("hbox");
indicator.setAttribute("class", "identity-popup-permission-item");
indicator.setAttribute("align", "center");
@ -1770,18 +1760,17 @@ var gIdentityHandler = {
text.setAttribute("flex", "1");
text.setAttribute("class", "identity-popup-permission-label");
let popupCount = gBrowser.selectedBrowser.blockedPopups.length;
let messageBase = gNavigatorBundle.getString(
"popupShowBlockedPopupsIndicatorText"
);
let message = PluralForm.get(popupCount, messageBase).replace(
let message = PluralForm.get(aTotalBlockedPopups, messageBase).replace(
"#1",
popupCount
aTotalBlockedPopups
);
text.textContent = message;
text.addEventListener("click", () => {
gPopupBlockerObserver.showAllBlockedPopups(gBrowser.selectedBrowser);
gBrowser.selectedBrowser.popupBlocker.unblockAllPopups();
});
indicator.appendChild(icon);

Просмотреть файл

@ -948,10 +948,9 @@ var gPopupBlockerObserver = {
gIdentityHandler.refreshIdentityBlock();
if (
!gBrowser.selectedBrowser.blockedPopups ||
!gBrowser.selectedBrowser.blockedPopups.length
) {
let popupCount = gBrowser.selectedBrowser.popupBlocker.getBlockedPopupCount();
if (!popupCount) {
// Hide the notification box (if it's visible).
let notificationBox = gBrowser.getNotificationBox();
let notification = notificationBox.getNotificationWithValue(
@ -966,11 +965,10 @@ var gPopupBlockerObserver = {
// Only show the notification again if we've not already shown it. Since
// notifications are per-browser, we don't need to worry about re-adding
// it.
if (!gBrowser.selectedBrowser.blockedPopups.reported) {
if (gBrowser.selectedBrowser.popupBlocker.shouldShowNotification) {
if (Services.prefs.getBoolPref("privacy.popups.showBrowserMessage")) {
var brandBundle = document.getElementById("bundle_brand");
var brandShortName = brandBundle.getString("brandShortName");
var popupCount = gBrowser.selectedBrowser.blockedPopups.length;
var stringKey =
AppConstants.platform == "win"
@ -1024,7 +1022,7 @@ var gPopupBlockerObserver = {
// Record the fact that we've reported this blocked popup, so we don't
// show it again.
gBrowser.selectedBrowser.blockedPopups.reported = true;
gBrowser.selectedBrowser.popupBlocker.didShowNotification();
}
},
@ -1035,7 +1033,7 @@ var gPopupBlockerObserver = {
pm.addFromPrincipal(gBrowser.contentPrincipal, "popup", perm);
if (!shouldBlock) {
this.showAllBlockedPopups(gBrowser.selectedBrowser);
gBrowser.selectedBrowser.popupBlocker.unblockAllPopups();
}
gBrowser.getNotificationBox().removeCurrentNotification();
@ -1106,65 +1104,68 @@ var gPopupBlockerObserver = {
);
blockedPopupsSeparator.setAttribute("hidden", true);
gBrowser.selectedBrowser
.retrieveListOfBlockedPopups()
.then(blockedPopups => {
let foundUsablePopupURI = false;
if (blockedPopups) {
for (let i = 0; i < blockedPopups.length; i++) {
let blockedPopup = blockedPopups[i];
browser.popupBlocker.getBlockedPopups().then(blockedPopups => {
let foundUsablePopupURI = false;
if (blockedPopups) {
for (let i = 0; i < blockedPopups.length; i++) {
let blockedPopup = blockedPopups[i];
// popupWindowURI will be null if the file picker popup is blocked.
// xxxdz this should make the option say "Show file picker" and do it (Bug 590306)
if (!blockedPopup.popupWindowURIspec) {
continue;
}
var popupURIspec = blockedPopup.popupWindowURIspec;
// Sometimes the popup URI that we get back from the blockedPopup
// isn't useful (for instance, netscape.com's popup URI ends up
// being "http://www.netscape.com", which isn't really the URI of
// the popup they're trying to show). This isn't going to be
// useful to the user, so we won't create a menu item for it.
if (
popupURIspec == "" ||
popupURIspec == "about:blank" ||
popupURIspec == "<self>" ||
popupURIspec == uri.spec
) {
continue;
}
// Because of the short-circuit above, we may end up in a situation
// in which we don't have any usable popup addresses to show in
// the menu, and therefore we shouldn't show the separator. However,
// since we got past the short-circuit, we must've found at least
// one usable popup URI and thus we'll turn on the separator later.
foundUsablePopupURI = true;
var menuitem = document.createXULElement("menuitem");
var label = gNavigatorBundle.getFormattedString(
"popupShowPopupPrefix",
[popupURIspec]
);
menuitem.setAttribute("label", label);
menuitem.setAttribute(
"oncommand",
"gPopupBlockerObserver.showBlockedPopup(event);"
);
menuitem.setAttribute("popupReportIndex", i);
menuitem.popupReportBrowser = browser;
aEvent.target.appendChild(menuitem);
// popupWindowURI will be null if the file picker popup is blocked.
// xxxdz this should make the option say "Show file picker" and do it (Bug 590306)
if (!blockedPopup.popupWindowURISpec) {
continue;
}
}
// Show the separator if we added any
// showable popup addresses to the menu.
if (foundUsablePopupURI) {
blockedPopupsSeparator.removeAttribute("hidden");
var popupURIspec = blockedPopup.popupWindowURISpec;
// Sometimes the popup URI that we get back from the blockedPopup
// isn't useful (for instance, netscape.com's popup URI ends up
// being "http://www.netscape.com", which isn't really the URI of
// the popup they're trying to show). This isn't going to be
// useful to the user, so we won't create a menu item for it.
if (
popupURIspec == "" ||
popupURIspec == "about:blank" ||
popupURIspec == "<self>" ||
popupURIspec == uri.spec
) {
continue;
}
// Because of the short-circuit above, we may end up in a situation
// in which we don't have any usable popup addresses to show in
// the menu, and therefore we shouldn't show the separator. However,
// since we got past the short-circuit, we must've found at least
// one usable popup URI and thus we'll turn on the separator later.
foundUsablePopupURI = true;
var menuitem = document.createXULElement("menuitem");
var label = gNavigatorBundle.getFormattedString(
"popupShowPopupPrefix",
[popupURIspec]
);
menuitem.setAttribute("label", label);
menuitem.setAttribute(
"oncommand",
"gPopupBlockerObserver.showBlockedPopup(event);"
);
menuitem.setAttribute("popupReportIndex", i);
menuitem.setAttribute(
"popupInnerWindowId",
blockedPopup.innerWindowId
);
menuitem.browsingContext = blockedPopup.browsingContext;
menuitem.popupReportBrowser = browser;
aEvent.target.appendChild(menuitem);
}
}, null);
}
// Show the separator if we added any
// showable popup addresses to the menu.
if (foundUsablePopupURI) {
blockedPopupsSeparator.removeAttribute("hidden");
}
}, null);
},
onPopupHiding(aEvent) {
@ -1177,20 +1178,16 @@ var gPopupBlockerObserver = {
},
showBlockedPopup(aEvent) {
var target = aEvent.target;
var popupReportIndex = target.getAttribute("popupReportIndex");
let target = aEvent.target;
let browsingContext = target.browsingContext;
let innerWindowId = target.getAttribute("popupInnerWindowId");
let popupReportIndex = target.getAttribute("popupReportIndex");
let browser = target.popupReportBrowser;
browser.unblockPopup(popupReportIndex);
},
showAllBlockedPopups(aBrowser) {
aBrowser.retrieveListOfBlockedPopups().then(popups => {
for (let i = 0; i < popups.length; i++) {
if (popups[i].popupWindowURIspec) {
aBrowser.unblockPopup(i);
}
}
}, null);
browser.popupBlocker.unblockPopup(
browsingContext,
innerWindowId,
popupReportIndex
);
},
editPopupSettings() {

Просмотреть файл

@ -1057,11 +1057,10 @@
this._appendStatusPanel();
if (
(oldBrowser.blockedPopups && !newBrowser.blockedPopups) ||
(!oldBrowser.blockedPopups && newBrowser.blockedPopups)
) {
newBrowser.updateBlockedPopups();
let oldBrowserPopupsBlocked = oldBrowser.popupBlocker.getBlockedPopupCount();
let newBrowserPopupsBlocked = newBrowser.popupBlocker.getBlockedPopupCount();
if (oldBrowserPopupsBlocked != newBrowserPopupsBlocked) {
newBrowser.popupBlocker.updateBlockedPopupsUI();
}
// Update the URL bar.

Просмотреть файл

@ -1,3 +1,6 @@
[DEFAULT]
support-files =
head.js
[browser_popupUI.js]
[browser_popup_blocker.js]
support-files =

Просмотреть файл

@ -71,27 +71,13 @@ add_task(async function test_opening_blocked_popups() {
// Show the menu.
let popupShown = BrowserTestUtils.waitForEvent(window, "popupshown");
let popupFilled = BrowserTestUtils.waitForMessage(
gBrowser.selectedBrowser.messageManager,
"PopupBlocking:ReplyGetBlockedPopupList"
);
let popupFilled = waitForBlockedPopups(2);
notification.querySelector("button").doCommand();
let popup_event = await popupShown;
let menu = popup_event.target;
is(menu.id, "blockedPopupOptions", "Blocked popup menu shown");
await popupFilled;
// The menu is filled on the same message that we waited for, so let's ensure that it
// had a chance of running before this test code.
await new Promise(resolve => executeSoon(resolve));
// Check the menu contents.
let sep = menu.querySelector("menuseparator");
let popupCount = 0;
for (let i = sep.nextElementSibling; i; i = i.nextElementSibling) {
popupCount++;
}
is(popupCount, 2, "Two popups were blocked");
// Pressing "allow" should open all blocked popups.
let popupTabs = [];

Просмотреть файл

@ -36,44 +36,45 @@ add_task(async function test_opening_blocked_popups() {
() =>
(notification = gBrowser
.getNotificationBox()
.getNotificationWithValue("popup-blocked"))
.getNotificationWithValue("popup-blocked")),
"Waiting for the popup-blocked notification."
);
ok(notification, "Should have notification.");
await ContentTask.spawn(tab.linkedBrowser, baseURL, async function(uri) {
let pageHideHappened = BrowserTestUtils.waitForContentEvent(
tab.linkedBrowser,
"pagehide",
true
);
await SpecialPowers.spawn(tab.linkedBrowser, [baseURL], async function(uri) {
let iframe = content.document.createElement("iframe");
let pageHideHappened = ContentTaskUtils.waitForEvent(
this,
"pagehide",
true
);
content.document.body.appendChild(iframe);
iframe.src = uri;
await pageHideHappened;
});
await pageHideHappened;
notification = gBrowser
.getNotificationBox()
.getNotificationWithValue("popup-blocked");
ok(notification, "Should still have notification");
pageHideHappened = BrowserTestUtils.waitForContentEvent(
tab.linkedBrowser,
"pagehide",
true
);
// Now navigate the subframe.
await ContentTask.spawn(tab.linkedBrowser, null, async function() {
let pageHideHappened = ContentTaskUtils.waitForEvent(
this,
"pagehide",
true
);
await SpecialPowers.spawn(tab.linkedBrowser, [], async function() {
content.document.getElementById(
"popupframe"
).contentDocument.location.href = "about:blank";
await pageHideHappened;
});
await pageHideHappened;
await BrowserTestUtils.waitForCondition(
() =>
!gBrowser.getNotificationBox().getNotificationWithValue("popup-blocked")
!gBrowser.getNotificationBox().getNotificationWithValue("popup-blocked"),
"Notification should go away"
);
ok(
!gBrowser.getNotificationBox().getNotificationWithValue("popup-blocked"),

Просмотреть файл

@ -0,0 +1,12 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
async function waitForBlockedPopups(numberOfPopups) {
let menupopup = document.getElementById("blockedPopupOptions");
await BrowserTestUtils.waitForCondition(() => {
let popups = menupopup.querySelectorAll("[popupReportIndex]");
return popups.length == numberOfPopups;
}, `Waiting for ${numberOfPopups} popups`);
}

Просмотреть файл

@ -5579,13 +5579,7 @@ void nsGlobalWindowOuter::FireAbuseEvents(
const nsAString& aPopupURL, const nsAString& aPopupWindowName,
const nsAString& aPopupWindowFeatures) {
// fetch the URI of the window requesting the opened window
nsCOMPtr<nsPIDOMWindowOuter> window = GetInProcessTop();
if (!window) {
return;
}
nsCOMPtr<Document> topDoc = window->GetDoc();
nsCOMPtr<Document> currentDoc = GetDoc();
nsCOMPtr<nsIURI> popupURI;
// build the URI of the would-have-been popup window
@ -5605,7 +5599,7 @@ void nsGlobalWindowOuter::FireAbuseEvents(
getter_AddRefs(popupURI));
// fire an event block full of informative URIs
FirePopupBlockedEvent(topDoc, popupURI, aPopupWindowName,
FirePopupBlockedEvent(currentDoc, popupURI, aPopupWindowName,
aPopupWindowFeatures);
}

Просмотреть файл

@ -7,153 +7,147 @@
var EXPORTED_SYMBOLS = ["PopupBlockingChild"];
const { ActorChild } = ChromeUtils.import(
"resource://gre/modules/ActorChild.jsm"
);
// The maximum number of popup information we'll send to the parent.
const MAX_SENT_POPUPS = 15;
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
class PopupBlockingChild extends ActorChild {
constructor(dispatcher) {
super(dispatcher);
class PopupBlockingChild extends JSWindowActorChild {
constructor() {
super();
this.weakDocStates = new WeakMap();
}
this.popupData = null;
this.popupDataInternal = null;
actorCreated() {
this.contentWindow.addEventListener("pageshow", this);
}
this.mm.addEventListener("pageshow", this, true);
this.mm.addEventListener("pagehide", this, true);
willDestroy() {
this.contentWindow.removeEventListener("pageshow", this);
}
this.mm.addMessageListener("PopupBlocking:UnblockPopup", this);
this.mm.addMessageListener("PopupBlocking:GetBlockedPopupList", this);
/**
* Returns the state for the current document referred to via
* this.document. If no such state exists, creates it, stores it
* and returns it.
*/
get docState() {
let state = this.weakDocStates.get(this.document);
if (!state) {
state = {
popupData: [],
};
this.weakDocStates.set(this.document, state);
}
return state;
}
receiveMessage(msg) {
switch (msg.name) {
case "PopupBlocking:UnblockPopup": {
case "UnblockPopup": {
let i = msg.data.index;
if (this.popupData && this.popupData[i]) {
let data = this.popupData[i];
let internals = this.popupDataInternal[i];
let dwi = internals.requestingWindow;
let state = this.docState;
let popupData = state.popupData[i];
if (popupData) {
let dwi = popupData.requestingWindow;
// If we have a requesting window and the requesting document is
// still the current document, open the popup.
if (dwi && dwi.document == internals.requestingDocument) {
if (dwi && dwi.document == popupData.requestingDocument) {
dwi.open(
data.popupWindowURIspec,
data.popupWindowName,
data.popupWindowFeatures
popupData.popupWindowURISpec,
popupData.popupWindowName,
popupData.popupWindowFeatures
);
}
}
break;
}
case "PopupBlocking:GetBlockedPopupList": {
let popupData = [];
let length = this.popupData ? this.popupData.length : 0;
case "GetBlockedPopupList": {
let state = this.docState;
let length = Math.min(state.popupData.length, MAX_SENT_POPUPS);
// Limit 15 popup URLs to be reported through the UI
length = Math.min(length, 15);
let result = [];
for (let i = 0; i < length; i++) {
let popupWindowURIspec = this.popupData[i].popupWindowURIspec;
for (let i = 0; i < length; ++i) {
let popup = state.popupData[i];
if (popupWindowURIspec == this.mm.content.location.href) {
popupWindowURIspec = "<self>";
let popupWindowURISpec = popup.popupWindowURISpec;
if (this.contentWindow.location.href == popupWindowURISpec) {
popupWindowURISpec = "<self>";
} else {
// Limit 500 chars to be sent because the URI will be cropped
// by the UI anyway, and data: URIs can be significantly larger.
popupWindowURIspec = popupWindowURIspec.substring(0, 500);
popupWindowURISpec = popupWindowURISpec.substring(0, 500);
}
popupData.push({ popupWindowURIspec });
result.push({
popupWindowURISpec,
});
}
this.mm.sendAsyncMessage("PopupBlocking:ReplyGetBlockedPopupList", {
popupData,
});
return result;
}
}
return null;
}
handleEvent(event) {
switch (event.type) {
case "DOMPopupBlocked":
this.onPopupBlocked(event);
break;
case "pageshow": {
this.onPageShow(event);
break;
}
}
}
handleEvent(ev) {
switch (ev.type) {
case "DOMPopupBlocked":
return this.onPopupBlocked(ev);
case "pageshow":
return this._removeIrrelevantPopupData();
case "pagehide":
return this._removeIrrelevantPopupData(ev.target);
}
return undefined;
}
onPopupBlocked(ev) {
if (!this.popupData) {
this.popupData = [];
this.popupDataInternal = [];
}
// Avoid spamming the parent process with too many blocked popups.
if (this.popupData.length >= PopupBlockingChild.maxReportedPopups) {
onPopupBlocked(event) {
if (event.target != this.document) {
return;
}
let obj = {
popupWindowURIspec: ev.popupWindowURI
? ev.popupWindowURI.spec
let state = this.docState;
// Avoid spamming the parent process with too many blocked popups.
if (state.popupData.length >= PopupBlockingChild.maxReportedPopups) {
return;
}
let popup = {
popupWindowURISpec: event.popupWindowURI
? event.popupWindowURI.spec
: "about:blank",
popupWindowFeatures: ev.popupWindowFeatures,
popupWindowName: ev.popupWindowName,
popupWindowFeatures: event.popupWindowFeatures,
popupWindowName: event.popupWindowName,
requestingWindow: event.requestingWindow,
requestingDocument: event.requestingWindow.document,
};
let internals = {
requestingWindow: ev.requestingWindow,
requestingDocument: ev.requestingWindow.document,
};
this.popupData.push(obj);
this.popupDataInternal.push(internals);
state.popupData.push(popup);
this.updateBlockedPopups(true);
}
_removeIrrelevantPopupData(removedDoc = null) {
if (this.popupData) {
let i = 0;
let oldLength = this.popupData.length;
while (i < this.popupData.length) {
let { requestingWindow, requestingDocument } = this.popupDataInternal[
i
];
// Filter out irrelevant reports.
if (
requestingWindow &&
requestingWindow.document == requestingDocument &&
requestingDocument != removedDoc
) {
i++;
} else {
this.popupData.splice(i, 1);
this.popupDataInternal.splice(i, 1);
}
}
if (!this.popupData.length) {
this.popupData = null;
this.popupDataInternal = null;
}
if (!this.popupData || oldLength > this.popupData.length) {
this.updateBlockedPopups(false);
}
onPageShow(event) {
if (event.target != this.document) {
return;
}
this.updateBlockedPopups(false);
}
updateBlockedPopups(freshPopup) {
this.mm.sendAsyncMessage("PopupBlocking:UpdateBlockedPopups", {
count: this.popupData ? this.popupData.length : 0,
freshPopup,
updateBlockedPopups(shouldNotify) {
this.sendAsyncMessage("UpdateBlockedPopups", {
shouldNotify,
count: this.docState.popupData.length,
});
}
}

Просмотреть файл

@ -0,0 +1,275 @@
/* 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/. */
"use strict";
var EXPORTED_SYMBOLS = ["PopupBlocker", "PopupBlockingParent"];
/**
* This class manages all popup blocking operations on a <xul:browser>, including
* notifying the UI about updates to the blocked popups, and allowing popups to
* be unblocked.
*/
class PopupBlocker {
constructor(browser) {
this._browser = browser;
this._allBlockedPopupCounts = new WeakMap();
this._shouldShowNotification = false;
}
/**
* Returns whether or not there are new blocked popups for the associated
* <xul:browser> that the user might need to be notified about.
*/
get shouldShowNotification() {
return this._shouldShowNotification;
}
/**
* Should be called by the UI when the user has been notified about blocked
* popups for the associated <xul:browser>.
*/
didShowNotification() {
this._shouldShowNotification = false;
}
/**
* Synchronously returns the most recent count of blocked popups for
* the associated <xul:browser>.
*
* @return {Number}
* The total number of blocked popups for this <xul:browser>.
*/
getBlockedPopupCount() {
let totalBlockedPopups = 0;
let contextsToVisit = [this._browser.browsingContext];
while (contextsToVisit.length) {
let currentBC = contextsToVisit.pop();
let windowGlobal = currentBC.currentWindowGlobal;
if (!windowGlobal) {
continue;
}
let popupCountForGlobal =
this._allBlockedPopupCounts.get(windowGlobal) || 0;
totalBlockedPopups += popupCountForGlobal;
contextsToVisit.push(...currentBC.getChildren());
}
return totalBlockedPopups;
}
/**
* Asynchronously retrieve information about the popups that have
* been blocked for the associated <xul:browser>. This information
* can be used to unblock those popups.
*
* @return {Promise}
* @resolves {Array}
* When the blocked popup information has been gathered,
* resolves with an Array of Objects with the following properties:
*
* browsingContext {BrowsingContext}
* The BrowsingContext that the popup was blocked for.
*
* innerWindowId {Number}
* The inner window ID for the blocked popup. This is used to differentiate
* popups that were blocked from one page load to the next.
*
* popupWindowURISpec {String}
* A string representing part or all of the URI that tried to be opened in a
* popup.
*/
async getBlockedPopups() {
let contextsToVisit = [this._browser.browsingContext];
let result = [];
while (contextsToVisit.length) {
let currentBC = contextsToVisit.pop();
let windowGlobal = currentBC.currentWindowGlobal;
if (!windowGlobal) {
continue;
}
let popupCountForGlobal =
this._allBlockedPopupCounts.get(windowGlobal) || 0;
if (popupCountForGlobal) {
let actor = windowGlobal.getActor("PopupBlocking");
let popups = await actor.sendQuery("GetBlockedPopupList");
for (let popup of popups) {
if (!popup.popupWindowURISpec) {
continue;
}
result.push({
browsingContext: currentBC,
innerWindowId: windowGlobal.innerWindowId,
popupWindowURISpec: popup.popupWindowURISpec,
});
}
}
contextsToVisit.push(...currentBC.getChildren());
}
return result;
}
/**
* Unblocks a popup that had been blocked. The information passed should
* come from the list of blocked popups returned via getBlockedPopups().
*
* Unblocking a popup causes that popup to open.
*
* @param browsingContext {BrowsingContext}
* The BrowsingContext that the popup was blocked for.
*
* @param innerWindowId {Number}
* The inner window ID for the blocked popup. This is used to differentiate popups
* that were blocked from one page load to the next.
*
* @param popupIndex {Number}
* The index of the entry in the Array returned by getBlockedPopups().
*/
unblockPopup(browsingContext, innerWindowId, popupIndex) {
let popupFrame = browsingContext.top.embedderElement;
let popupBrowser = popupFrame.outerBrowser
? popupFrame.outerBrowser
: popupFrame;
if (this._browser != popupBrowser) {
throw new Error(
"Attempting to unblock popup in a BrowsingContext no longer hosted in this browser."
);
}
let windowGlobal = browsingContext.currentWindowGlobal;
if (!windowGlobal || windowGlobal.innerWindowId != innerWindowId) {
// The inner window has moved on since the user clicked on
// the blocked popups dropdown, so we'll just exit silently.
return;
}
let actor = browsingContext.currentWindowGlobal.getActor("PopupBlocking");
actor.sendAsyncMessage("UnblockPopup", { index: popupIndex });
}
/**
* Goes through the most recent list of blocked popups for the associated
* <xul:browser> and unblocks all of them. Unblocking a popup causes the popup
* to open.
*/
async unblockAllPopups() {
let popups = await this.getBlockedPopups();
for (let i = 0; i < popups.length; ++i) {
let popup = popups[i];
this.unblockPopup(popup.browsingContext, popup.innerWindowId, i);
}
}
/**
* Fires a DOMUpdateBlockedPopups chrome-only event so that the UI can
* update itself to represent the current state of popup blocking for
* the associated <xul:browser>.
*/
updateBlockedPopupsUI() {
let event = this._browser.ownerDocument.createEvent("Events");
event.initEvent("DOMUpdateBlockedPopups", true, true);
this._browser.dispatchEvent(event);
}
/** Private methods **/
/**
* Updates the current popup count for a particular BrowsingContext based
* on messages from the underlying process.
*
* This should only be called by a PopupBlockingParent instance.
*
* @param browsingContext {BrowsingContext}
* The BrowsingContext to update the internal blocked popup count for.
*
* @param blockedPopupData {Object}
* An Object representing information about how many popups are blocked
* for the BrowsingContext. The Object has the following properties:
*
* count {Number}
* The total number of blocked popups for the BrowsingContext.
*
* shouldNotify {Boolean}
* Whether or not the list of blocked popups has changed in such a way that
* the UI should be updated about it.
*/
_updateBlockedPopupEntries(browsingContext, blockedPopupData) {
let windowGlobal = browsingContext.currentWindowGlobal;
let { count, shouldNotify } = blockedPopupData;
if (!this.shouldShowNotification && shouldNotify) {
this._shouldShowNotification = true;
}
if (windowGlobal) {
this._allBlockedPopupCounts.set(windowGlobal, count);
}
this.updateBlockedPopupsUI();
}
}
/**
* To keep things properly encapsulated, these should only be instantiated via
* the PopupBlocker class for a particular <xul:browser>.
*
* Instantiated for a WindowGlobalParent for a BrowsingContext in one of two cases:
*
* 1. One or more popups have been blocked for the underlying frame represented
* by the WindowGlobalParent.
*
* 2. Something in the parent process is querying a frame for information about
* any popups that may have been blocked inside of it.
*/
class PopupBlockingParent extends JSWindowActorParent {
didDestroy() {
this.updatePopupCountForBrowser({ count: 0, shouldNotify: false });
}
receiveMessage(message) {
if (message.name == "UpdateBlockedPopups") {
this.updatePopupCountForBrowser({
count: message.data.count,
shouldNotify: message.data.shouldNotify,
});
}
}
/**
* Updates the PopupBlocker for the <xul:browser> associated with this
* PopupBlockingParent with the most recent count of blocked popups.
*
* @param data {Object}
* An Object with the following properties:
*
* count {Number}:
* The number of blocked popups for the underlying document.
*
* shouldNotify {Boolean}:
* Whether or not the list of blocked popups has changed in such a way that
* the UI should be updated about it.
*/
updatePopupCountForBrowser(data) {
let browser = this.browsingContext.top.embedderElement;
if (!browser) {
return;
}
if (browser.outerBrowser) {
browser = browser.outerBrowser; // handle RDM mode
}
browser.popupBlocker._updateBlockedPopupEntries(this.browsingContext, data);
}
}

Просмотреть файл

@ -44,6 +44,7 @@ FINAL_TARGET_FILES.actors += [
'KeyPressEventModelCheckerChild.jsm',
'PictureInPictureChild.jsm',
'PopupBlockingChild.jsm',
'PopupBlockingParent.jsm',
'PrintingChild.jsm',
'PurgeSessionHistoryChild.jsm',
'SelectChild.jsm',

Просмотреть файл

@ -34,6 +34,18 @@
"resource://gre/modules/RemoteWebNavigation.jsm"
);
ChromeUtils.defineModuleGetter(
LazyModules,
"PopupBlocker",
"resource://gre/actors/PopupBlockingParent.jsm"
);
ChromeUtils.defineModuleGetter(
LazyModules,
"XPCOMUtils",
"resource://gre/modules/XPCOMUtils.jsm"
);
const elementsToDestroyOnUnload = new Set();
window.addEventListener(
@ -95,6 +107,10 @@
// between calls to destroy().
this.progressListeners = [];
LazyModules.XPCOMUtils.defineLazyGetter(this, "popupBlocker", () => {
return new LazyModules.PopupBlocker(this);
});
this.addEventListener(
"keypress",
event => {
@ -341,8 +357,6 @@
this._mStrBundle = null;
this.blockedPopups = null;
this._audioMuted = false;
this._hasAnyPlayingMediaBeenBlocked = false;
@ -1078,38 +1092,6 @@
}
}
updateBlockedPopups() {
let event = document.createEvent("Events");
event.initEvent("DOMUpdateBlockedPopups", true, true);
this.dispatchEvent(event);
}
retrieveListOfBlockedPopups() {
this.messageManager.sendAsyncMessage(
"PopupBlocking:GetBlockedPopupList",
null
);
return new Promise(resolve => {
let self = this;
this.messageManager.addMessageListener(
"PopupBlocking:ReplyGetBlockedPopupList",
function replyReceived(msg) {
self.messageManager.removeMessageListener(
"PopupBlocking:ReplyGetBlockedPopupList",
replyReceived
);
resolve(msg.data.popupData);
}
);
});
}
unblockPopup(aPopupIndex) {
this.messageManager.sendAsyncMessage("PopupBlocking:UnblockPopup", {
index: aPopupIndex,
});
}
audioPlaybackStarted() {
if (this._audioMuted) {
return;
@ -1339,10 +1321,6 @@
}
if (this.messageManager) {
this.messageManager.addMessageListener(
"PopupBlocking:UpdateBlockedPopups",
this
);
this.messageManager.addMessageListener(
"UnselectedTabHover:Toggle",
this
@ -1403,15 +1381,6 @@
_receiveMessage(aMessage) {
let data = aMessage.data;
switch (aMessage.name) {
case "PopupBlocking:UpdateBlockedPopups": {
this.blockedPopups = {
length: data.count,
reported: !data.freshPopup,
};
this.updateBlockedPopups();
break;
}
case "UnselectedTabHover:Toggle":
this._shouldSendUnselectedTabHover = data.enable
? ++this._unselectedTabHoverMessageListenerCount > 0

Просмотреть файл

@ -263,6 +263,19 @@ let ACTORS = {
allFrames: true,
},
PopupBlocking: {
parent: {
moduleURI: "resource://gre/actors/PopupBlockingParent.jsm",
},
child: {
moduleURI: "resource://gre/actors/PopupBlockingChild.jsm",
events: {
DOMPopupBlocked: { capture: true },
},
},
allFrames: true,
},
PurgeSessionHistory: {
child: {
moduleURI: "resource://gre/actors/PurgeSessionHistoryChild.jsm",
@ -455,15 +468,6 @@ let LEGACY_ACTORS = {
},
},
PopupBlocking: {
child: {
module: "resource://gre/actors/PopupBlockingChild.jsm",
events: {
DOMPopupBlocked: { capture: true },
},
},
},
Printing: {
child: {
module: "resource://gre/actors/PrintingChild.jsm",