Bug 1317101 - Part 7e: Load extension options pages in a remote browser. r=aswan
authorKris Maglione <maglione.k@gmail.com>
Wed, 16 Nov 2016 13:24:08 -0800
changeset 323248 3330301202ab60286f826d61a473f1e1a8057301
parent 323247 29f9433a43acf579cb3314c484d8a0eff2e88d72
child 323249 2fa61db0ad05e0d55f58ed2046c8a41503a3ce13
push id21
push usermaklebus@msu.edu
push dateThu, 01 Dec 2016 06:22:08 +0000
reviewersaswan
bugs1317101
milestone53.0a1
Bug 1317101 - Part 7e: Load extension options pages in a remote browser. r=aswan MozReview-Commit-ID: 963sD0DcwhT
toolkit/components/extensions/ext-browser-content.js
toolkit/mozapps/extensions/content/extensions.js
toolkit/mozapps/extensions/test/browser/browser_inlinesettings_browser.js
--- a/toolkit/components/extensions/ext-browser-content.js
+++ b/toolkit/components/extensions/ext-browser-content.js
@@ -93,17 +93,17 @@ const BrowserListener = {
         if (event.target === content.document) {
           sendAsyncMessage("Extension:BrowserContentLoaded", {url: content.location.href});
           this.handleDOMChange(true);
         }
         break;
 
       case "load":
         if (event.target.contentWindow === content) {
-          // For about:addons inline <browsers>, we currently receive a load
+          // For about:addons inline <browser>s, we currently receive a load
           // event on the <browser> element, but no load or DOMContentLoaded
           // events from the content window.
           sendAsyncMessage("Extension:BrowserContentLoaded", {url: content.location.href});
         } else if (event.target !== content.document) {
           break;
         }
 
         // We use a capturing listener, so we get this event earlier than any
--- a/toolkit/mozapps/extensions/content/extensions.js
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -8,21 +8,26 @@
 /* globals XMLStylesheetProcessingInstruction*/
 
 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/Task.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/DownloadUtils.jsm");
 Cu.import("resource://gre/modules/AddonManager.jsm");
 Cu.import("resource://gre/modules/addons/AddonRepository.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "E10SUtils", "resource:///modules/E10SUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ExtensionParent",
+                                  "resource://gre/modules/ExtensionParent.jsm");
+
 const CONSTANTS = {};
 Cu.import("resource://gre/modules/addons/AddonConstants.jsm", CONSTANTS);
 const SIGNING_REQUIRED = CONSTANTS.REQUIRE_SIGNING ?
                          true :
                          Services.prefs.getBoolPref("xpinstall.signatures.required");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
                                   "resource://gre/modules/PluralForm.jsm");
@@ -124,43 +129,51 @@ class MessageDispatcher {
  * @param {Element} browser
  *        A XUL <browser> element.
  */
 class FakeFrameMessageManager {
   constructor(browser) {
     let dispatcher = new MessageDispatcher(browser);
     let frameDispatcher = new MessageDispatcher(null);
 
-    this.sendAsyncMessage = frameDispatcher.sendAsyncMessage.bind(frameDispatcher);
-    this.addMessageListener = dispatcher.addMessageListener.bind(dispatcher);
-    this.removeMessageListener = dispatcher.removeMessageListener.bind(dispatcher);
+    let bind = (object, method) => object[method].bind(object);
+
+    this.sendAsyncMessage = bind(frameDispatcher, "sendAsyncMessage");
+    this.addMessageListener = bind(dispatcher, "addMessageListener");
+    this.removeMessageListener = bind(dispatcher, "removeMessageListener");
 
     this.frame = {
       get content() {
         return browser.contentWindow;
       },
 
       get docShell() {
         return browser.docShell;
       },
 
-      addEventListener: browser.addEventListener.bind(browser),
-      removeEventListener: browser.removeEventListener.bind(browser),
-
-      sendAsyncMessage: dispatcher.sendAsyncMessage.bind(dispatcher),
-      addMessageListener: frameDispatcher.addMessageListener.bind(frameDispatcher),
-      removeMessageListener: frameDispatcher.removeMessageListener.bind(frameDispatcher),
+      addEventListener: bind(browser, "addEventListener"),
+      removeEventListener: bind(browser, "removeEventListener"),
+
+      sendAsyncMessage: bind(dispatcher, "sendAsyncMessage"),
+      addMessageListener: bind(frameDispatcher, "addMessageListener"),
+      removeMessageListener: bind(frameDispatcher, "removeMessageListener"),
     }
   }
 
   loadFrameScript(url) {
     Services.scriptloader.loadSubScript(url, Object.create(this.frame));
   }
 }
 
+function promiseEvent(event, target, capture = false) {
+  return new Promise(resolve => {
+    target.addEventListener(event, resolve, {capture, once: true});
+  });
+}
+
 var gPendingInitializations = 1;
 Object.defineProperty(this, "gIsInitializing", {
   get: () => gPendingInitializations > 0
 });
 
 function initialize(event) {
   // XXXbz this listener gets _all_ load events for all nodes in the
   // document... but relies on not being called "too early".
@@ -3512,50 +3525,63 @@ var gDetailView = {
 
       let detailViewBoxObject = gDetailView.node.boxObject;
       top -= detailViewBoxObject.y;
 
       detailViewBoxObject.scrollTo(0, top);
     }
   },
 
-  createOptionsBrowser: function(parentNode) {
+  createOptionsBrowser: Task.async(function*(parentNode) {
     let browser = document.createElement("browser");
     browser.setAttribute("type", "content");
     browser.setAttribute("disableglobalhistory", "true");
     browser.setAttribute("class", "inline-options-browser");
 
-    return new Promise((resolve, reject) => {
+    let {optionsURL} = this._addon;
+    let remote = !E10SUtils.canLoadURIInProcess(optionsURL, Services.appinfo.PROCESS_TYPE_DEFAULT);
+
+    let readyPromise;
+    if (remote) {
+      browser.setAttribute("remote", "true");
+      readyPromise = promiseEvent("XULFrameLoaderCreated", browser);
+    } else {
+      readyPromise = promiseEvent("load", browser, true);
+    }
+
+    parentNode.appendChild(browser);
+
+    // Force bindings to apply synchronously.
+    browser.clientTop;
+
+    yield readyPromise;
+    if (remote) {
+      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);
         },
       };
 
-      let onload = () => {
-        browser.removeEventListener("load", onload, true);
-
-        let mm = new FakeFrameMessageManager(browser);
-        mm.loadFrameScript("chrome://extensions/content/ext-browser-content.js",
-                           false);
-        mm.addMessageListener("Extension:BrowserContentLoaded", messageListener);
-        mm.addMessageListener("Extension:BrowserResized", messageListener);
-        mm.sendAsyncMessage("Extension:InitBrowser", {fixedWidth: true});
-
-        browser.setAttribute("src", this._addon.optionsURL);
-      };
-      browser.addEventListener("load", onload, true);
-      browser.addEventListener("error", reject);
-
-      parentNode.appendChild(browser);
+      let mm = browser.messageManager || new FakeFrameMessageManager(browser);
+      mm.loadFrameScript("chrome://extensions/content/ext-browser-content.js",
+                         false);
+      mm.addMessageListener("Extension:BrowserContentLoaded", messageListener);
+      mm.addMessageListener("Extension:BrowserResized", messageListener);
+      mm.sendAsyncMessage("Extension:InitBrowser", {fixedWidth: true});
+
+      browser.loadURI(optionsURL);
     });
-  },
+  }),
 
   getSelectedAddon: function() {
     return this._addon;
   },
 
   onEnabling: function() {
     this.updateState();
   },
--- a/toolkit/mozapps/extensions/test/browser/browser_inlinesettings_browser.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_inlinesettings_browser.js
@@ -119,22 +119,24 @@ add_task(function* test_inline_browser_a
 
   function checkHeights(expected) {
     is(body.clientHeight, expected, `Document body should be ${expected}px tall`);
     is(body.clientHeight, body.scrollHeight,
        "Document body should be tall enough to fit its contents");
 
     let heightDiff = browser.clientHeight - expected;
     ok(heightDiff >= 0 && heightDiff < 50,
-       "Browser should be slightly taller than the document body");
+       `Browser should be slightly taller than the document body (${browser.clientHeight} vs. ${expected})`);
   }
 
   // Delay long enough to avoid hitting our resize rate limit.
   let delay = () => new Promise(resolve => setTimeout(resolve, 300));
 
+  yield delay();
+
   checkHeights(300);
 
   info("Increase the document height, and expect the browser to grow correspondingly");
   body.classList.toggle("bigger");
 
   yield delay();
 
   checkHeights(600);