diff options
Diffstat (limited to 'comm/mail/actors/PromptParent.jsm')
-rw-r--r-- | comm/mail/actors/PromptParent.jsm | 180 |
1 files changed, 180 insertions, 0 deletions
diff --git a/comm/mail/actors/PromptParent.jsm b/comm/mail/actors/PromptParent.jsm new file mode 100644 index 0000000000..5aedf5a1b9 --- /dev/null +++ b/comm/mail/actors/PromptParent.jsm @@ -0,0 +1,180 @@ +/* vim: set ts=2 sw=2 et tw=80: */ +/* 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 = ["PromptParent"]; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + PromptUtils: "resource://gre/modules/PromptUtils.sys.mjs", +}); + +/** + * @typedef {object} Prompt + * @property {Function} resolver + * The resolve function to be called with the data from the Prompt + * after the user closes it. + * @property {object} tabModalPrompt + * The TabModalPrompt being shown to the user. + */ + +/** + * gBrowserPrompts weakly maps BrowsingContexts to a Map of their currently + * active Prompts. + * + * @type {WeakMap<BrowsingContext, Prompt>} + */ +let gBrowserPrompts = new WeakMap(); + +class PromptParent extends JSWindowActorParent { + didDestroy() { + // In the event that the subframe or tab crashed, make sure that + // we close any active Prompts. + this.forceClosePrompts(); + } + + /** + * Registers a new Prompt to be tracked for a particular BrowsingContext. + * We need to track a Prompt so that we can, for example, force-close the + * TabModalPrompt if the originating subframe or tab unloads or crashes. + * + * @param {object} tabModalPrompt + * The TabModalPrompt that will be shown to the user. + * @param {string} id + * A unique ID to differentiate multiple Prompts coming from the same + * BrowsingContext. + * @returns {Promise} + * @resolves {object} + * Resolves with the arguments returned from the TabModalPrompt when it + * is dismissed. + */ + registerPrompt(tabModalPrompt, id) { + let prompts = gBrowserPrompts.get(this.browsingContext); + if (!prompts) { + prompts = new Map(); + gBrowserPrompts.set(this.browsingContext, prompts); + } + + let promise = new Promise(resolve => { + prompts.set(id, { + tabModalPrompt, + resolver: resolve, + }); + }); + + return promise; + } + + /** + * Removes a Prompt for a BrowsingContext with a particular ID from the registry. + * This needs to be done to avoid leaking <xul:browser>'s. + * + * @param {string} id + * A unique ID to differentiate multiple Prompts coming from the same + * BrowsingContext. + */ + unregisterPrompt(id) { + let prompts = gBrowserPrompts.get(this.browsingContext); + if (prompts) { + prompts.delete(id); + } + } + + /** + * Programmatically closes all Prompts for the current BrowsingContext. + */ + forceClosePrompts() { + let prompts = gBrowserPrompts.get(this.browsingContext) || []; + + for (let [, prompt] of prompts) { + prompt.tabModalPrompt && prompt.tabModalPrompt.abortPrompt(); + } + } + + receiveMessage(message) { + let args = message.data; + + switch (message.name) { + case "Prompt:Open": { + return this.openWindowPrompt(args); + } + } + + return undefined; + } + + /** + * Opens a window prompt for a BrowsingContext, and puts the associated + * browser in the modal state until the prompt is closed. + * + * @param {object} args + * The arguments passed up from the BrowsingContext to be passed + * directly to the modal window. + * @returns {Promise} + * Resolves when the window prompt is dismissed. + * @resolves {object} + * The arguments returned from the window prompt. + */ + async openWindowPrompt(args) { + const COMMON_DIALOG = "chrome://global/content/commonDialog.xhtml"; + const SELECT_DIALOG = "chrome://global/content/selectDialog.xhtml"; + let uri = args.promptType == "select" ? SELECT_DIALOG : COMMON_DIALOG; + + let browsingContext = this.browsingContext.top; + + let browser = browsingContext.embedderElement; + let win; + + // If we are a chrome actor we can use the associated chrome win. + if (!browsingContext.isContent && browsingContext.window) { + win = browsingContext.window; + } else { + win = browser?.ownerGlobal; + if (!win?.isChromeWindow) { + win = browsingContext.topChromeWindow; + } + } + + // There's a requirement for prompts to be blocked if a window is + // passed and that window is hidden (eg, auth prompts are suppressed if the + // passed window is the hidden window). + // See bug 875157 comment 30 for more.. + if (win?.winUtils && !win.winUtils.isParentWindowMainWidgetVisible) { + throw new Error("Cannot call openModalWindow on a hidden window"); + } + + try { + if (browser) { + // The compose editor does not support enter/leaveModalState. + browser.enterModalState?.(); + lazy.PromptUtils.fireDialogEvent( + win, + "DOMWillOpenModalDialog", + browser + ); + } + + let bag = lazy.PromptUtils.objectToPropBag(args); + + Services.ww.openWindow( + win, + uri, + "_blank", + "centerscreen,chrome,modal,titlebar", + bag + ); + + lazy.PromptUtils.propBagToObject(bag, args); + } finally { + if (browser) { + browser.leaveModalState?.(); + lazy.PromptUtils.fireDialogEvent(win, "DOMModalDialogClosed", browser); + } + } + return args; + } +} |