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 323103 3330301202ab60286f826d61a473f1e1a8057301
parent 323102 29f9433a43acf579cb3314c484d8a0eff2e88d72
child 323104 2fa61db0ad05e0d55f58ed2046c8a41503a3ce13
push id84052
push usermaglione.k@gmail.com
push dateThu, 17 Nov 2016 22:02:22 +0000
treeherdermozilla-inbound@06ef26669994 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan
bugs1317101
milestone53.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 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);