Bug 999239: Copy session history when recreating browser element for the remote -> non-remote transition. r=bz, r=felipe, sr=gavin
authorDave Townsend <dtownsend@oxymoronical.com>
Thu, 25 Sep 2014 11:35:45 -0700
changeset 207247 bd3fa77c80f0e7544414550cda725dd352689c09
parent 207246 05645d479d896500a553c29a011f1921525722ae
child 207248 2a4cc563fffc495827964b0068a6b02fefb4f3e5
child 207407 1a8a722c3dfcadf996f08cdd0b36dccc15437667
push idunknown
push userunknown
push dateunknown
reviewersbz, felipe, gavin
bugs999239
milestone35.0a1
Bug 999239: Copy session history when recreating browser element for the remote -> non-remote transition. r=bz, r=felipe, sr=gavin
browser/base/content/browser.js
browser/base/content/content.js
browser/base/content/tabbrowser.xml
browser/base/content/test/general/browser.ini
browser/base/content/test/general/browser_bug880101.js
browser/base/content/test/general/browser_e10s_switchbrowser.js
browser/base/content/test/general/head.js
browser/modules/E10SUtils.jsm
browser/modules/TabCrashReporter.jsm
browser/modules/moz.build
docshell/base/nsDocShell.cpp
dom/ipc/TabChild.cpp
embedding/browser/nsIWebBrowserChrome3.idl
xpfe/appshell/nsContentTreeOwner.cpp
xpfe/appshell/nsIXULBrowserWindow.idl
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -8,16 +8,18 @@ let Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/NotificationDB.jsm");
 Cu.import("resource:///modules/RecentWindow.jsm");
 Cu.import("resource://gre/modules/WindowsPrefSync.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry",
                                   "resource:///modules/BrowserUITelemetry.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "E10SUtils",
+                                  "resource:///modules/E10SUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
                                   "resource://gre/modules/BrowserUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu",
                                   "resource://gre/modules/CharsetMenu.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils",
                                   "resource://gre/modules/ShortcutUtils.jsm");
@@ -161,16 +163,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   "resource:///modules/translation/Translation.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "SitePermissions",
   "resource:///modules/SitePermissions.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
   "resource:///modules/sessionstore/SessionStore.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "TabState",
+  "resource:///modules/sessionstore/TabState.jsm");
+
 XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
   "resource://gre/modules/FxAccounts.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "gWebRTCUI",
   "resource:///modules/webrtcUI.jsm", "webrtcUI");
 
 #ifdef MOZ_CRASHREPORTER
 XPCOMUtils.defineLazyModuleGetter(this, "TabCrashReporter",
@@ -761,16 +766,46 @@ function gKeywordURIFixup({ target: brow
                                          notificationBox.PRIORITY_INFO_HIGH,
                                          buttons);
     notification.persistence = 1;
   };
 
   gDNSService.asyncResolve(hostName, 0, onLookupComplete, Services.tm.mainThread);
 }
 
+// Called when a docshell has attempted to load a page in an incorrect process.
+// This function is responsible for loading the page in the correct process.
+function RedirectLoad({ target: browser, data }) {
+  let tab = gBrowser._getTabForBrowser(browser);
+  // Flush the tab state before getting it
+  TabState.flush(browser);
+  let tabState = JSON.parse(SessionStore.getTabState(tab));
+
+  if (data.historyIndex < 0) {
+    // Add a pseudo-history state for the new url to load
+    let newEntry = {
+      url: data.uri,
+      referrer: data.referrer,
+    };
+
+    tabState.entries = tabState.entries.slice(0, tabState.index);
+    tabState.entries.push(newEntry);
+    tabState.index++;
+    tabState.userTypedValue = null;
+  }
+  else {
+    // Update the history state to point to the requested index
+    tabState.index = data.historyIndex + 1;
+  }
+
+  // SessionStore takes care of setting the browser remoteness before restoring
+  // history into it.
+  SessionStore.setTabState(tab, JSON.stringify(tabState));
+}
+
 var gBrowserInit = {
   delayedStartupFinished: false,
 
   onLoad: function() {
     var mustLoadSidebar = false;
 
     gBrowser.addEventListener("DOMUpdatePageReport", gPopupBlockerObserver, false);
 
@@ -1059,16 +1094,17 @@ var gBrowserInit = {
 
     Services.obs.addObserver(gSessionHistoryObserver, "browser:purge-session-history", false);
     Services.obs.addObserver(gXPInstallObserver, "addon-install-disabled", false);
     Services.obs.addObserver(gXPInstallObserver, "addon-install-started", false);
     Services.obs.addObserver(gXPInstallObserver, "addon-install-blocked", false);
     Services.obs.addObserver(gXPInstallObserver, "addon-install-failed", false);
     Services.obs.addObserver(gXPInstallObserver, "addon-install-complete", false);
     window.messageManager.addMessageListener("Browser:URIFixup", gKeywordURIFixup);
+    window.messageManager.addMessageListener("Browser:LoadURI", RedirectLoad);
 
     BrowserOffline.init();
     OfflineApps.init();
     IndexedDBPromptHelper.init();
     gRemoteTabsUI.init();
 
     // Initialize the full zoom setting.
     // We do this before the session restore service gets initialized so we can
@@ -1367,16 +1403,17 @@ var gBrowserInit = {
 
       Services.obs.removeObserver(gSessionHistoryObserver, "browser:purge-session-history");
       Services.obs.removeObserver(gXPInstallObserver, "addon-install-disabled");
       Services.obs.removeObserver(gXPInstallObserver, "addon-install-started");
       Services.obs.removeObserver(gXPInstallObserver, "addon-install-blocked");
       Services.obs.removeObserver(gXPInstallObserver, "addon-install-failed");
       Services.obs.removeObserver(gXPInstallObserver, "addon-install-complete");
       window.messageManager.removeMessageListener("Browser:URIFixup", gKeywordURIFixup);
+      window.messageManager.removeMessageListener("Browser:LoadURI", RedirectLoad);
 
       try {
         gPrefService.removeObserver(gHomeButton.prefDomain, gHomeButton);
       } catch (ex) {
         Cu.reportError(ex);
       }
 
       if (typeof WindowsPrefSync !== 'undefined') {
@@ -3546,16 +3583,38 @@ var XULBrowserWindow = {
 
   // Called before links are navigated to to allow us to retarget them if needed.
   onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) {
     let target = BrowserUtils.onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab);
     SocialUI.closeSocialPanelForLinkTraversal(target, linkNode);
     return target;
   },
 
+  // Check whether this URI should load in the current process
+  shouldLoadURI: function(aDocShell, aURI, aReferrer) {
+    if (!gMultiProcessBrowser)
+      return true;
+
+    let browser = aDocShell.QueryInterface(Ci.nsIDocShellTreeItem)
+                           .sameTypeRootTreeItem
+                           .QueryInterface(Ci.nsIDocShell)
+                           .chromeEventHandler;
+
+    // Ignore loads that aren't in the main tabbrowser
+    if (browser.localName != "browser" || browser.getTabBrowser() != gBrowser)
+      return true;
+
+    if (!E10SUtils.shouldLoadURI(aDocShell, aURI, aReferrer)) {
+      E10SUtils.redirectLoad(aDocShell, aURI, aReferrer);
+      return false;
+    }
+
+    return true;
+  },
+
   onProgressChange: function (aWebProgress, aRequest,
                               aCurSelfProgress, aMaxSelfProgress,
                               aCurTotalProgress, aMaxTotalProgress) {
     // Do nothing.
   },
 
   onProgressChange64: function (aWebProgress, aRequest,
                                 aCurSelfProgress, aMaxSelfProgress,
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -4,16 +4,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 let {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource:///modules/ContentWebRTC.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "E10SUtils",
+  "resource:///modules/E10SUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
   "resource://gre/modules/BrowserUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ContentLinkHandler",
   "resource:///modules/ContentLinkHandler.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent",
   "resource://gre/modules/LoginManagerContent.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils",
   "resource://gre/modules/InsecurePasswordUtils.jsm");
@@ -678,16 +680,26 @@ addEventListener("unload", () => {
 addMessageListener("Browser:AppTab", function(message) {
   docShell.isAppTab = message.data.isAppTab;
 });
 
 let WebBrowserChrome = {
   onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) {
     return BrowserUtils.onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab);
   },
+
+  // Check whether this URI should load in the current process
+  shouldLoadURI: function(aDocShell, aURI, aReferrer) {
+    if (!E10SUtils.shouldLoadURI(aDocShell, aURI, aReferrer)) {
+      E10SUtils.redirectLoad(aDocShell, aURI, aReferrer);
+      return false;
+    }
+
+    return true;
+  },
 };
 
 if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
   let tabchild = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                          .getInterface(Ci.nsITabChild);
   tabchild.webBrowserChrome = WebBrowserChrome;
 }
 
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -4,21 +4,16 @@
    - 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/. -->
 
 <!DOCTYPE bindings [
 <!ENTITY % tabBrowserDTD SYSTEM "chrome://browser/locale/tabbrowser.dtd" >
 %tabBrowserDTD;
 ]>
 
-# MAKE_E10S_WORK surrounds code needed to have the front-end try to be smart
-# about using non-remote browsers for loading certain URIs when remote tabs
-# (browser.tabs.remote) are enabled.
-#define MAKE_E10S_WORK 1
-
 <bindings id="tabBrowserBindings"
           xmlns="http://www.mozilla.org/xbl"
           xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
           xmlns:xbl="http://www.mozilla.org/xbl">
 
   <binding id="tabbrowser">
     <resources>
       <stylesheet src="chrome://browser/content/tabbrowser.css"/>
@@ -1469,59 +1464,28 @@
             if (wasActive)
               aBrowser.focus();
 
             return true;
           ]]>
         </body>
       </method>
 
-#ifdef MAKE_E10S_WORK
       <method name="updateBrowserRemotenessByURL">
         <parameter name="aBrowser"/>
         <parameter name="aURL"/>
         <body>
           <![CDATA[
-            let shouldBeRemote = this._shouldBrowserBeRemote(aURL);
+            let shouldBeRemote = gMultiProcessBrowser &&
+                                 E10SUtils.shouldBrowserBeRemote(aURL);
             return this.updateBrowserRemoteness(aBrowser, shouldBeRemote);
           ]]>
         </body>
       </method>
 
-      <!--
-        Returns true if we want to load the content for this URL in a
-        remote process. Eventually this should just check whether aURL
-        is unprivileged. Right now, though, we would like to load
-        some unprivileged URLs (like about:neterror) in the main
-        process since they interact with chrome code through
-        BrowserOnClick.
-      -->
-      <method name="_shouldBrowserBeRemote">
-        <parameter name="aURL"/>
-        <body>
-          <![CDATA[
-            if (!gMultiProcessBrowser)
-              return false;
-
-            // loadURI in browser.xml treats null as about:blank
-            if (!aURL)
-              aURL = "about:blank";
-
-            if (aURL.startsWith("about:") &&
-                aURL.toLowerCase() != "about:home" &&
-                aURL.toLowerCase() != "about:blank") {
-              return false;
-            }
-
-            return true;
-          ]]>
-        </body>
-      </method>
-#endif
-
       <method name="addTab">
         <parameter name="aURI"/>
         <parameter name="aReferrerURI"/>
         <parameter name="aCharset"/>
         <parameter name="aPostData"/>
         <parameter name="aOwner"/>
         <parameter name="aAllowThirdPartyFixup"/>
         <body>
@@ -1553,21 +1517,17 @@
             var t = document.createElementNS(NS_XUL, "tab");
 
             var uriIsAboutBlank = !aURI || aURI == "about:blank";
 
             t.setAttribute("crop", "end");
             t.setAttribute("onerror", "this.removeAttribute('image');");
             t.className = "tabbrowser-tab";
 
-#ifdef MAKE_E10S_WORK
-            let remote = this._shouldBrowserBeRemote(aURI);
-#else
-            let remote = gMultiProcessBrowser;
-#endif
+            let remote = gMultiProcessBrowser && E10SUtils.shouldBrowserBeRemote(aURI);
             if (remote)
               t.setAttribute("remote", "true");
 
             this.tabContainer._unlockTabSizing();
 
             // When overflowing, new tabs are scrolled into view smoothly, which
             // doesn't go well together with the width transition. So we skip the
             // transition in that case.
@@ -2785,53 +2745,31 @@
 
       <!-- throws exception for unknown schemes -->
       <method name="loadURI">
         <parameter name="aURI"/>
         <parameter name="aReferrerURI"/>
         <parameter name="aCharset"/>
         <body>
           <![CDATA[
-#ifdef MAKE_E10S_WORK
-            this.updateBrowserRemotenessByURL(this.mCurrentBrowser, aURI);
-            try {
-#endif
             return this.mCurrentBrowser.loadURI(aURI, aReferrerURI, aCharset);
-#ifdef MAKE_E10S_WORK
-            } catch (e) {
-              let url = this.mCurrentBrowser.currentURI.spec;
-              this.updateBrowserRemotenessByURL(this.mCurrentBrowser, url);
-              throw e;
-            }
-#endif
           ]]>
         </body>
       </method>
 
       <!-- throws exception for unknown schemes -->
       <method name="loadURIWithFlags">
         <parameter name="aURI"/>
         <parameter name="aFlags"/>
         <parameter name="aReferrerURI"/>
         <parameter name="aCharset"/>
         <parameter name="aPostData"/>
         <body>
           <![CDATA[
-#ifdef MAKE_E10S_WORK
-            this.updateBrowserRemotenessByURL(this.mCurrentBrowser, aURI);
-            try {
-#endif
             return this.mCurrentBrowser.loadURIWithFlags(aURI, aFlags, aReferrerURI, aCharset, aPostData);
-#ifdef MAKE_E10S_WORK
-            } catch (e) {
-              let url = this.mCurrentBrowser.currentURI.spec;
-              this.updateBrowserRemotenessByURL(this.mCurrentBrowser, url);
-              throw e;
-            }
-#endif
           ]]>
         </body>
       </method>
 
       <method name="goHome">
         <body>
           <![CDATA[
             return this.mCurrentBrowser.goHome();
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -478,9 +478,11 @@ skip-if = e10s # Bug 516755 - SessionSto
 [browser_bug1003461-switchtab-override.js]
 skip-if = e10s
 [browser_bug1024133-switchtab-override-keynav.js]
 skip-if = e10s
 [browser_bug1025195_switchToTabHavingURI_ignoreFragment.js]
 [browser_addCertException.js]
 skip-if = e10s # Bug ?????? - test directly manipulates content (content.document.getElementById)
 [browser_bug1045809.js]
-skip-if = e10s
+skip-if = e10s # Bug 1068360 - [e10s] Mixed content blocker doorhanger doesn't work
+[browser_e10s_switchbrowser.js]
+
--- a/browser/base/content/test/general/browser_bug880101.js
+++ b/browser/base/content/test/general/browser_bug880101.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-const URL = "about:robots";
+const URL = "http://example.com/browser/browser/base/content/test/general/dummy_page.html";
 
 function test() {
   let win;
 
   let listener = {
     onLocationChange: (webProgress, request, uri, flags) => {
       ok(webProgress.isTopLevel, "Received onLocationChange from top frame");
       is(uri.spec, URL, "Received onLocationChange for correct URL");
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_e10s_switchbrowser.js
@@ -0,0 +1,104 @@
+const DUMMY_PATH = "browser/browser/base/content/test/general/dummy_page.html";
+
+const gExpectedHistory = {
+  index: -1,
+  entries: []
+};
+
+function check_history() {
+  let webNav = gBrowser.webNavigation;
+  let sessionHistory = webNav.sessionHistory;
+
+  let count = sessionHistory.count;
+  is(count, gExpectedHistory.entries.length, "Should have the right number of history entries");
+  is(sessionHistory.index, gExpectedHistory.index, "Should have the right history index");
+
+  for (let i = 0; i < count; i++) {
+    let entry = sessionHistory.getEntryAtIndex(i, false);
+    is(entry.URI.spec, gExpectedHistory.entries[i].uri, "Should have the right URI");
+    is(entry.title, gExpectedHistory.entries[i].title, "Should have the right title");
+  }
+}
+
+// Waits for a load and updates the known history
+let waitForLoad = Task.async(function*(uri) {
+  info("Loading " + uri);
+  gBrowser.loadURI(uri);
+
+  yield waitForDocLoadComplete();
+  gExpectedHistory.index++;
+  gExpectedHistory.entries.push({
+    uri: gBrowser.currentURI.spec,
+    title: gBrowser.contentTitle
+  });
+});
+
+let back = Task.async(function*() {
+  info("Going back");
+  gBrowser.goBack();
+  yield waitForDocLoadComplete();
+  gExpectedHistory.index--;
+});
+
+let forward = Task.async(function*() {
+  info("Going forward");
+  gBrowser.goForward();
+  yield waitForDocLoadComplete();
+  gExpectedHistory.index++;
+});
+
+// Tests that navigating from a page that should be in the remote process and
+// a page that should be in the main process works and retains history
+add_task(function*() {
+  SimpleTest.requestCompleteLog();
+
+  let remoting = Services.prefs.getBoolPref("browser.tabs.remote.autostart");
+  let expectedRemote = remoting ? "true" : "";
+
+  info("1");
+  // Create a tab and load a remote page in it
+  gBrowser.selectedTab = gBrowser.addTab("about:blank", {skipAnimation: true});
+  yield waitForLoad("http://example.org/" + DUMMY_PATH);
+  is(gBrowser.selectedTab.getAttribute("remote"), expectedRemote, "Remote attribute should be correct");
+
+  info("2");
+  // Load another page
+  yield waitForLoad("http://example.com/" + DUMMY_PATH);
+  is(gBrowser.selectedTab.getAttribute("remote"), expectedRemote, "Remote attribute should be correct");
+  check_history();
+
+  info("3");
+  // Load a non-remote page
+  yield waitForLoad("about:robots");
+  is(gBrowser.selectedTab.getAttribute("remote"), "", "Remote attribute should be correct");
+  check_history();
+
+  info("4");
+  // Load a remote page
+  yield waitForLoad("http://example.org/" + DUMMY_PATH);
+  is(gBrowser.selectedTab.getAttribute("remote"), expectedRemote, "Remote attribute should be correct");
+  check_history();
+
+  info("5");
+  yield back();
+  is(gBrowser.selectedTab.getAttribute("remote"), "", "Remote attribute should be correct");
+  check_history();
+
+  info("6");
+  yield back();
+  is(gBrowser.selectedTab.getAttribute("remote"), expectedRemote, "Remote attribute should be correct");
+  check_history();
+
+  info("7");
+  yield forward();
+  is(gBrowser.selectedTab.getAttribute("remote"), "", "Remote attribute should be correct");
+  check_history();
+
+  info("8");
+  yield forward();
+  is(gBrowser.selectedTab.getAttribute("remote"), expectedRemote, "Remote attribute should be correct");
+  check_history();
+
+  info("9");
+  gBrowser.removeCurrentTab();
+});
--- a/browser/base/content/test/general/head.js
+++ b/browser/base/content/test/general/head.js
@@ -449,22 +449,25 @@ function waitForDocLoadAndStopIt(aExpect
  * Waits for the next load to complete in the current browser.
  *
  * @return promise
  */
 function waitForDocLoadComplete(aBrowser=gBrowser) {
   let deferred = Promise.defer();
   let progressListener = {
     onStateChange: function (webProgress, req, flags, status) {
-      let docStart = Ci.nsIWebProgressListener.STATE_IS_NETWORK |
-                     Ci.nsIWebProgressListener.STATE_STOP;
-      info("Saw state " + flags.toString(16));
-      if ((flags & docStart) == docStart) {
+      let docStop = Ci.nsIWebProgressListener.STATE_IS_NETWORK |
+                    Ci.nsIWebProgressListener.STATE_STOP;
+      info("Saw state " + flags.toString(16) + " and status " + status.toString(16));
+
+      // When a load needs to be retargetted to a new process it is cancelled
+      // with NS_BINDING_ABORTED so ignore that case
+      if ((flags & docStop) == docStop && status != Cr.NS_BINDING_ABORTED) {
         aBrowser.removeProgressListener(progressListener);
-        info("Browser loaded");
+        info("Browser loaded " + aBrowser.contentWindow.location);
         deferred.resolve();
       }
     },
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
                                            Ci.nsISupportsWeakReference])
   };
   aBrowser.addProgressListener(progressListener);
   info("Waiting for browser load");
new file mode 100644
--- /dev/null
+++ b/browser/modules/E10SUtils.jsm
@@ -0,0 +1,59 @@
+/* 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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["E10SUtils"];
+
+const {interfaces: Ci, utils: Cu, classes: Cc} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+this.E10SUtils = {
+  shouldBrowserBeRemote: function(aURL) {
+    // loadURI in browser.xml treats null as about:blank
+    if (!aURL)
+      aURL = "about:blank";
+
+    if (aURL.startsWith("about:") &&
+        aURL.toLowerCase() != "about:home" &&
+        aURL.toLowerCase() != "about:blank" &&
+        !aURL.toLowerCase().startsWith("about:neterror")) {
+      return false;
+    }
+
+    return true;
+  },
+
+  shouldLoadURI: function(aDocShell, aURI, aReferrer) {
+    // about:blank is the initial document and can load anywhere
+    if (aURI.spec == "about:blank")
+      return true;
+
+    // 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
+    let isRemote = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
+    if (this.shouldBrowserBeRemote(aURI.spec) == isRemote)
+      return true;
+
+    return false;
+  },
+
+  redirectLoad: function(aDocShell, aURI, aReferrer) {
+    // Retarget the load to the correct process
+    let messageManager = aDocShell.QueryInterface(Ci.nsIInterfaceRequestor)
+                                  .getInterface(Ci.nsIContentFrameMessageManager);
+    let sessionHistory = aDocShell.getInterface(Ci.nsIWebNavigation).sessionHistory;
+
+    messageManager.sendAsyncMessage("Browser:LoadURI", {
+      uri: aURI.spec,
+      referrer: aReferrer ? aReferrer.spec : null,
+      historyIndex: sessionHistory.requestedIndex,
+    });
+    return false;
+  },
+};
--- a/browser/modules/TabCrashReporter.jsm
+++ b/browser/modules/TabCrashReporter.jsm
@@ -91,17 +91,16 @@ this.TabCrashReporter = {
     if (browser.isRemoteBrowser)
       return;
 
     let doc = browser.contentDocument;
     if (!doc.documentURI.startsWith("about:tabcrashed"))
       return;
 
     let url = browser.currentURI.spec;
-    browser.getTabBrowser().updateBrowserRemotenessByURL(browser, url);
     browser.loadURIWithFlags(url, Ci.nsIWebNavigation.LOAD_FLAGS_NONE, null, null, null);
   },
 
   onAboutTabCrashedLoad: function (aBrowser) {
     if (!this.childMap)
       return;
 
     let dumpID = this.childMap.get(this.browserMap.get(aBrowser));
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -12,16 +12,17 @@ EXTRA_JS_MODULES += [
     'BrowserNewTabPreloader.jsm',
     'BrowserUITelemetry.jsm',
     'Chat.jsm',
     'ContentClick.jsm',
     'ContentLinkHandler.jsm',
     'ContentSearch.jsm',
     'ContentWebRTC.jsm',
     'CustomizationTabPreloader.jsm',
+    'E10SUtils.jsm',
     'Feeds.jsm',
     'FormSubmitObserver.jsm',
     'FormValidationHandler.jsm',
     'NetworkPrioritizer.jsm',
     'offlineAppCache.jsm',
     'PanelFrame.jsm',
     'RemotePrompt.jsm',
     'SharedFrame.jsm',
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -9833,17 +9833,28 @@ nsDocShell::InternalLoad(nsIURI * aURI,
 
             // Inform the favicon service that the favicon for oldURI also
             // applies to aURI.
             CopyFavicon(currentURI, aURI, mInPrivateBrowsing);
 
             return NS_OK;
         }
     }
-    
+
+    // Check if the webbrowser chrome wants the load to proceed; this can be
+    // used to cancel attempts to load URIs in the wrong process.
+    nsCOMPtr<nsIWebBrowserChrome3> browserChrome3 = do_GetInterface(mTreeOwner);
+    if (browserChrome3) {
+        bool shouldLoad;
+        rv = browserChrome3->ShouldLoadURI(this, aURI, aReferrer, &shouldLoad);
+        if (NS_SUCCEEDED(rv) && !shouldLoad) {
+            return NS_OK;
+        }
+    }
+
     // mContentViewer->PermitUnload can destroy |this| docShell, which
     // causes the next call of CanSavePresentation to crash. 
     // Hold onto |this| until we return, to prevent a crash from happening. 
     // (bug#331040)
     nsCOMPtr<nsIDocShell> kungFuDeathGrip(this);
 
     // Don't init timing for javascript:, since it generally doesn't
     // actually start a load or anything.  If it does, we'll init
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -75,17 +75,16 @@
 #include "nsViewportInfo.h"
 #include "JavaScriptChild.h"
 #include "nsILoadContext.h"
 #include "ipc/nsGUIEventIPC.h"
 #include "mozilla/gfx/Matrix.h"
 #include "UnitTransforms.h"
 #include "ClientLayerManager.h"
 #include "LayersLogging.h"
-#include "nsIWebBrowserChrome3.h"
 
 #include "nsColorPickerProxy.h"
 
 #define BROWSER_ELEMENT_CHILD_SCRIPT \
     NS_LITERAL_STRING("chrome://global/content/BrowserElementChild.js")
 
 #define TABC_LOG(...)
 // #define TABC_LOG(...) printf_stderr("TABC: " __VA_ARGS__)
--- a/embedding/browser/nsIWebBrowserChrome3.idl
+++ b/embedding/browser/nsIWebBrowserChrome3.idl
@@ -1,20 +1,23 @@
 /* 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/. */
 
 #include "nsIWebBrowserChrome2.idl"
 #include "nsIURI.idl"
 #include "nsIDOMNode.idl"
 
+interface nsIDocShell;
+interface nsIInputStream;
+
 /**
  * nsIWebBrowserChrome3 is an extension to nsIWebBrowserChrome2.
  */
-[scriptable, uuid(7f2aa813-b250-4e46-afeb-97b1e91bc9a5)]
+[scriptable, uuid(9e6c2372-5d9d-4ce8-ab9e-c5df1494dc84)]
 interface nsIWebBrowserChrome3 : nsIWebBrowserChrome2
 {
   /**
    * Determines the appropriate target for a link.
    *
    * @param originalTarget
    *        The original link target.
    * @param linkURI
@@ -25,9 +28,23 @@ interface nsIWebBrowserChrome3 : nsIWebB
    *        Whether or not the link is in an app tab.
    * @returns A new link target, if appropriate.
    *          Otherwise returns originalTarget.
    */
   AString onBeforeLinkTraversal(in AString originalTarget,
                                 in nsIURI linkURI,
                                 in nsIDOMNode linkNode,
                                 in boolean isAppTab);
+
+  /**
+   * Determines whether a load should continue.
+   *
+   * @param aDocShell
+   *        The docshell performing the load.
+   * @param aURI
+   *        The URI being loaded.
+   * @param aReferrer
+   *        The referrer of the load.
+   */
+  bool shouldLoadURI(in nsIDocShell    aDocShell,
+                     in nsIURI         aURI,
+                     in nsIURI         aReferrer);
 };
--- a/xpfe/appshell/nsContentTreeOwner.cpp
+++ b/xpfe/appshell/nsContentTreeOwner.cpp
@@ -440,16 +440,33 @@ NS_IMETHODIMP nsContentTreeOwner::OnBefo
   if (xulBrowserWindow)
     return xulBrowserWindow->OnBeforeLinkTraversal(originalTarget, linkURI,
                                                    linkNode, isAppTab, _retval);
   
   _retval = originalTarget;
   return NS_OK;
 }
 
+NS_IMETHODIMP nsContentTreeOwner::ShouldLoadURI(nsIDocShell *aDocShell,
+                                                nsIURI *aURI,
+                                                nsIURI *aReferrer,
+                                                bool *_retval)
+{
+  NS_ENSURE_STATE(mXULWindow);
+
+  nsCOMPtr<nsIXULBrowserWindow> xulBrowserWindow;
+  mXULWindow->GetXULBrowserWindow(getter_AddRefs(xulBrowserWindow));
+
+  if (xulBrowserWindow)
+    return xulBrowserWindow->ShouldLoadURI(aDocShell, aURI, aReferrer, _retval);
+
+  *_retval = true;
+  return NS_OK;
+}
+
 //*****************************************************************************
 // nsContentTreeOwner::nsIWebBrowserChrome2
 //*****************************************************************************   
 
 NS_IMETHODIMP nsContentTreeOwner::SetStatusWithContext(uint32_t aStatusType,
                                                        const nsAString &aStatusText,
                                                        nsISupports *aStatusContext)
 {
--- a/xpfe/appshell/nsIXULBrowserWindow.idl
+++ b/xpfe/appshell/nsIXULBrowserWindow.idl
@@ -5,23 +5,25 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 #include "nsIURI.idl"
 #include "nsIDOMNode.idl"
 
 interface nsIRequest;
 interface nsIDOMElement;
+interface nsIInputStream;
+interface nsIDocShell;
 
 /**
  * The nsIXULBrowserWindow supplies the methods that may be called from the
  * internals of the browser area to tell the containing xul window to update
  * its ui. 
  */
-[scriptable, uuid(e4ee85a0-645d-11e3-949a-0800200c9a66)]
+[scriptable, uuid(162d3378-a7d5-410c-8635-fe80e32020fc)]
 interface nsIXULBrowserWindow : nsISupports
 {
   /**
    * Sets the status according to JS' version of status.
    */
   void setJSStatus(in AString status);
 
   /**
@@ -34,14 +36,27 @@ interface nsIXULBrowserWindow : nsISuppo
    * Determines the appropriate target for a link.
    */
   AString onBeforeLinkTraversal(in AString originalTarget,
                                 in nsIURI linkURI,
                                 in nsIDOMNode linkNode,
                                 in boolean isAppTab);
 
   /**
+   * Determines whether a load should continue.
+   *
+   * @param aDocShell
+   *        The docshell performing the load.
+   * @param aURI
+   *        The URI being loaded.
+   * @param aReferrer
+   *        The referrer of the load.
+   */
+  bool shouldLoadURI(in nsIDocShell    aDocShell,
+                     in nsIURI         aURI,
+                     in nsIURI         aReferrer);
+  /**
    * Show/hide a tooltip (when the user mouses over a link, say).
    */
   void showTooltip(in long x, in long y, in AString tooltip);
   void hideTooltip();
 };