Bug 1496217 - Support process switching in GeckoView r=esawin
authorJames Willcox <snorp@snorp.net>
Tue, 23 Oct 2018 16:34:05 -0500
changeset 443980 271918d50e1b0d6e3f9b03ce5b9e2fecf58c58a4
parent 443979 92b2b11ba2e1d92918bfe6127bf2bb0187eb267d
child 443981 36e15ad214ea1e7d9febe9ee6742f6b470af563f
push id34977
push userdvarga@mozilla.com
push dateThu, 01 Nov 2018 22:29:07 +0000
treeherdermozilla-central@2dd516cee24f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersesawin
bugs1496217
milestone65.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 1496217 - Support process switching in GeckoView r=esawin Differential Revision: https://phabricator.services.mozilla.com/D9589
mobile/android/app/mobile.js
mobile/android/chrome/geckoview/GeckoViewNavigationContent.js
mobile/android/chrome/geckoview/geckoview.js
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt
mobile/android/modules/geckoview/GeckoViewModule.jsm
mobile/android/modules/geckoview/GeckoViewNavigation.jsm
mobile/android/modules/geckoview/GeckoViewSettings.jsm
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -886,8 +886,13 @@ pref("media.openUnsupportedTypeWithExter
 
 pref("dom.keyboardevent.dispatch_during_composition", true);
 
 // Ask for permission when enumerating WebRTC devices.
 pref("media.navigator.permission.device", true);
 
 // Allow system add-on updates
 pref("extensions.systemAddon.update.url", "https://aus5.mozilla.org/update/3/SystemAddons/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/update.xml");
+
+// E10s stuff. We don't support 'file' or 'priveleged' process types.
+pref("browser.tabs.remote.separateFileUriProcess", false);
+pref("browser.tabs.remote.allowLinkedWebInFileUriProcess", true);
+pref("browser.tabs.remote.separatePrivilegedContentProcess", false);
--- a/mobile/android/chrome/geckoview/GeckoViewNavigationContent.js
+++ b/mobile/android/chrome/geckoview/GeckoViewNavigationContent.js
@@ -2,24 +2,32 @@
 /* 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/. */
 
 ChromeUtils.import("resource://gre/modules/GeckoViewContentModule.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetters(this, {
+  BrowserUtils: "resource://gre/modules/BrowserUtils.jsm",
+  E10SUtils: "resource://gre/modules/E10SUtils.jsm",
   ErrorPageEventHandler: "chrome://geckoview/content/ErrorPageEventHandler.js",
   LoadURIDelegate: "resource://gre/modules/LoadURIDelegate.jsm",
 });
 
 // Implements nsILoadURIDelegate.
 class GeckoViewNavigationContent extends GeckoViewContentModule {
   onInit() {
     docShell.loadURIDelegate = this;
+
+    if (Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_CONTENT) {
+      let tabchild = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+                             .getInterface(Ci.nsITabChild);
+      tabchild.webBrowserChrome = this;
+    }
   }
 
   // nsILoadURIDelegate.
   loadURI(aUri, aWhere, aFlags, aTriggeringPrincipal) {
     debug `loadURI: uri=${aUri && aUri.spec}
                     where=${aWhere} flags=${aFlags}
                     tp=${aTriggeringPrincipal && aTriggeringPrincipal.URI &&
                          aTriggeringPrincipal.URI.spec}`;
@@ -46,12 +54,51 @@ class GeckoViewNavigationContent extends
     if (!this.enabled) {
       Components.returnCode = Cr.NS_ERROR_ABORT;
       return null;
     }
 
     return LoadURIDelegate.handleLoadError(content, this.eventDispatcher,
                                            aUri, aError, aErrorModule);
   }
+
+  // nsIWebBrowserChrome
+  onBeforeLinkTraversal(aOriginalTarget, aLinkURI, aLinkNode, aIsAppTab) {
+    debug `onBeforeLinkTraversal ${aLinkURI.displaySpec}`;
+    return BrowserUtils.onBeforeLinkTraversal(aOriginalTarget, aLinkURI, aLinkNode, aIsAppTab);
+  }
+
+  // nsIWebBrowserChrome
+  shouldLoadURI(aDocShell, aURI, aReferrer, aHasPostData, aTriggeringPrincipal) {
+    debug `shouldLoadURI ${aURI.displaySpec}`;
+
+    // We currently only support one remoteType, "web", so we only need to bail out
+    // if we want to load this URI in the parent.
+    // const remoteType = E10SUtils.getRemoteTypeForURIObject(aURI, true);
+    // if (!remoteType) {
+    //   E10SUtils.redirectLoad(aDocShell, aURI, aReferrer, aTriggeringPrincipal, false);
+    //   return false;
+    // }
+
+    if (!E10SUtils.shouldLoadURI(aDocShell, aURI, aReferrer, aHasPostData)) {
+      E10SUtils.redirectLoad(aDocShell, aURI, aReferrer, aTriggeringPrincipal, false);
+      return false;
+    }
+
+    return true;
+  }
+
+  // nsIWebBrowserChrome
+  shouldLoadURIInThisProcess(aURI) {
+    debug `shouldLoadURIInThisProcess ${aURI.displaySpec}`;
+    return E10SUtils.shouldLoadURIInThisProcess(aURI);
+  }
+
+  // nsIWebBrowserChrome
+  reloadInFreshProcess(aDocShell, aURI, aReferrer, aTriggeringPrincipal, aLoadFlags) {
+    debug `reloadInFreshProcess ${aURI.displaySpec}`;
+    E10SUtils.redirectLoad(aDocShell, aURI, aReferrer, aTriggeringPrincipal, true, aLoadFlags);
+    return true;
+  }
 }
 
 let {debug, warn} = GeckoViewNavigationContent.initLogging("GeckoViewNavigation");
 let module = GeckoViewNavigationContent.create(this);
--- a/mobile/android/chrome/geckoview/geckoview.js
+++ b/mobile/android/chrome/geckoview/geckoview.js
@@ -2,16 +2,17 @@
  * 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";
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetters(this, {
+  E10SUtils: "resource://gre/modules/E10SUtils.jsm",
   EventDispatcher: "resource://gre/modules/Messaging.jsm",
   GeckoViewUtils: "resource://gre/modules/GeckoViewUtils.jsm",
   Services: "resource://gre/modules/Services.jsm",
 });
 
 XPCOMUtils.defineLazyGetter(this, "WindowEventDispatcher",
   () => EventDispatcher.for(window));
 
@@ -93,16 +94,74 @@ var ModuleManager = {
   get settings() {
     return this._frozenSettings;
   },
 
   forEach(aCallback) {
     this._modules.forEach(aCallback, this);
   },
 
+  updateRemoteType(aRemoteType) {
+    debug `updateRemoteType remoteType=${aRemoteType}`;
+
+    const currentRemoteType = this.browser.getAttribute("remoteType");
+
+    if (aRemoteType && !this.settings.useMultiprocess) {
+      warn `Tried to create a remote browser in multiprocess mode`;
+      return false;
+    }
+
+    if (currentRemoteType === aRemoteType) {
+      // We're already using a child process of the correct type.
+      return false;
+    }
+
+    // Now we're switching the remoteness (value of "remote" attr).
+    debug `updateRemoteType: changing from '${currentRemoteType}' to '${aRemoteType}'`;
+
+    this.forEach(module => {
+      if (module.enabled && module.impl) {
+        module.impl.onDisable();
+      }
+    });
+
+    this.forEach(module => {
+      if (module.impl) {
+        module.impl.onDestroyBrowser();
+      }
+    });
+
+    const parent = this.browser.parentNode;
+    this.browser.remove();
+    if (aRemoteType) {
+      this.browser.setAttribute("remote", "true");
+      this.browser.setAttribute("remoteType", aRemoteType);
+    } else {
+      this.browser.setAttribute("remote", "false");
+      this.browser.removeAttribute("remoteType");
+    }
+
+    this.forEach(module => {
+      if (module.impl) {
+        module.impl.onInitBrowser();
+      }
+    });
+
+    parent.appendChild(this.browser);
+
+    this.forEach(module => {
+      if (module.enabled && module.impl) {
+        module.impl.onEnable();
+      }
+    });
+
+    this.browser.focus();
+    return true;
+  },
+
   _updateSettings(aSettings) {
     Object.assign(this._settings, aSettings);
     this._frozenSettings = Object.freeze(Object.assign({}, this._settings));
 
     this.forEach(module => {
       if (module.impl) {
         module.impl.onSettingsUpdate();
       }
@@ -299,16 +358,23 @@ class ModuleInfo {
   }
 }
 
 function createBrowser() {
   const browser = window.browser = document.createElement("browser");
   browser.setAttribute("type", "content");
   browser.setAttribute("primary", "true");
   browser.setAttribute("flex", "1");
+
+  const settings = window.arguments[0].QueryInterface(Ci.nsIAndroidView).initData.settings;
+  if (settings.useMultiprocess) {
+    browser.setAttribute("remote", "true");
+    browser.setAttribute("remoteType", E10SUtils.DEFAULT_REMOTE_TYPE);
+  }
+
   return browser;
 }
 
 function startup() {
   GeckoViewUtils.initLogging("XUL", window);
 
   const browser = createBrowser();
   ModuleManager.init(browser, [{
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt
@@ -968,9 +968,20 @@ class NavigationDelegateTest : BaseSessi
             }
         })
 
         sessionRule.session.evaluateJS("$('#targetBlankLink').click()")
 
         sessionRule.session.waitUntilCalled(GeckoSession.NavigationDelegate::class,
                                             "onNewSession")
     }
+
+    @Test
+    fun processSwitching() {
+        // This loads in the parent process
+        mainSession.loadUri("about:config")
+        sessionRule.waitForPageStop()
+
+        // This will load a page in the child
+        mainSession.loadTestPath(HELLO_HTML_PATH)
+        sessionRule.waitForPageStop()
+    }
 }
--- a/mobile/android/modules/geckoview/GeckoViewModule.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewModule.jsm
@@ -24,38 +24,45 @@ class GeckoViewModule {
     return this._info.name;
   }
 
   get enabled() {
     return this._info.enabled;
   }
 
   get window() {
-    return this._info.manager.window;
+    return this.moduleManager.window;
   }
 
   get browser() {
-    return this._info.manager.browser;
+    return this.moduleManager.browser;
   }
 
   get messageManager() {
-    return this._info.manager.messageManager;
+    return this.moduleManager.messageManager;
   }
 
   get eventDispatcher() {
-    return this._info.manager.eventDispatcher;
+    return this.moduleManager.eventDispatcher;
   }
 
   get settings() {
-    return this._info.manager.settings;
+    return this.moduleManager.settings;
+  }
+
+  get moduleManager() {
+    return this._info.manager;
   }
 
   // Override to initialize the browser before it is bound to the window.
   onInitBrowser() {}
 
+  // Override to cleanup when the browser is destroyed.
+  onDestroyBrowser() {}
+
   // Override to initialize module.
   onInit() {}
 
   // Override to cleanup when the window is closed
   onDestroy() {}
 
   // Override to detect settings change. Access settings via this.settings.
   onSettingsUpdate() {}
--- a/mobile/android/modules/geckoview/GeckoViewNavigation.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewNavigation.jsm
@@ -5,16 +5,18 @@
 "use strict";
 
 var EXPORTED_SYMBOLS = ["GeckoViewNavigation"];
 
 ChromeUtils.import("resource://gre/modules/GeckoViewModule.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetters(this, {
+  E10SUtils: "resource://gre/modules/E10SUtils.jsm",
+  Utils: "resource://gre/modules/sessionstore/Utils.jsm",
   LoadURIDelegate: "resource://gre/modules/LoadURIDelegate.jsm",
   Services: "resource://gre/modules/Services.jsm",
 });
 
 // Handles navigation requests between Gecko and a GeckoView.
 // Handles GeckoView:GoBack and :GoForward requests dispatched by
 // GeckoView.goBack and .goForward.
 // Dispatches GeckoView:LocationChange to the GeckoView on location change when
@@ -33,16 +35,18 @@ class GeckoViewNavigation extends GeckoV
   onInit() {
     this.registerListener([
       "GeckoView:GoBack",
       "GeckoView:GoForward",
       "GeckoView:LoadUri",
       "GeckoView:Reload",
       "GeckoView:Stop",
     ]);
+
+    this.messageManager.addMessageListener("Browser:LoadURI", this);
   }
 
   // Bundle event handler.
   onEvent(aEvent, aData, aCallback) {
     debug `onEvent: event=${aEvent}, data=${aData}`;
 
     switch (aEvent) {
       case "GeckoView:GoBack":
@@ -68,16 +72,20 @@ class GeckoViewNavigation extends GeckoV
         if (flags & (1 << 2)) {
           navFlags |= Ci.nsIWebNavigation.LOAD_FLAGS_EXTERNAL;
         }
 
         if (flags & (1 << 3)) {
           navFlags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_POPUPS;
         }
 
+        const remoteType =
+          E10SUtils.getRemoteTypeForURI(uri, this.settings.useMultiprocess);
+        this.moduleManager.updateRemoteType(remoteType);
+
         this.browser.loadURI(uri, {
           flags: navFlags,
           referrerURI: referrer,
         });
         break;
       case "GeckoView:Reload":
         this.browser.reload();
         break;
@@ -85,16 +93,33 @@ class GeckoViewNavigation extends GeckoV
         this.browser.stop();
         break;
     }
   }
 
   // Message manager event handler.
   receiveMessage(aMsg) {
     debug `receiveMessage: ${aMsg.name}`;
+
+    switch (aMsg.name) {
+      case "Browser:LoadURI":
+        // This is triggered by E10SUtils.redirectLoad(), and means
+        // we may need to change the remoteness of our browser and
+        // load the URI.
+        const { uri, flags, referrer, triggeringPrincipal } = aMsg.data.loadOptions;
+        const remoteType =
+          E10SUtils.getRemoteTypeForURI(uri, this.settings.useMultiprocess);
+
+        this.moduleManager.updateRemoteType(remoteType);
+        this.browser.loadURI(aMsg.data.loadOptions.uri, {
+          flags, referrerURI: referrer,
+          triggeringPrincipal: Utils.deserializePrincipal(triggeringPrincipal),
+        });
+        break;
+    }
   }
 
   waitAndSetupWindow(aSessionId, { opener, nextTabParentId }) {
     if (!aSessionId) {
       return Promise.resolve(null);
     }
 
     return new Promise(resolve => {
--- a/mobile/android/modules/geckoview/GeckoViewSettings.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewSettings.jsm
@@ -39,22 +39,16 @@ XPCOMUtils.defineLazyGetter(
 const USER_AGENT_MODE_MOBILE = 0;
 const USER_AGENT_MODE_DESKTOP = 1;
 const USER_AGENT_MODE_VR = 2;
 
 // Handles GeckoView settings including:
 // * multiprocess
 // * user agent override
 class GeckoViewSettings extends GeckoViewModule {
-  onInitBrowser() {
-    if (this.settings.useMultiprocess) {
-      this.browser.setAttribute("remote", "true");
-    }
-  }
-
   onInit() {
     debug `onInit`;
     this._useTrackingProtection = false;
     this._userAgentMode = USER_AGENT_MODE_MOBILE;
     // Required for safe browsing and tracking protection.
     SafeBrowsing.init();
 
     this.registerListener([