Bug 1147911 Part 2: Add a remote type property and use it to drive the process switching in frontend code. r=gijs, r=jryans, r=mikedeboer
☠☠ backed out by bbaedc341b7c ☠ ☠
authorBob Owen <bobowencode@gmail.com>
Wed, 23 Nov 2016 13:36:57 +0000
changeset 324081 5b26ae9afaea99909f22dc274f259575c0a998dc
parent 324080 e793767cb441db6307235e81575c04bf60e1139d
child 324082 ec84ee6acb880a630dbdd274b074713984a75ed7
push id24
push usermaklebus@msu.edu
push dateTue, 20 Dec 2016 03:11:33 +0000
reviewersgijs, jryans, mikedeboer
bugs1147911
milestone53.0a1
Bug 1147911 Part 2: Add a remote type property and use it to drive the process switching in frontend code. r=gijs, r=jryans, r=mikedeboer
browser/base/content/browser.js
browser/base/content/tabbrowser.xml
browser/components/sessionstore/SessionStore.jsm
browser/modules/E10SUtils.jsm
devtools/client/responsive.html/browser/swap.js
devtools/client/responsive.html/browser/tunnel.js
devtools/client/responsive.html/components/browser.js
toolkit/components/viewsource/ViewSourceBrowser.jsm
toolkit/components/viewsource/content/viewSource.js
toolkit/content/widgets/browser.xml
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -825,24 +825,23 @@ function _loadURIWithFlags(browser, uri,
     uri = "about:blank";
   }
   let flags = params.flags || 0;
   let referrer = params.referrerURI;
   let referrerPolicy = ('referrerPolicy' in params ? params.referrerPolicy :
                         Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT);
   let postData = params.postData;
 
-  let wasRemote = browser.isRemoteBrowser;
-
-  let process = browser.isRemoteBrowser ? Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT
-                                        : Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
-  let mustChangeProcess = gMultiProcessBrowser &&
-                          !E10SUtils.canLoadURIInProcess(uri, process);
-  if ((!wasRemote && !mustChangeProcess) ||
-      (wasRemote && mustChangeProcess)) {
+  let currentRemoteType = browser.remoteType;
+  let requiredRemoteType =
+    E10SUtils.getRemoteTypeForURI(uri, gMultiProcessBrowser, currentRemoteType);
+  let mustChangeProcess = requiredRemoteType != currentRemoteType;
+
+  // !requiredRemoteType means we're loading in the parent/this process.
+  if (!requiredRemoteType) {
     browser.inLoadURI = true;
   }
   try {
     if (!mustChangeProcess) {
       if (params.userContextId) {
         browser.webNavigation.setOriginAttributesBeforeLoading({ userContextId: params.userContextId });
       }
 
@@ -883,18 +882,17 @@ function _loadURIWithFlags(browser, uri,
       }
 
       browser.webNavigation.loadURIWithOptions(uri, flags, referrer, referrerPolicy,
                                                postData, null, null);
     } else {
       throw e;
     }
   } finally {
-    if ((!wasRemote && !mustChangeProcess) ||
-        (wasRemote && mustChangeProcess)) {
+    if (!requiredRemoteType) {
       browser.inLoadURI = false;
     }
   }
 }
 
 // Starts a new load in the browser first switching the browser to the correct
 // process
 function LoadInOtherProcess(browser, loadOptions, historyIndex = -1) {
@@ -1140,32 +1138,22 @@ var gBrowserInit = {
 
         // We must set usercontextid before updateBrowserRemoteness()
         // so that the newly created remote tab child has correct usercontextid
         if (tabToOpen.hasAttribute("usercontextid")) {
           let usercontextid = tabToOpen.getAttribute("usercontextid");
           gBrowser.selectedBrowser.setAttribute("usercontextid", usercontextid);
         }
 
-        // If the browser that we're swapping in was remote, then we'd better
-        // be able to support remote browsers, and then make our selectedTab
-        // remote.
         try {
-          if (tabToOpen.linkedBrowser.isRemoteBrowser) {
-            if (!gMultiProcessBrowser) {
-              throw new Error("Cannot drag a remote browser into a window " +
-                              "without the remote tabs load context.");
-            }
-            gBrowser.updateBrowserRemoteness(gBrowser.selectedBrowser, true);
-          } else if (gBrowser.selectedBrowser.isRemoteBrowser) {
-            // If the browser is remote, then it's implied that
-            // gMultiProcessBrowser is true. We need to flip the remoteness
-            // of this tab to false in order for the tab drag to work.
-            gBrowser.updateBrowserRemoteness(gBrowser.selectedBrowser, false);
-          }
+          // Make sure selectedBrowser has the same remote settings as the one
+          // we are swapping in.
+          gBrowser.updateBrowserRemoteness(gBrowser.selectedBrowser,
+                                           tabToOpen.linkedBrowser.isRemoteBrowser,
+                                           tabToOpen.linkedBrowser.remoteType);
           gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, tabToOpen);
         } catch (e) {
           Cu.reportError(e);
         }
       }
       // window.arguments[2]: referrer (nsIURI | string)
       //                 [3]: postData (nsIInputStream)
       //                 [4]: allowThirdPartyFixup (bool)
@@ -2266,34 +2254,32 @@ function BrowserViewSourceOfDocument(aAr
   } else {
     args = aArgsOrDocument;
   }
 
   let viewInternal = () => {
     let inTab = Services.prefs.getBoolPref("view_source.tab");
     if (inTab) {
       let tabBrowser = gBrowser;
-      let forceNotRemote = false;
+      let preferredRemoteType;
       if (!tabBrowser) {
         if (!args.browser) {
           throw new Error("BrowserViewSourceOfDocument should be passed the " +
                           "subject browser if called from a window without " +
                           "gBrowser defined.");
         }
-        forceNotRemote = !args.browser.isRemoteBrowser;
+        preferredRemoteType = args.browser.remoteType;
       } else {
         // Some internal URLs (such as specific chrome: and about: URLs that are
         // not yet remote ready) cannot be loaded in a remote browser.  View
         // source in tab expects the new view source browser's remoteness to match
         // that of the original URL, so disable remoteness if necessary for this
         // URL.
-        let contentProcess = Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT
-        forceNotRemote =
-          gMultiProcessBrowser &&
-          !E10SUtils.canLoadURIInProcess(args.URL, contentProcess)
+        preferredRemoteType =
+          E10SUtils.getRemoteTypeForURI(args.URL, gMultiProcessBrowser);
       }
 
       // In the case of popups, we need to find a non-popup browser window.
       if (!tabBrowser || !window.toolbar.visible) {
         // This returns only non-popup browser windows by default.
         let browserWindow = RecentWindow.getMostRecentBrowserWindow();
         tabBrowser = browserWindow.gBrowser;
       }
@@ -2301,17 +2287,17 @@ function BrowserViewSourceOfDocument(aAr
       // `viewSourceInBrowser` will load the source content from the page
       // descriptor for the tab (when possible) or fallback to the network if
       // that fails.  Either way, the view source module will manage the tab's
       // location, so use "about:blank" here to avoid unnecessary redundant
       // requests.
       let tab = tabBrowser.loadOneTab("about:blank", {
         relatedToCurrent: true,
         inBackground: false,
-        forceNotRemote,
+        preferredRemoteType,
         relatedBrowser: args.browser
       });
       args.viewSourceBrowser = tabBrowser.getBrowserForTab(tab);
       top.gViewSourceUtils.viewSourceInBrowser(args);
     } else {
       top.gViewSourceUtils.viewSource(args);
     }
   }
@@ -3260,21 +3246,21 @@ function getPEMString(cert)
 var PrintPreviewListener = {
   _printPreviewTab: null,
   _tabBeforePrintPreview: null,
   _simplifyPageTab: null,
 
   getPrintPreviewBrowser: function() {
     if (!this._printPreviewTab) {
       let browser = gBrowser.selectedTab.linkedBrowser;
-      let forceNotRemote = gMultiProcessBrowser && !browser.isRemoteBrowser;
+      let preferredRemoteType = browser.remoteType;
       this._tabBeforePrintPreview = gBrowser.selectedTab;
       this._printPreviewTab = gBrowser.loadOneTab("about:blank",
                                                   { inBackground: false,
-                                                    forceNotRemote,
+                                                    preferredRemoteType,
                                                     relatedBrowser: browser });
       gBrowser.selectedTab = this._printPreviewTab;
     }
     return gBrowser.getBrowserForTab(this._printPreviewTab);
   },
   createSimplifiedBrowser: function() {
     this._simplifyPageTab = gBrowser.loadOneTab("about:blank",
                                                 { inBackground: true });
@@ -4227,17 +4213,17 @@ var XULBrowserWindow = {
     let initBrowser =
       document.getAnonymousElementByAttribute(gBrowser, "anonid", "initialBrowser");
     return initBrowser.frameLoader.tabParent;
   },
 
   forceInitialBrowserNonRemote: function(aOpener) {
     let initBrowser =
       document.getAnonymousElementByAttribute(gBrowser, "anonid", "initialBrowser");
-    gBrowser.updateBrowserRemoteness(initBrowser, false, aOpener);
+    gBrowser.updateBrowserRemoteness(initBrowser, false, E10SUtils.NOT_REMOTE, aOpener);
   },
 
   setDefaultStatus: function(status) {
     this.defaultStatus = status;
     this.updateStatusField();
   },
 
   setOverLink: function(url, anchorElt) {
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1509,16 +1509,17 @@
         <body>
           <![CDATA[
             var aReferrerPolicy;
             var aFromExternal;
             var aRelatedToCurrent;
             var aAllowMixedContent;
             var aSkipAnimation;
             var aForceNotRemote;
+            var aPreferredRemoteType;
             var aNoReferrer;
             var aUserContextId;
             var aRelatedBrowser;
             var aOriginPrincipal;
             var aOpener;
             if (arguments.length == 2 &&
                 typeof arguments[1] == "object" &&
                 !(arguments[1] instanceof Ci.nsIURI)) {
@@ -1529,16 +1530,17 @@
               aPostData             = params.postData;
               aLoadInBackground     = params.inBackground;
               aAllowThirdPartyFixup = params.allowThirdPartyFixup;
               aFromExternal         = params.fromExternal;
               aRelatedToCurrent     = params.relatedToCurrent;
               aAllowMixedContent    = params.allowMixedContent;
               aSkipAnimation        = params.skipAnimation;
               aForceNotRemote       = params.forceNotRemote;
+              aPreferredRemoteType  = params.preferredRemoteType;
               aNoReferrer           = params.noReferrer;
               aUserContextId        = params.userContextId;
               aRelatedBrowser       = params.relatedBrowser;
               aOriginPrincipal      = params.originPrincipal;
               aOpener               = params.opener;
             }
 
             var bgLoad = (aLoadInBackground != null) ? aLoadInBackground :
@@ -1551,16 +1553,17 @@
                                   postData: aPostData,
                                   ownerTab: owner,
                                   allowThirdPartyFixup: aAllowThirdPartyFixup,
                                   fromExternal: aFromExternal,
                                   relatedToCurrent: aRelatedToCurrent,
                                   skipAnimation: aSkipAnimation,
                                   allowMixedContent: aAllowMixedContent,
                                   forceNotRemote: aForceNotRemote,
+                                  preferredRemoteType: aPreferredRemoteType,
                                   noReferrer: aNoReferrer,
                                   userContextId: aUserContextId,
                                   originPrincipal: aOriginPrincipal,
                                   relatedBrowser: aRelatedBrowser,
                                   opener: aOpener });
             if (!bgLoad)
               this.selectedTab = tab;
 
@@ -1668,36 +1671,48 @@
               this.selectedBrowser.focus();
           }
         ]]></body>
       </method>
 
       <method name="updateBrowserRemoteness">
         <parameter name="aBrowser"/>
         <parameter name="aShouldBeRemote"/>
+        <parameter name="aNewRemoteType"/>
         <parameter name="aOpener"/>
         <parameter name="aFreshProcess"/>
         <body>
           <![CDATA[
+            if (!gMultiProcessBrowser && aShouldBeRemote) {
+              throw new Error("Cannot switch to remote browser in a window " +
+                              "without the remote tabs load context.");
+            }
+
             let isRemote = aBrowser.getAttribute("remote") == "true";
 
+            // If going remote and no aNewRemoteType then use default.
+            if (aShouldBeRemote && !aNewRemoteType) {
+              aNewRemoteType = E10SUtils.DEFAULT_REMOTE_TYPE;
+            }
+
             // If we are passed an opener, we must be making the browser non-remote, and
             // if the browser is _currently_ non-remote, we need the openers to match,
             // because it is already too late to change it.
             if (aOpener) {
               if (aShouldBeRemote) {
-                throw new Exception("Cannot set an opener on a browser which should be remote!");
+                throw new Error("Cannot set an opener on a browser which should be remote!");
               }
               if (!isRemote && aBrowser.contentWindow.opener != aOpener) {
-                throw new Exception("Cannot change opener on an already non-remote browser!");
+                throw new Error("Cannot change opener on an already non-remote browser!");
               }
             }
 
             // Abort if we're not going to change anything
-            if (isRemote == aShouldBeRemote && !aFreshProcess) {
+            if (isRemote == aShouldBeRemote && !aFreshProcess &&
+                (!isRemote || aBrowser.getAttribute("remotetype") == aNewRemoteType)) {
               return false;
             }
 
             let tab = this.getTabForBrowser(aBrowser);
             let evt = document.createEvent("Events");
             evt.initEvent("BeforeTabRemotenessChange", true, false);
             tab.dispatchEvent(evt);
 
@@ -1724,17 +1739,23 @@
             // Make sure to restore the original droppedLinkHandler and
             // relatedBrowser.
             let droppedLinkHandler = aBrowser.droppedLinkHandler;
             let relatedBrowser = aBrowser.relatedBrowser;
 
             // Change the "remote" attribute.
             let parent = aBrowser.parentNode;
             parent.removeChild(aBrowser);
-            aBrowser.setAttribute("remote", aShouldBeRemote ? "true" : "false");
+            if (aShouldBeRemote) {
+              aBrowser.setAttribute("remote", "true");
+              aBrowser.setAttribute("remoteType", aNewRemoteType);
+            } else {
+              aBrowser.setAttribute("remote", "false");
+              aBrowser.removeAttribute("remoteType");
+            }
 
             // NB: This works with the hack in the browser constructor that
             // turns this normal property into a field.
             aBrowser.relatedBrowser = relatedBrowser;
 
             // Set the opener window on the browser, such that when the frame
             // loader is created the opener is set correctly.
             aBrowser.presetOpenerWindow(aOpener);
@@ -1808,47 +1829,38 @@
             evt.initEvent("TabRemotenessChange", true, false);
             tab.dispatchEvent(evt);
 
             return true;
           ]]>
         </body>
       </method>
 
-      <method name="switchBrowserIntoFreshProcess">
-        <parameter name="aBrowser"/>
-        <body>
-          <![CDATA[
-            if (!gMultiProcessBrowser) {
-              return this.updateBrowserRemoteness(aBrowser, false);
-            }
-
-            return this.updateBrowserRemoteness(aBrowser,
-                                                /* aShouldBeRemote */ true,
-                                                /* aOpener */ null,
-                                                /* aFreshProcess */ true);
-          ]]>
-        </body>
-      </method>
-
       <method name="updateBrowserRemotenessByURL">
         <parameter name="aBrowser"/>
         <parameter name="aURL"/>
+        <parameter name="aFreshProcess"/>
         <body>
           <![CDATA[
             if (!gMultiProcessBrowser)
               return this.updateBrowserRemoteness(aBrowser, false);
 
-            let process = aBrowser.isRemoteBrowser ? Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT
-                                                   : Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
-
-            // If this URL can't load in the browser's current process then flip
-            // it to the other process
-            if (!E10SUtils.canLoadURIInProcess(aURL, process))
-              return this.updateBrowserRemoteness(aBrowser, !aBrowser.isRemoteBrowser);
+            // If this URL can't load in the current browser then flip it to the
+            // correct type.
+            let currentRemoteType = aBrowser.remoteType;
+            let requiredRemoteType =
+              E10SUtils.getRemoteTypeForURI(aURL, gMultiProcessBrowser,
+                                            currentRemoteType);
+            if (currentRemoteType != requiredRemoteType || aFreshProcess) {
+              return this.updateBrowserRemoteness(aBrowser,
+                                                  !!requiredRemoteType,
+                                                  requiredRemoteType,
+                                                  /* aOpener */ null,
+                                                  aFreshProcess);
+            }
 
             return false;
           ]]>
         </body>
       </method>
 
       <field name="_preloadedBrowser">null</field>
       <method name="_getPreloadedBrowser">
@@ -1895,25 +1907,26 @@
         <body>
           <![CDATA[
             // Do nothing if we have a preloaded browser already
             // or preloading of newtab pages is disabled.
             if (this._preloadedBrowser || !this._isPreloadingEnabled()) {
               return;
             }
 
-            let remote = gMultiProcessBrowser &&
-                         E10SUtils.canLoadURIInProcess(BROWSER_NEW_TAB_URL, Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT);
-            let browser = this._createBrowser({isPreloadBrowser: true, remote: remote});
+            let remoteType =
+              E10SUtils.getRemoteTypeForURI(BROWSER_NEW_TAB_URL,
+                                            gMultiProcessBrowser);
+            let browser = this._createBrowser({isPreloadBrowser: true, remoteType});
             this._preloadedBrowser = browser;
 
             let notificationbox = this.getNotificationBox(browser);
             this.mPanelContainer.appendChild(notificationbox);
 
-            if (remote) {
+            if (remoteType != E10SUtils.NOT_REMOTE) {
               // For remote browsers, we need to make sure that the webProgress is
               // instantiated, otherwise the parent won't get informed about the state
               // of the preloaded browser until it gets attached to a tab.
               browser.webProgress;
             }
 
             browser.loadURI(BROWSER_NEW_TAB_URL);
             browser.docShellIsActive = false;
@@ -1921,38 +1934,45 @@
         </body>
       </method>
 
       <method name="_createBrowser">
         <parameter name="aParams"/>
         <body>
           <![CDATA[
             // Supported parameters:
-            // userContextId, remote, isPreloadBrowser, uriIsAboutBlank, permanentKey
+            // userContextId, remote, remoteType, isPreloadBrowser,
+            // uriIsAboutBlank, permanentKey
 
             const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
             let b = document.createElementNS(NS_XUL, "browser");
             b.permanentKey = aParams.permanentKey || {};
             b.setAttribute("type", "content-targetable");
             b.setAttribute("message", "true");
             b.setAttribute("messagemanagergroup", "browsers");
             b.setAttribute("contextmenu", this.getAttribute("contentcontextmenu"));
             b.setAttribute("tooltip", this.getAttribute("contenttooltip"));
 
             if (aParams.userContextId) {
               b.setAttribute("usercontextid", aParams.userContextId);
             }
 
-            if (aParams.remote) {
+            // remote parameter used by some addons, use default in this case.
+            if (aParams.remote && !aParams.remoteType) {
+              aParams.remoteType = E10SUtils.DEFAULT_REMOTE_TYPE;
+            }
+
+            if (aParams.remoteType) {
+              b.setAttribute("remoteType", aParams.remoteType);
               b.setAttribute("remote", "true");
             }
 
             if (aParams.opener) {
-              if (aParams.remote) {
+              if (aParams.remoteType) {
                 throw new Exception("Cannot set opener window on a remote browser!");
               }
               b.QueryInterface(Ci.nsIFrameLoaderOwner).presetOpenerWindow(aParams.opener);
             }
 
             if (window.gShowPageResizers && window.windowState == window.STATE_NORMAL) {
               b.setAttribute("showresizer", "true");
             }
@@ -2015,25 +2035,24 @@
         <parameter name="aTab"/>
         <parameter name="aURI"/>
         <parameter name="aParams"/>
         <body>
           <![CDATA[
             "use strict";
 
             // Supported parameters:
-            // forceNotRemote, userContextId
+            // forceNotRemote, preferredRemoteType, userContextId
 
             let uriIsAboutBlank = !aURI || aURI == "about:blank";
 
-            // The new browser should be remote if this is an e10s window and
-            // the uri to load can be loaded remotely.
-            let remote = gMultiProcessBrowser &&
-                         !aParams.forceNotRemote &&
-                         E10SUtils.canLoadURIInProcess(aURI, Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT);
+            let remoteType =
+              aParams.forceNotRemote ? E10SUtils.NOT_REMOTE
+              : E10SUtils.getRemoteTypeForURI(aURI, gMultiProcessBrowser,
+                                              aParams.preferredRemoteType);
 
             let browser;
             let usingPreloadedContent = false;
 
             // If we open a new tab with the newtab URL in the default
             // userContext, check if there is a preloaded browser ready.
             // Private windows are not included because both the label and the
             // icon for the tab would be set incorrectly (see bug 1195981).
@@ -2045,17 +2064,17 @@
                 usingPreloadedContent = true;
                 aTab.permanentKey = browser.permanentKey;
               }
             }
 
             if (!browser) {
               // No preloaded browser found, create one.
               browser = this._createBrowser({permanentKey: aTab.permanentKey,
-                                             remote: remote,
+                                             remoteType,
                                              uriIsAboutBlank: uriIsAboutBlank,
                                              userContextId: aParams.userContextId,
                                              relatedBrowser: aParams.relatedBrowser,
                                              opener: aParams.opener});
             }
 
             let notificationbox = this.getNotificationBox(browser);
             let uniqueId = this._generateUniquePanelID();
@@ -2090,17 +2109,17 @@
             // activeness in the tab switcher.
             browser.docShellIsActive = false;
 
             // When addTab() is called with an URL that is not "about:blank" we
             // set the "nodefaultsrc" attribute that prevents a frameLoader
             // from being created as soon as the linked <browser> is inserted
             // into the DOM. We thus have to register the new outerWindowID
             // for non-remote browsers after we have called browser.loadURI().
-            if (!remote) {
+            if (remoteType == E10SUtils.NOT_REMOTE) {
               this._outerWindowIDBrowserMap.set(browser.outerWindowID, browser);
             }
 
             var evt = new CustomEvent("TabBrowserInserted", { bubbles: true, detail: {} });
             aTab.dispatchEvent(evt);
 
             return { usingPreloadedContent: usingPreloadedContent };
           ]]>
@@ -2120,16 +2139,17 @@
 
             const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
             var aReferrerPolicy;
             var aFromExternal;
             var aRelatedToCurrent;
             var aSkipAnimation;
             var aAllowMixedContent;
             var aForceNotRemote;
+            var aPreferredRemoteType;
             var aNoReferrer;
             var aUserContextId;
             var aEventDetail;
             var aRelatedBrowser;
             var aOriginPrincipal;
             var aDisallowInheritPrincipal;
             var aOpener;
             if (arguments.length == 2 &&
@@ -2142,16 +2162,17 @@
               aPostData                 = params.postData;
               aOwner                    = params.ownerTab;
               aAllowThirdPartyFixup     = params.allowThirdPartyFixup;
               aFromExternal             = params.fromExternal;
               aRelatedToCurrent         = params.relatedToCurrent;
               aSkipAnimation            = params.skipAnimation;
               aAllowMixedContent        = params.allowMixedContent;
               aForceNotRemote           = params.forceNotRemote;
+              aPreferredRemoteType      = params.preferredRemoteType;
               aNoReferrer               = params.noReferrer;
               aUserContextId            = params.userContextId;
               aEventDetail              = params.eventDetail;
               aRelatedBrowser           = params.relatedBrowser;
               aOriginPrincipal          = params.originPrincipal;
               aDisallowInheritPrincipal = params.disallowInheritPrincipal;
               aOpener                   = params.opener;
             }
@@ -2215,16 +2236,17 @@
             // browser to immediately be linked.  In future incarnations of this
             // bug this will be removed so we can leave the tab in its "lazy"
             // state to be exploited for startup optimization.  Note that for
             // now this must occur before "TabOpen" event is fired, as that will
             // trigger SessionStore.jsm to run code that expects the existence
             // of tab.linkedBrowser.
             let browserParams = {
               forceNotRemote: aForceNotRemote,
+              preferredRemoteType: aPreferredRemoteType,
               userContextId:  aUserContextId,
               relatedBrowser: aRelatedBrowser,
               opener: aOpener,
             };
             let { usingPreloadedContent } = this._linkBrowserToTab(t, aURI, browserParams);
             let b = t.linkedBrowser;
 
             // Dispatch a new tab notification.  We do this once we're
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -3538,23 +3538,18 @@ var SessionStoreInternal = {
     }
 
     // We have to mark this tab as restoring first, otherwise
     // the "pending" attribute will be applied to the linked
     // browser, which removes it from the display list. We cannot
     // flip the remoteness of any browser that is not being displayed.
     this.markTabAsRestoring(aTab);
 
-    let isRemotenessUpdate = false;
-    if (aReloadInFreshProcess) {
-      isRemotenessUpdate = tabbrowser.switchBrowserIntoFreshProcess(browser);
-    } else {
-      isRemotenessUpdate = tabbrowser.updateBrowserRemotenessByURL(browser, uri);
-    }
-
+    let isRemotenessUpdate =
+      tabbrowser.updateBrowserRemotenessByURL(browser, uri, aReloadInFreshProcess);
     if (isRemotenessUpdate) {
       // We updated the remoteness, so we need to send the history down again.
       //
       // Start a new epoch to discard all frame script messages relating to a
       // previous epoch. All async messages that are still on their way to chrome
       // will be ignored and don't override any tab data set when restoring.
       let epoch = this.startNextEpoch(browser);
 
--- a/browser/modules/E10SUtils.jsm
+++ b/browser/modules/E10SUtils.jsm
@@ -23,78 +23,115 @@ function getAboutModule(aURL) {
   }
   catch (e) {
     // Either the about module isn't defined or it is broken. In either case
     // ignore it.
     return null;
   }
 }
 
+const NOT_REMOTE = null;
+const WEB_REMOTE_TYPE = "web";
+const DEFAULT_REMOTE_TYPE = WEB_REMOTE_TYPE;
+
 this.E10SUtils = {
+  DEFAULT_REMOTE_TYPE,
+  NOT_REMOTE,
+  WEB_REMOTE_TYPE,
+
   canLoadURIInProcess: function(aURL, aProcess) {
+    let remoteType = aProcess == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT
+                     ? DEFAULT_REMOTE_TYPE : NOT_REMOTE;
+    return remoteType == this.getRemoteTypeForURI(aURL, true, remoteType);
+  },
+
+  getRemoteTypeForURI: function(aURL, aMultiProcess,
+                                aPreferredRemoteType = DEFAULT_REMOTE_TYPE) {
+    if (!aMultiProcess) {
+      return NOT_REMOTE;
+    }
+
     // loadURI in browser.xml treats null as about:blank
-    if (!aURL)
+    if (!aURL) {
       aURL = "about:blank";
+    }
 
     // Javascript urls can load in any process, they apply to the current document
-    if (aURL.startsWith("javascript:"))
-      return true;
+    if (aURL.startsWith("javascript:")) {
+      return aPreferredRemoteType;
+    }
 
-    let processIsRemote = aProcess == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT;
-
-    let canLoadRemote = true;
-    let mustLoadRemote = true;
+    // We need data: URIs to load in any remote process, because some of our
+    // tests rely on this.
+    if (aURL.startsWith("data:")) {
+      return aPreferredRemoteType == NOT_REMOTE ? DEFAULT_REMOTE_TYPE
+                                                : aPreferredRemoteType;
+    }
 
     if (aURL.startsWith("about:")) {
+      // We need to special case about:blank because it needs to load in any.
+      if (aURL == "about:blank") {
+        return aPreferredRemoteType;
+      }
+
       let url = Services.io.newURI(aURL, null, null);
       let module = getAboutModule(url);
       // If the module doesn't exist then an error page will be loading, that
-      // should be ok to load in either process
-      if (module) {
-        let flags = module.getURIFlags(url);
-        canLoadRemote = !!(flags & Ci.nsIAboutModule.URI_CAN_LOAD_IN_CHILD);
-        mustLoadRemote = !!(flags & Ci.nsIAboutModule.URI_MUST_LOAD_IN_CHILD);
+      // should be ok to load in any process
+      if (!module) {
+        return aPreferredRemoteType;
       }
+
+      let flags = module.getURIFlags(url);
+      if (flags & Ci.nsIAboutModule.URI_MUST_LOAD_IN_CHILD) {
+        return DEFAULT_REMOTE_TYPE;
+      }
+
+      if (flags & Ci.nsIAboutModule.URI_CAN_LOAD_IN_CHILD &&
+          aPreferredRemoteType != NOT_REMOTE) {
+        return DEFAULT_REMOTE_TYPE;
+      }
+
+      return NOT_REMOTE;
     }
 
     if (aURL.startsWith("chrome:")) {
       let url;
       try {
         // This can fail for invalid Chrome URIs, in which case we will end up
         // not loading anything anyway.
         url = Services.io.newURI(aURL, null, null);
       } catch (ex) {
-        canLoadRemote = true;
-        mustLoadRemote = false;
+        return aPreferredRemoteType;
       }
 
-      if (url) {
-        let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].
-                        getService(Ci.nsIXULChromeRegistry);
-        canLoadRemote = chromeReg.canLoadURLRemotely(url);
-        mustLoadRemote = chromeReg.mustLoadURLRemotely(url);
+      let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].
+                      getService(Ci.nsIXULChromeRegistry);
+      if (chromeReg.mustLoadURLRemotely(url)) {
+        return DEFAULT_REMOTE_TYPE;
       }
+
+      if (chromeReg.canLoadURLRemotely(url) &&
+          aPreferredRemoteType != NOT_REMOTE) {
+        return DEFAULT_REMOTE_TYPE;
+      }
+
+      return NOT_REMOTE;
     }
 
     if (aURL.startsWith("moz-extension:")) {
-      canLoadRemote = useRemoteWebExtensions;
-      mustLoadRemote = useRemoteWebExtensions;
+      return useRemoteWebExtensions ? WEB_REMOTE_TYPE : NOT_REMOTE;
     }
 
     if (aURL.startsWith("view-source:")) {
-      return this.canLoadURIInProcess(aURL.substr("view-source:".length), aProcess);
+      return this.getRemoteTypeForURI(aURL.substr("view-source:".length),
+                                      aMultiProcess, aPreferredRemoteType);
     }
 
-    if (mustLoadRemote)
-      return processIsRemote;
-
-    if (!canLoadRemote && processIsRemote)
-      return false;
-
-    return true;
+    return WEB_REMOTE_TYPE;
   },
 
   shouldLoadURI: function(aDocShell, aURI, aReferrer) {
     // Inner frames should always load in the current process
     if (aDocShell.QueryInterface(Ci.nsIDocShellTreeItem).sameTypeParent)
       return true;
 
     // If the URI can be loaded in the current process then continue
--- a/devtools/client/responsive.html/browser/swap.js
+++ b/devtools/client/responsive.html/browser/swap.js
@@ -134,17 +134,18 @@ function swapToInnerBrowser({ tab, conta
       //    `gBrowser._swapBrowserDocShells`.
       dispatchDevToolsBrowserSwap(innerBrowser, contentBrowser);
       gBrowser._swapBrowserDocShells(contentTab, innerBrowser);
       innerBrowser = null;
 
       // 5. Force the original browser tab to be remote since web content is
       //    loaded in the child process, and we're about to swap the content
       //    into this tab.
-      gBrowser.updateBrowserRemoteness(tab.linkedBrowser, true);
+      gBrowser.updateBrowserRemoteness(tab.linkedBrowser, true,
+                                       contentBrowser.remoteType);
 
       // 6. Swap the content into the original browser tab and close the
       //    temporary tab used to hold the content via
       //    `swapBrowsersAndCloseOther`.
       dispatchDevToolsBrowserSwap(contentBrowser, tab.linkedBrowser);
       gBrowser.swapBrowsersAndCloseOther(tab, contentTab);
       gBrowser = null;
 
@@ -207,16 +208,25 @@ function addXULBrowserDecorations(browse
     Object.defineProperty(browser, "isRemoteBrowser", {
       get() {
         return this.getAttribute("remote") == "true";
       },
       configurable: true,
       enumerable: true,
     });
   }
+  if (browser.remoteType == undefined) {
+    Object.defineProperty(browser, "remoteType", {
+      get() {
+        return this.getAttribute("remoteType");
+      },
+      configurable: true,
+      enumerable: true,
+    });
+  }
   if (browser.messageManager == undefined) {
     Object.defineProperty(browser, "messageManager", {
       get() {
         return this.frameLoader.messageManager;
       },
       configurable: true,
       enumerable: true,
     });
--- a/devtools/client/responsive.html/browser/tunnel.js
+++ b/devtools/client/responsive.html/browser/tunnel.js
@@ -138,16 +138,17 @@ function tunnelToInnerBrowser(outer, inn
       mmTunnel = new MessageManagerTunnel(outer, inner);
 
       // We are tunneling to an inner browser with a specific remoteness, so it is simpler
       // for the logic of the browser UI to assume this tab has taken on that remoteness,
       // even though it's not true.  Since the actions the browser UI performs are sent
       // down to the inner browser by this tunnel, the tab's remoteness effectively is the
       // remoteness of the inner browser.
       outer.setAttribute("remote", "true");
+      outer.setAttribute("remoteType", inner.remoteType);
 
       // Clear out any cached state that references the current non-remote XBL binding,
       // such as form fill controllers.  Otherwise they will remain in place and leak the
       // outer docshell.
       outer.destroy();
       // The XBL binding for remote browsers uses the message manager for many actions in
       // the UI and that works well here, since it gives us one main thing we need to
       // route to the inner browser (the messages), instead of having to tweak many
@@ -264,16 +265,17 @@ function tunnelToInnerBrowser(outer, inn
       outer.webProgress.removeProgressListener(filteredProgressListener);
 
       // Reset the XBL binding back to the default.
       outer.destroy();
       outer.style.MozBinding = "";
 
       // Reset @remote since this is now back to a regular, non-remote browser
       outer.setAttribute("remote", "false");
+      outer.removeAttribute("remoteType");
 
       // Delete browser window properties exposed on content's owner global
       delete inner.ownerGlobal.PopupNotifications;
       delete inner.ownerGlobal.whereToOpenLink;
 
       // Remove mozbrowser event handlers
       inner.removeEventListener("mozbrowseropenwindow", this);
 
--- a/devtools/client/responsive.html/components/browser.js
+++ b/devtools/client/responsive.html/components/browser.js
@@ -122,27 +122,28 @@ module.exports = createClass({
       {
         ref: "browserContainer",
         className: "browser-container",
 
         /**
          * React uses a whitelist for attributes, so we need some way to set
          * attributes it does not know about, such as @mozbrowser.  If this were
          * the only issue, we could use componentDidMount or ref: node => {} to
-         * set the atttibutes. In the case of @remote, the attribute must be set
-         * before the element is added to the DOM to have any effect, which we
-         * are able to do with this approach.
+         * set the atttibutes. In the case of @remote and @remoteType, the
+         * attribute must be set before the element is added to the DOM to have
+         * any effect, which we are able to do with this approach.
          *
          * @noisolation and @allowfullscreen are needed so that these frames
          * have the same access to browser features as regular browser tabs.
          * The `swapFrameLoaders` platform API we use compares such features
          * before allowing the swap to proceed.
          */
         dangerouslySetInnerHTML: {
-          __html: `<iframe class="browser" mozbrowser="true" remote="true"
+          __html: `<iframe class="browser" mozbrowser="true"
+                           remote="true" remoteType="web"
                            noisolation="true" allowfullscreen="true"
                            src="${location}" width="100%" height="100%">
                    </iframe>`
         }
       }
     );
   },
 
--- a/toolkit/components/viewsource/ViewSourceBrowser.jsm
+++ b/toolkit/components/viewsource/ViewSourceBrowser.jsm
@@ -185,17 +185,17 @@ ViewSourceBrowser.prototype = {
       throw new Error("Must supply a URL when opening view source.");
     }
 
     if (browser) {
       this.browser.relatedBrowser = browser;
 
       // If we're dealing with a remote browser, then the browser
       // for view source needs to be remote as well.
-      this.updateBrowserRemoteness(browser.isRemoteBrowser);
+      this.updateBrowserRemoteness(browser.isRemoteBrowser, browser.remoteType);
     } else if (outerWindowID) {
       throw new Error("Must supply the browser if passing the outerWindowID");
     }
 
     this.sendAsyncMessage("ViewSource:LoadSource",
                           { URL, outerWindowID, lineNumber });
   },
 
@@ -216,19 +216,22 @@ ViewSourceBrowser.prototype = {
    * Updates the "remote" attribute of the view source browser. This
    * will remove the browser from the DOM, and then re-add it in the
    * same place it was taken from.
    *
    * @param shouldBeRemote
    *        True if the browser should be made remote. If the browsers
    *        remoteness already matches this value, this function does
    *        nothing.
+   * @param remoteType
+   *        The type of remote browser process.
    */
-  updateBrowserRemoteness(shouldBeRemote) {
-    if (this.browser.isRemoteBrowser != shouldBeRemote) {
+  updateBrowserRemoteness(shouldBeRemote, remoteType) {
+    if (this.browser.isRemoteBrowser != shouldBeRemote ||
+        this.browser.remoteType != remoteType) {
       // In this base case, where we are handed a <browser> someone else is
       // managing, we don't know for sure that it's safe to toggle remoteness.
       // For view source in a window, this is overridden to actually do the
       // flip if needed.
       throw new Error("View source browser's remoteness mismatch");
     }
   },
 
--- a/toolkit/components/viewsource/content/viewSource.js
+++ b/toolkit/components/viewsource/content/viewSource.js
@@ -661,37 +661,42 @@ ViewSourceChrome.prototype = {
    * Updates the "remote" attribute of the view source browser. This
    * will remove the browser from the DOM, and then re-add it in the
    * same place it was taken from.
    *
    * @param shouldBeRemote
    *        True if the browser should be made remote. If the browsers
    *        remoteness already matches this value, this function does
    *        nothing.
+   * @param remoteType
+   *        The type of remote browser process.
    */
-  updateBrowserRemoteness(shouldBeRemote) {
-    if (this.browser.isRemoteBrowser == shouldBeRemote) {
+  updateBrowserRemoteness(shouldBeRemote, remoteType) {
+    if (this.browser.isRemoteBrowser == shouldBeRemote &&
+        this.browser.remoteType == remoteType) {
       return;
     }
 
     let parentNode = this.browser.parentNode;
     let nextSibling = this.browser.nextSibling;
 
     // XX Removing and re-adding the browser from and to the DOM strips its
     // XBL properties. Save and restore relatedBrowser. Note that when we
     // restore relatedBrowser, there won't yet be a binding or setter. This
     // works in conjunction with the hack in <xul:browser>'s constructor to
     // re-get the weak reference to it.
     let relatedBrowser = this.browser.relatedBrowser;
 
     this.browser.remove();
     if (shouldBeRemote) {
       this.browser.setAttribute("remote", "true");
+      this.browser.setAttribute("remoteType", remoteType);
     } else {
       this.browser.removeAttribute("remote");
+      this.browser.removeAttribute("remoteType");
     }
 
     this.browser.relatedBrowser = relatedBrowser;
 
     // If nextSibling was null, this will put the browser at
     // the end of the list.
     parentNode.insertBefore(this.browser, nextSibling);
 
--- a/toolkit/content/widgets/browser.xml
+++ b/toolkit/content/widgets/browser.xml
@@ -333,16 +333,35 @@
           ]]>
         </getter>
       </property>
 
       <property name="isRemoteBrowser"
                 onget="return (this.getAttribute('remote') == 'true');"
                 readonly="true"/>
 
+      <property name="remoteType"
+                readonly="true">
+        <getter>
+          <![CDATA[
+            if (!this.isRemoteBrowser) {
+              return null;
+            }
+
+            let remoteType = this.getAttribute("remoteType");
+            if (remoteType) {
+              return remoteType;
+            }
+
+            let E10SUtils = Components.utils.import("resource://gre/modules/E10SUtils.jsm", {}).E10SUtils;
+            return E10SUtils.DEFAULT_REMOTE_TYPE;
+          ]]>
+        </getter>
+      </property>
+
       <property name="messageManager"
                 readonly="true">
         <getter>
           <![CDATA[
             var owner = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner);
             if (!owner.frameLoader) {
               return null;
             }