Bug 999239: Copy session history when recreating browser element for the remote -> non-remote transition. r=bz, r=felipe, sr=gavin

This commit is contained in:
Dave Townsend 2014-09-25 11:35:45 -07:00
Родитель e132fb9f69
Коммит 8b46b5daab
15 изменённых файлов: 313 добавлений и 77 удалений

Просмотреть файл

@ -13,6 +13,8 @@ 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",
@ -166,6 +168,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "SitePermissions",
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");
@ -766,6 +771,36 @@ function gKeywordURIFixup({ target: browser, data: fixupInfo }) {
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,
@ -1064,6 +1099,7 @@ var gBrowserInit = {
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();
@ -1372,6 +1408,7 @@ var gBrowserInit = {
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);
@ -3551,6 +3588,28 @@ var XULBrowserWindow = {
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) {

Просмотреть файл

@ -9,6 +9,8 @@ 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",
@ -683,6 +685,16 @@ 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) {

Просмотреть файл

@ -9,11 +9,6 @@
%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"
@ -1474,49 +1469,18 @@
</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"/>
@ -1558,11 +1522,7 @@
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");
@ -2790,18 +2750,7 @@
<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>
@ -2815,18 +2764,7 @@
<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>

Просмотреть файл

@ -483,4 +483,6 @@ skip-if = e10s
[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]

Просмотреть файл

@ -1,7 +1,7 @@
/* 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;

Просмотреть файл

@ -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();
});

Просмотреть файл

@ -454,12 +454,15 @@ 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();
}
},

Просмотреть файл

@ -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;
},
};

Просмотреть файл

@ -96,7 +96,6 @@ this.TabCrashReporter = {
return;
let url = browser.currentURI.spec;
browser.getTabBrowser().updateBrowserRemotenessByURL(browser, url);
browser.loadURIWithFlags(url, Ci.nsIWebNavigation.LOAD_FLAGS_NONE, null, null, null);
},

Просмотреть файл

@ -17,6 +17,7 @@ EXTRA_JS_MODULES += [
'ContentSearch.jsm',
'ContentWebRTC.jsm',
'CustomizationTabPreloader.jsm',
'E10SUtils.jsm',
'Feeds.jsm',
'FormSubmitObserver.jsm',
'FormValidationHandler.jsm',

Просмотреть файл

@ -9838,7 +9838,18 @@ nsDocShell::InternalLoad(nsIURI * aURI,
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.

Просмотреть файл

@ -80,7 +80,6 @@
#include "UnitTransforms.h"
#include "ClientLayerManager.h"
#include "LayersLogging.h"
#include "nsIWebBrowserChrome3.h"
#include "nsColorPickerProxy.h"

Просмотреть файл

@ -6,10 +6,13 @@
#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
{
/**
@ -30,4 +33,18 @@ interface nsIWebBrowserChrome3 : nsIWebBrowserChrome2
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);
};

Просмотреть файл

@ -445,6 +445,23 @@ NS_IMETHODIMP nsContentTreeOwner::OnBeforeLinkTraversal(const nsAString &origina
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
//*****************************************************************************

Просмотреть файл

@ -10,13 +10,15 @@
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
{
/**
@ -38,6 +40,19 @@ interface nsIXULBrowserWindow : nsISupports
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).
*/