Bug 1385548 - Part 1: Support tab modals in WebExtensions options_ui pages. r=kmag
authorLuca Greco <lgreco@mozilla.com>
Mon, 18 Sep 2017 22:10:11 +0200
changeset 385209 f42d24d8aa41eac04c0cbcc196072251fd3cd5b1
parent 385208 1858ac6e51282e858eb4ea5346db53f2a3afc625
child 385210 a4de1a9d965add037872c11a11eac026158be657
push id32649
push userarchaeopteryx@coole-files.de
push dateTue, 10 Oct 2017 09:11:18 +0000
treeherdermozilla-central@87ca0b304342 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskmag
bugs1385548
milestone58.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1385548 - Part 1: Support tab modals in WebExtensions options_ui pages. r=kmag This patch introduces a stub gBrowser object which allow a WebExtensions options_ui page to open a tab modal using alert/prompt/confirm. The about:addons page is defined at toolkit level but the TabModalPromptBox is defined at browser level, and so to be able to provide a TabMobalPromptBox from the about:addons page this patch uses the implementation provided by the window that contains the about:addons tab, if any. MozReview-Commit-ID: m6khgJyMs
browser/base/content/tabbrowser.xml
toolkit/components/addoncompat/RemoteAddonsParent.jsm
toolkit/mozapps/extensions/content/extensions.js
toolkit/mozapps/extensions/test/browser/browser_inlinesettings_browser.js
toolkit/mozapps/extensions/test/browser/browser_webext_options.js
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -6094,18 +6094,20 @@
           // Focus window for beforeunload dialog so it is seen but don't
           // steal focus from other applications.
           if (event.detail &&
               event.detail.tabPrompt &&
               event.detail.inPermitUnload &&
               Services.focus.activeWindow)
             window.focus();
 
-          // Don't need to act if the tab is already selected:
-          if (tabForEvent.selected)
+          // Don't need to act if the tab is already selected or if there isn't
+          // a tab for the event (e.g. for the webextensions options_ui remote
+          // browsers embedded in the "about:addons" page):
+          if (!tabForEvent || tabForEvent.selected)
             return;
 
           // We always switch tabs for beforeunload tab-modal prompts.
           if (event.detail &&
               event.detail.tabPrompt &&
               !event.detail.inPermitUnload) {
             let docPrincipal = targetIsWindow ? event.target.document.nodePrincipal : null;
             // At least one of these should/will be non-null:
--- a/toolkit/components/addoncompat/RemoteAddonsParent.jsm
+++ b/toolkit/components/addoncompat/RemoteAddonsParent.jsm
@@ -433,20 +433,25 @@ var EventTargetParent = {
 
     if (target instanceof Ci.nsIDOMXULElement) {
       if (target.localName == "browser") {
         return target;
       } else if (target.localName == "tab") {
         return target.linkedBrowser;
       }
 
-      // Check if |target| is somewhere on the patch from the
+      // Check if |target| is somewhere on the path from the
       // <tabbrowser> up to the root element.
       let window = target.ownerGlobal;
-      if (window && target.contains(window.gBrowser)) {
+
+      // Some non-browser windows define gBrowser globals which are not elements
+      // and can't be passed to target.contains().
+      if (window &&
+          window.gBrowser instanceof Ci.nsIDOMXULElement &&
+          target.contains(window.gBrowser)) {
         return window;
       }
     }
 
     return null;
   },
 
   // When a given event fires in the child, we fire it on the
--- a/toolkit/mozapps/extensions/content/extensions.js
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -1,17 +1,17 @@
 /* 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";
 
 /* import-globals-from ../../../content/contentAreaUtils.js */
 /* globals XMLStylesheetProcessingInstruction */
-/* exported UPDATES_RELEASENOTES_TRANSFORMFILE, XMLURI_PARSE_ERROR, loadView */
+/* exported UPDATES_RELEASENOTES_TRANSFORMFILE, XMLURI_PARSE_ERROR, loadView, gBrowser */
 
 var Cc = Components.classes;
 var Ci = Components.interfaces;
 var Cu = Components.utils;
 var Cr = Components.results;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
@@ -3646,25 +3646,25 @@ var gDetailView = {
 
     var rows = document.getElementById("detail-downloads").parentNode;
 
     try {
       if (this._addon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_BROWSER) {
         whenViewLoaded(async () => {
           await this._addon.startupPromise;
 
-          let browser = await this.createOptionsBrowser(rows);
+          const browserContainer = await this.createOptionsBrowser(rows);
 
           // Make sure the browser is unloaded as soon as we change views,
           // rather than waiting for the next detail view to load.
           document.addEventListener("ViewChanged", function() {
-            browser.remove();
+            browserContainer.remove();
           }, {once: true});
 
-          finish(browser);
+          finish(browserContainer);
         });
 
         if (aCallback)
           aCallback();
       } else {
         var xhr = new XMLHttpRequest();
         xhr.open("GET", this._addon.optionsURL, true);
         xhr.responseType = "xml";
@@ -3729,16 +3729,19 @@ var gDetailView = {
       let detailViewBoxObject = gDetailView.node.boxObject;
       top -= detailViewBoxObject.y;
 
       detailViewBoxObject.scrollTo(0, top);
     }
   },
 
   async createOptionsBrowser(parentNode) {
+    let stack = document.createElement("stack");
+    stack.setAttribute("id", "addon-options-prompts-stack");
+
     let browser = document.createElement("browser");
     browser.setAttribute("type", "content");
     browser.setAttribute("disableglobalhistory", "true");
     browser.setAttribute("id", "addon-options");
     browser.setAttribute("class", "inline-options-browser");
     browser.setAttribute("transparent", "true");
     browser.setAttribute("forcemessagemanager", "true");
     browser.setAttribute("selectmenulist", "ContentSelectDropdown");
@@ -3757,31 +3760,32 @@ var gDetailView = {
     if (remote) {
       browser.setAttribute("remote", "true");
       browser.setAttribute("remoteType", E10SUtils.EXTENSION_REMOTE_TYPE);
       readyPromise = promiseEvent("XULFrameLoaderCreated", browser);
     } else {
       readyPromise = promiseEvent("load", browser, true);
     }
 
-    parentNode.appendChild(browser);
+    stack.appendChild(browser);
+    parentNode.appendChild(stack);
 
     // Force bindings to apply synchronously.
     browser.clientTop;
 
     await readyPromise;
     ExtensionParent.apiManager.emit("extension-browser-inserted", browser);
 
     return new Promise(resolve => {
       let messageListener = {
         receiveMessage({name, data}) {
           if (name === "Extension:BrowserResized")
             browser.style.height = `${data.height}px`;
           else if (name === "Extension:BrowserContentLoaded")
-            resolve(browser);
+            resolve(stack);
         },
       };
 
       let mm = browser.messageManager;
       mm.loadFrameScript("chrome://extensions/content/ext-browser-content.js",
                          false);
       mm.addMessageListener("Extension:BrowserContentLoaded", messageListener);
       mm.addMessageListener("Extension:BrowserResized", messageListener);
@@ -4127,8 +4131,23 @@ var gDragDrop = {
           AddonManager.installAddonFromAOM(browser, document.documentURIObject, install);
         }, "application/x-xpinstall");
       }
     }
 
     aEvent.preventDefault();
   }
 };
+
+// Stub tabbrowser implementation for use by the tab-modal alert code
+// when an alert/prompt/confirm method is called in a WebExtensions options_ui page
+// (See Bug 1385548 for rationale).
+var gBrowser = {
+  getTabModalPromptBox(browser) {
+    const parentWindow = document.docShell.chromeEventHandler.ownerGlobal;
+
+    if (parentWindow.gBrowser) {
+      return parentWindow.gBrowser.getTabModalPromptBox(browser);
+    }
+
+    return null;
+  }
+};
--- a/toolkit/mozapps/extensions/test/browser/browser_inlinesettings_browser.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_inlinesettings_browser.js
@@ -91,27 +91,29 @@ async function openDetailsBrowser(addonI
   await TestUtils.topicObserved(AddonManager.OPTIONS_NOTIFICATION_DISPLAYED,
                                 (subject, data) => data == addonId);
 
   is(gManagerWindow.gViewController.currentViewId,
      `addons://detail/${encodeURIComponent(addonId)}/preferences`,
      "Current view should scroll to preferences");
 
   var browser = gManagerWindow.document.querySelector(
-    "#detail-grid > rows > .inline-options-browser");
-  var rows = browser.parentNode;
+    "#detail-grid > rows > stack > .inline-options-browser");
+  var rows = browser.parentNode.parentNode;
 
   let url = await ContentTask.spawn(browser, {}, () => content.location.href);
 
-  ok(browser, "Grid should have a browser child");
-  is(browser.localName, "browser", "Grid should have a browser child");
+  ok(browser, "Grid should have a browser descendant");
+  is(browser.localName, "browser", "Grid should have a browser descendant");
   is(url, addon.mAddon.optionsURL, "Browser has the expected options URL loaded")
 
+  is(browser.clientWidth, browser.parentNode.clientWidth,
+     "Browser should be the same width as its direct parent");
   is(browser.clientWidth, rows.clientWidth,
-     "Browser should be the same width as its parent node");
+     "Browser should be the same width as its rows ancestor");
 
   button = gManagerWindow.document.getElementById("detail-prefs-btn");
   is_element_hidden(button, "Preferences button should not be visible");
 
   return browser;
 }
 
 
--- a/toolkit/mozapps/extensions/test/browser/browser_webext_options.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_webext_options.js
@@ -25,27 +25,29 @@ async function runTest(installer) {
 
   await TestUtils.topicObserved(AddonManager.OPTIONS_NOTIFICATION_DISPLAYED,
                                 (subject, data) => data == id);
 
   is(mgrWindow.gViewController.currentViewId,
      `addons://detail/${encodeURIComponent(id)}/preferences`,
      "Current view should scroll to preferences");
 
-  var browser = mgrWindow.document.querySelector("#detail-grid > rows > .inline-options-browser");
+  var browser = mgrWindow.document.querySelector("#detail-grid > rows > stack > .inline-options-browser");
   var rows = browser.parentNode;
 
   let url = await ContentTask.spawn(browser, {}, () => content.location.href);
 
-  ok(browser, "Grid should have a browser child");
-  is(browser.localName, "browser", "Grid should have a browser child");
+  ok(browser, "Grid should have a browser descendant");
+  is(browser.localName, "browser", "Grid should have a browser descendant");
   is(url, element.mAddon.optionsURL, "Browser has the expected options URL loaded")
 
+  is(browser.clientWidth, browser.parentNode.clientWidth,
+     "Browser should be the same width as its direct parent");
   is(browser.clientWidth, rows.clientWidth,
-     "Browser should be the same width as its parent node");
+     "Browser should be the same width as its rows ancestor");
 
   button = mgrWindow.document.getElementById("detail-prefs-btn");
   is_element_hidden(button, "Preferences button should not be visible");
 
   await close_manager(mgrWindow);
 
   addon.uninstall();
 }