diff options
Diffstat (limited to 'toolkit/actors/PopupBlockingParent.sys.mjs')
-rw-r--r-- | toolkit/actors/PopupBlockingParent.sys.mjs | 268 |
1 files changed, 268 insertions, 0 deletions
diff --git a/toolkit/actors/PopupBlockingParent.sys.mjs b/toolkit/actors/PopupBlockingParent.sys.mjs new file mode 100644 index 0000000000..adb49a1ba2 --- /dev/null +++ b/toolkit/actors/PopupBlockingParent.sys.mjs @@ -0,0 +1,268 @@ +/* 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/. */ + +/** + * 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. + */ +export 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.children); + } + + 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.children); + } + + 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. + */ +export 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; + } + + browser.popupBlocker._updateBlockedPopupEntries(this.browsingContext, data); + } +} |