Bug 1631147 - Properly port bug 1615588 - Extend nsIPromptService to support tab modal system prompts. r=mkmelin
authorGeoff Lankow <geoff@darktrojan.net>
Thu, 21 May 2020 20:08:29 +1200
changeset 39211 1f1af3f6677b3bf539cc86f1e42546ecdfe85aa1
parent 39210 b8c06eb419142c4deea5775cd522c863518a81df
child 39212 a15cee0df19fc84a95e32cb0e1ae1f438001c917
push id402
push userclokep@gmail.com
push dateMon, 29 Jun 2020 20:48:04 +0000
reviewersmkmelin
bugs1631147, 1615588
Bug 1631147 - Properly port bug 1615588 - Extend nsIPromptService to support tab modal system prompts. r=mkmelin
mail/base/modules/PromptParent.jsm
mail/components/MailGlue.jsm
mail/test/browser/content-tabs/browser.ini
--- a/mail/base/modules/PromptParent.jsm
+++ b/mail/base/modules/PromptParent.jsm
@@ -34,248 +34,123 @@ ChromeUtils.defineModuleGetter(
  * @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(this.browsingContext);
+    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 {BrowsingContext} browsingContext
-   *        The BrowsingContext from which the request to open the Prompt came.
    * @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.
    * @return {Promise}
    * @resolves {Object}
    *           Resolves with the arguments returned from the TabModalPrompt when it
    *           is dismissed.
    */
-  registerPrompt(browsingContext, tabModalPrompt, id) {
-    let prompts = gBrowserPrompts.get(browsingContext);
+  registerPrompt(tabModalPrompt, id) {
+    let prompts = gBrowserPrompts.get(this.browsingContext);
     if (!prompts) {
       prompts = new Map();
-      gBrowserPrompts.set(browsingContext, prompts);
+      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 {BrowsingContext} browsingContext
-   *        The BrowsingContext from which the request to open the Prompt came.
    * @param {string} id
    *        A unique ID to differentiate multiple Prompts coming from the same
    *        BrowsingContext.
    */
-  unregisterPrompt(browsingContext, id) {
-    let prompts = gBrowserPrompts.get(browsingContext);
+  unregisterPrompt(id) {
+    let prompts = gBrowserPrompts.get(this.browsingContext);
     if (prompts) {
       prompts.delete(id);
     }
   }
 
   /**
-   * Programmatically closes a Prompt, without waiting for the TabModalPrompt to
-   * return with any arguments.
-   *
-   * @param {BrowsingContext} browsingContext
-   *        The BrowsingContext from which the request to open the Prompt came.
-   * @param {string} id
-   *        A unique ID to differentiate multiple Prompts coming from the same
-   *        BrowsingContext.
+   * Programmatically closes all Prompts for the current BrowsingContext.
    */
-  forceClosePrompt(browsingContext, id) {
-    let prompts = gBrowserPrompts.get(browsingContext);
-    let prompt = prompts.get(id);
-    if (prompt && prompt.tabModalPrompt) {
-      prompt.tabModalPrompt.abortPrompt();
-    }
-  }
-
-  /**
-   * Programmatically closes all Prompts for a BrowsingContext.
-   *
-   * @param {BrowsingContext} browsingContext
-   *        The BrowsingContext from which the request to open the Prompts came.
-   */
-  forceClosePrompts(browsingContext) {
-    let prompts = gBrowserPrompts.get(browsingContext) || [];
+  forceClosePrompts() {
+    let prompts = gBrowserPrompts.get(this.browsingContext) || [];
 
     for (let [, prompt] of prompts) {
       prompt.tabModalPrompt && prompt.tabModalPrompt.abortPrompt();
     }
   }
 
   receiveMessage(message) {
     let args = message.data;
-    let browsingContext = args.browsingContext || this.browsingContext;
-    let id = args._remoteId;
 
     switch (message.name) {
       case "Prompt:Open": {
-        let topPrincipal =
-          browsingContext.top.currentWindowGlobal.documentPrincipal;
-        args.showAlertOrigin =
-          args.promptPrincipal && topPrincipal.equals(args.promptPrincipal);
-        if (args.modalType === Ci.nsIPrompt.MODAL_TYPE_WINDOW) {
-          return this.openWindowPrompt(args, browsingContext);
-        }
-        return this.openTabPrompt(args, browsingContext, id);
-      }
-      case "Prompt:ForceClose": {
-        this.forceClosePrompt(browsingContext, id);
-        break;
-      }
-      case "Prompt:OnPageHide": {
-        // User navigates away, close all non window prompts
-        this.forceClosePrompts(browsingContext);
-        break;
+        return this.openWindowPrompt(args);
       }
     }
 
     return undefined;
   }
 
   /**
-   * Opens a TabModalPrompt for a BrowsingContext, and puts the associated browser
-   * in the modal state until the TabModalPrompt is closed.
-   *
-   * @param {Object} args
-   *        The arguments passed up from the BrowsingContext to be passed directly
-   *        to the TabModalPrompt.
-   * @param {BrowsingContext} browsingContext
-   *        The BrowsingContext from which the request to open the Prompts came.
-   * @param {string} id
-   *        A unique ID to differentiate multiple Prompts coming from the same
-   *        BrowsingContext.
-   * @return {Promise}
-   *         Resolves when the TabModalPrompt is dismissed.
-   * @resolves {Object}
-   *           The arguments returned from the TabModalPrompt.
-   */
-  openTabPrompt(args, browsingContext = this.browsingContext, id) {
-    let browser = browsingContext.top.embedderElement;
-    if (!browser) {
-      throw new Error("Cannot tab-prompt without a browser!");
-    }
-    let window = browser.ownerGlobal;
-    let tabPrompt = window.gBrowser.getTabModalPromptBox(browser);
-    let newPrompt;
-    let needRemove = false;
-
-    let onPromptClose = forceCleanup => {
-      let promptData = gBrowserPrompts.get(browsingContext);
-      if (!promptData || !promptData.has(id)) {
-        throw new Error(
-          "Failed to close a prompt since it wasn't registered for some reason."
-        );
-      }
-
-      let { resolver, tabModalPrompt } = promptData.get(id);
-      // It's possible that we removed the prompt during the
-      // appendPrompt call below. In that case, newPrompt will be
-      // undefined. We set the needRemove flag to remember to remove
-      // it right after we've finished adding it.
-      if (tabModalPrompt) {
-        tabPrompt.removePrompt(tabModalPrompt);
-      } else {
-        needRemove = true;
-      }
-
-      this.unregisterPrompt(browsingContext, id);
-
-      PromptUtils.fireDialogEvent(window, "DOMModalDialogClosed", browser);
-      resolver(args);
-      browser.maybeLeaveModalState();
-    };
-
-    try {
-      browser.enterModalState();
-      let eventDetail = {
-        tabPrompt: true,
-        promptPrincipal: args.promptPrincipal,
-        inPermitUnload: args.inPermitUnload,
-      };
-      PromptUtils.fireDialogEvent(
-        window,
-        "DOMWillOpenModalDialog",
-        browser,
-        eventDetail
-      );
-
-      args.promptActive = true;
-
-      newPrompt = tabPrompt.appendPrompt(args, onPromptClose);
-      let promise = this.registerPrompt(browsingContext, newPrompt, id);
-
-      if (needRemove) {
-        tabPrompt.removePrompt(newPrompt);
-      }
-
-      return promise;
-    } catch (ex) {
-      Cu.reportError(ex);
-      onPromptClose(true);
-    }
-
-    return null;
-  }
-
-  /**
    * 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.
-   * @param {BrowsingContext} browsingContext
-   *        The BrowsingContext from which the request to open the window-modal
-   *        prompt came.
    * @return {Promise}
    *         Resolves when the window prompt is dismissed.
    * @resolves {Object}
    *           The arguments returned from the window prompt.
    */
-  openWindowPrompt(args, browsingContext = this.browsingContext) {
+  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 browser = browsingContext.top.embedderElement;
-    // If can't get the browser, because the BC does not have an embedder element,
-    // use window associated with the BC.
-    // This happens if we are passed a browsingContext of a chrome window.
-    let win = (browser && browser.ownerGlobal) || browsingContext.top.window;
+    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;
+    }
 
     // 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 && win.winUtils && !win.winUtils.isParentWindowMainWidgetVisible) {
+    if (win?.winUtils && !win.winUtils.isParentWindowMainWidgetVisible) {
       throw new Error("Cannot call openModalWindow on a hidden window");
     }
 
     try {
       if (browser) {
         browser.enterModalState();
         PromptUtils.fireDialogEvent(win, "DOMWillOpenModalDialog", browser);
       }
@@ -292,11 +167,11 @@ class PromptParent extends JSWindowActor
 
       PromptUtils.propBagToObject(bag, args);
     } finally {
       if (browser) {
         browser.leaveModalState();
         PromptUtils.fireDialogEvent(win, "DOMModalDialogClosed", browser);
       }
     }
-    return Promise.resolve(args);
+    return args;
   }
 }
--- a/mail/components/MailGlue.jsm
+++ b/mail/components/MailGlue.jsm
@@ -36,17 +36,17 @@ XPCOMUtils.defineLazyGetter(this, "gMail
 });
 
 ChromeUtils.defineModuleGetter(
   this,
   "ActorManagerParent",
   "resource://gre/modules/ActorManagerParent.jsm"
 );
 
-let ACTORS = {
+let JSWINDOWACTORS = {
   Prompt: {
     parent: {
       moduleURI: "resource:///actors/PromptParent.jsm",
     },
     includeChrome: true,
     allFrames: true,
   },
 };
@@ -116,17 +116,17 @@ MailGlue.prototype = {
 
     ExtensionSupport.registerWindowListener("Thunderbird-internal-Toolbox", {
       chromeURLs: [
         "chrome://devtools/content/framework/toolbox-process-window.xhtml",
       ],
       onLoadWindow: _setupToolbox,
     });
 
-    ActorManagerParent.addActors(ACTORS);
+    ActorManagerParent.addJSWindowActors(JSWINDOWACTORS);
     ActorManagerParent.flush();
   },
 
   // cleanup (called at shutdown)
   _dispose() {
     Services.obs.removeObserver(this, "xpcom-shutdown");
     Services.obs.removeObserver(this, "final-ui-startup");
     Services.obs.removeObserver(this, "intl:app-locales-changed");
--- a/mail/test/browser/content-tabs/browser.ini
+++ b/mail/test/browser/content-tabs/browser.ini
@@ -41,10 +41,9 @@ prefs =
   mailnews.start_page.override_url=about:blank
   mailnews.start_page.url=about:blank
 subsuite = thunderbird
 support-files = html/**
 
 [browser_aboutSupport.js]
 [browser_addonsMgr.js]
 [browser_contentTab.js]
-skip-if = true # Bug 1631147
 [browser_installXpi.js]