Bug 1317101 - Part 7e: Load extension options pages in a remote browser. r=aswan
☠☠ backed out by ed48815cbe18 ☠ ☠
authorKris Maglione <maglione.k@gmail.com>
Wed, 16 Nov 2016 13:24:08 -0800
changeset 322834 1e1bfb578dcd24433d43fb67cc1f75e87ba48d21
parent 322833 0f8144296a9d8e67a4f307491559a0051f95a9a7
child 322835 e06d269a5d4f35b608b5bc7f1c61e875f2322b45
push id83985
push usermaglione.k@gmail.com
push dateWed, 16 Nov 2016 21:47:37 +0000
treeherdermozilla-inbound@61f8a4084bbd [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);