Bug 1554302 - Restore history when switching process during navigation. r=snorp

History is kept locally on the content process (or main process for main
process pages), so when going from one process to the other we need to restore
history. This will eventually be superseded by moving all history to the main
process, but we don't know when that's going to happen so we need to add this
workaround here. Desktop has the same workaround in place and this patch is
based on that code.

There are two places where we need to restore history:

- App navigates to page directly using `loadURI` or similar: in this case we
  need to pass down the load details to the content process alongside the
  history information so that we can restore and immediatelly navigate to the
  new page. This also avoids an extra history reloading that ordinarely happens
  when restoring history.

- App calls `goBack`, `goForward`, etc: in this case we don't need to reload a
  page but just restore the history and adjust the `historyIndex`. I'm not
  entirely sure why we need to add `1` to the `historyIndex` but that's what
  Desktop does and it seems to work correctly so I just did it.

This patch changes `updateRemoteTypeForURI` to `updateRemoteAndNavigate` which
more closely matches what that method is doing now, this is similar to what
happens on desktop.

This patch also adds a `window.moduleManager` that can be used in Actors to
access the current `moduleManager`. I expect this to go away when we fully
migrate all modules to actors.

Differential Revision: https://phabricator.services.mozilla.com/D62970

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Agi Sferro 2020-02-20 19:07:46 +00:00
Родитель 04971529b6
Коммит 64dab61eb5
8 изменённых файлов: 270 добавлений и 55 удалений

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

@ -0,0 +1,35 @@
/* 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";
var EXPORTED_SYMBOLS = ["BrowserTabParent"];
const { GeckoViewUtils } = ChromeUtils.import(
"resource://gre/modules/GeckoViewUtils.jsm"
);
const { debug, warn } = GeckoViewUtils.initLogging("BrowserTabParent"); // eslint-disable-line no-unused-vars
class BrowserTabParent extends JSWindowActorParent {
async receiveMessage(aMsg) {
debug`receiveMessage: ${aMsg.name} ${aMsg}`;
switch (aMsg.name) {
case "Browser:LoadURI": {
const browser = this.browsingContext.top.embedderElement;
const window = browser.ownerGlobal;
if (!window || !window.moduleManager) {
debug`No window`;
return;
}
const { loadOptions, historyIndex } = aMsg.data;
const { uri } = loadOptions;
const { moduleManager } = window;
moduleManager.updateRemoteAndNavigate(uri, loadOptions, historyIndex);
}
}
}
}

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

@ -0,0 +1,64 @@
/* 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/. */
const { GeckoViewActorChild } = ChromeUtils.import(
"resource://gre/modules/GeckoViewActorChild.jsm"
);
var { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, {
SessionHistory: "resource://gre/modules/sessionstore/SessionHistory.jsm",
PrivacyFilter: "resource://gre/modules/sessionstore/PrivacyFilter.jsm",
});
var EXPORTED_SYMBOLS = ["GeckoViewContentChild"];
class GeckoViewContentChild extends GeckoViewActorChild {
collectSessionState() {
const { docShell, contentWindow } = this;
let history = SessionHistory.collect(docShell);
let formdata = SessionStoreUtils.collectFormData(contentWindow);
let scrolldata = SessionStoreUtils.collectScrollPosition(contentWindow);
// Save the current document resolution.
let zoom = 1;
let domWindowUtils = contentWindow.windowUtils;
zoom = domWindowUtils.getResolution();
scrolldata = scrolldata || {};
scrolldata.zoom = {};
scrolldata.zoom.resolution = zoom;
// Save some data that'll help in adjusting the zoom level
// when restoring in a different screen orientation.
let displaySize = {};
let width = {},
height = {};
domWindowUtils.getContentViewerSize(width, height);
displaySize.width = width.value;
displaySize.height = height.value;
scrolldata.zoom.displaySize = displaySize;
formdata = PrivacyFilter.filterFormData(formdata || {});
return { history, formdata, scrolldata };
}
receiveMessage(message) {
const { name } = message;
switch (name) {
case "CollectSessionState": {
return this.collectSessionState();
}
default:
return null;
}
}
}
const { debug, warn } = GeckoViewContentChild.initLogging("GeckoViewContent"); // eslint-disable-line no-unused-vars

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

@ -6,6 +6,8 @@ with Files('**'):
BUG_COMPONENT = ('GeckoView', 'General')
FINAL_TARGET_FILES.actors += [
'BrowserTabParent.jsm',
'GeckoViewContentChild.jsm',
'LoadURIDelegateChild.jsm',
'WebBrowserChromeChild.jsm',
]

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

@ -23,6 +23,7 @@ const SCROLL_BEHAVIOR_SMOOTH = 0;
const SCROLL_BEHAVIOR_AUTO = 1;
XPCOMUtils.defineLazyModuleGetters(this, {
E10SUtils: "resource://gre/modules/E10SUtils.jsm",
ManifestObtainer: "resource://gre/modules/ManifestObtainer.jsm",
PrivacyFilter: "resource://gre/modules/sessionstore/PrivacyFilter.jsm",
SessionHistory: "resource://gre/modules/sessionstore/SessionHistory.jsm",
@ -210,18 +211,15 @@ class GeckoViewContentChild extends GeckoViewChildModule {
}
case "GeckoView:RestoreState":
this._savedState = aMsg.data;
const { history, formdata, scrolldata, loadOptions } = aMsg.data;
this._savedState = { history, formdata, scrolldata };
if (this._savedState.history) {
let restoredHistory = SessionHistory.restore(
docShell,
this._savedState.history
);
if (history) {
let restoredHistory = SessionHistory.restore(docShell, history);
addEventListener(
"load",
_ => {
const formdata = this._savedState.formdata;
if (formdata) {
this.Utils.restoreFrameTreeData(
content,
@ -243,7 +241,6 @@ class GeckoViewContentChild extends GeckoViewChildModule {
let scrollRestore = _ => {
if (content.location != "about:blank") {
const scrolldata = this._savedState.scrolldata;
if (scrolldata) {
this.Utils.restoreFrameTreeData(
content,
@ -278,7 +275,7 @@ class GeckoViewContentChild extends GeckoViewChildModule {
.getInterface(Ci.nsIWebProgress);
webProgress.addProgressListener(this.progressFilter, this.flags);
restoredHistory.QueryInterface(Ci.nsISHistory).reloadCurrentEntry();
this.loadEntry(loadOptions, restoredHistory);
}
break;
@ -335,6 +332,35 @@ class GeckoViewContentChild extends GeckoViewChildModule {
}
}
loadEntry(loadOptions, history) {
if (!loadOptions) {
history.QueryInterface(Ci.nsISHistory).reloadCurrentEntry();
return;
}
const webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
let {
referrerInfo,
triggeringPrincipal,
uri,
flags,
csp,
headers,
} = loadOptions;
referrerInfo = E10SUtils.deserializeReferrerInfo(referrerInfo);
triggeringPrincipal = E10SUtils.deserializePrincipal(triggeringPrincipal);
csp = E10SUtils.deserializeCSP(csp);
webNavigation.loadURI(uri, {
triggeringPrincipal,
referrerInfo,
loadFlags: flags,
csp,
headers,
});
}
// eslint-disable-next-line complexity
handleEvent(aEvent) {
debug`handleEvent: ${aEvent.type}`;

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

@ -125,24 +125,34 @@ var ModuleManager = {
this._modules.forEach(aCallback, this);
},
updateRemoteTypeForURI(aURI) {
const currentType = this.browser.remoteType || E10SUtils.NOT_REMOTE;
const remoteType = E10SUtils.getRemoteTypeForURI(
getActor(aActorName) {
return this.browser.browsingContext.currentWindowGlobal.getActor(
aActorName
);
},
remoteTypeFor(aURI, currentType) {
return E10SUtils.getRemoteTypeForURI(
aURI,
GeckoViewSettings.useMultiprocess,
/* useRemoteSubframes */ false,
currentType,
this.browser.currentURI
);
},
debug`updateRemoteType: uri=${aURI} currentType=${currentType}
shouldLoadInThisProcess(aURI) {
const currentType = this.browser.remoteType || E10SUtils.NOT_REMOTE;
return currentType === this.remoteTypeFor(aURI, currentType);
},
async updateRemoteAndNavigate(aURI, aLoadOptions, aHistoryIndex = -1) {
const currentType = this.browser.remoteType || E10SUtils.NOT_REMOTE;
const remoteType = this.remoteTypeFor(aURI, currentType);
debug`updateRemoteAndNavigate: uri=${aURI} currentType=${currentType}
remoteType=${remoteType}`;
if (currentType === remoteType) {
// We're already using a child process of the correct type.
return false;
}
if (
remoteType !== E10SUtils.NOT_REMOTE &&
!GeckoViewSettings.useMultiprocess
@ -151,8 +161,29 @@ var ModuleManager = {
return false;
}
// Now we're switching the remoteness (value of "remote" attr).
// Session state like history is maintained at the process level so we need
// to collect it and restore it in the other process when switching.
// TODO: This should go away when we migrate the history to the main
// process Bug 1507287.
const sessionState = await this.getActor("GeckoViewContent").sendQuery(
"CollectSessionState"
);
const { history } = sessionState;
// If the navigation is from history we don't need to load the page again
// so we ignore loadOptions
if (aHistoryIndex >= 0) {
// Make sure the historyIndex is valid
history.index = aHistoryIndex + 1;
history.index = Math.max(
1,
Math.min(history.index, history.entries.length)
);
} else {
sessionState.loadOptions = aLoadOptions;
}
// Now we're switching the remoteness (value of "remote" attr).
let disabledModules = [];
this.forEach(module => {
if (module.enabled) {
@ -197,6 +228,11 @@ var ModuleManager = {
module.enabled = true;
});
this.messageManager.sendAsyncMessage(
"GeckoView:RestoreState",
sessionState
);
this.browser.focus();
return true;
},
@ -544,6 +580,10 @@ function startup() {
},
]);
// TODO: Bug 1569360 Allows actors to temporarely access ModuleManager until
// we migrate everything over to actors.
window.moduleManager = ModuleManager;
Services.tm.dispatchToMainThread(() => {
// This should always be the first thing we do here - any additional delayed
// initialisation tasks should be added between "browser-delayed-startup-finished"

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

@ -22,6 +22,16 @@ XPCOMUtils.defineLazyModuleGetters(this, {
const { debug, warn } = GeckoViewUtils.initLogging("Startup"); // eslint-disable-line no-unused-vars
const ACTORS = {
BrowserTab: {
parent: {
moduleURI: "resource:///actors/BrowserTabParent.jsm",
},
},
GeckoViewContent: {
child: {
moduleURI: "resource:///actors/GeckoViewContentChild.jsm",
},
},
LoadURIDelegate: {
child: {
moduleURI: "resource:///actors/LoadURIDelegateChild.jsm",

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

@ -1343,13 +1343,51 @@ class NavigationDelegateTest : BaseSessionTest() {
@Test
fun processSwitching() {
val settings = sessionRule.runtime.settings
val aboutConfigEnabled = settings.aboutConfigEnabled
settings.aboutConfigEnabled = true
var currentUrl: String? = null
mainSession.delegateUntilTestEnd(object: GeckoSession.NavigationDelegate {
override fun onLocationChange(session: GeckoSession, url: String?) {
currentUrl = url
}
override fun onLoadError(session: GeckoSession, uri: String?, error: WebRequestError): GeckoResult<String>? {
assertThat("Should not get here", false, equalTo(true))
return null
}
})
// This loads in the parent process
mainSession.loadUri("about:config")
sessionRule.waitForPageStop()
// Switching processes involves loading about:blank
sessionRule.waitForPageStops(2)
assertThat("URL should match", currentUrl!!, equalTo("about:config"))
// This will load a page in the child
mainSession.loadTestPath(HELLO_HTML_PATH)
sessionRule.waitForPageStop()
sessionRule.waitForPageStops(2)
assertThat("URL should match", currentUrl!!, endsWith(HELLO_HTML_PATH))
mainSession.loadUri("about:config")
sessionRule.waitForPageStops(2)
assertThat("URL should match", currentUrl!!, equalTo("about:config"))
sessionRule.session.goBack()
sessionRule.waitForPageStops(2)
assertThat("URL should match", currentUrl!!, endsWith(HELLO_HTML_PATH))
sessionRule.session.goBack()
sessionRule.waitForPageStops(2)
assertThat("URL should match", currentUrl!!, equalTo("about:config"))
settings.aboutConfigEnabled = aboutConfigEnabled
}
@Test fun setLocationHash() {

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

@ -15,7 +15,6 @@ const { XPCOMUtils } = ChromeUtils.import(
XPCOMUtils.defineLazyModuleGetters(this, {
E10SUtils: "resource://gre/modules/E10SUtils.jsm",
GeckoViewSettings: "resource://gre/modules/GeckoViewSettings.jsm",
LoadURIDelegate: "resource://gre/modules/LoadURIDelegate.jsm",
Services: "resource://gre/modules/Services.jsm",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
@ -68,7 +67,6 @@ class GeckoViewNavigation extends GeckoViewModule {
"GeckoView:PurgeHistory",
]);
this.messageManager.addMessageListener("Browser:LoadURI", this);
this._initialAboutBlank = true;
debug`sessionContextId=${this.settings.sessionContextId}`;
@ -89,7 +87,7 @@ class GeckoViewNavigation extends GeckoViewModule {
}
// Bundle event handler.
onEvent(aEvent, aData, aCallback) {
async onEvent(aEvent, aData, aCallback) {
debug`onEvent: event=${aEvent}, data=${aData}`;
switch (aEvent) {
@ -136,11 +134,8 @@ class GeckoViewNavigation extends GeckoViewModule {
navFlags |= Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY;
}
if (GeckoViewSettings.useMultiprocess) {
this.moduleManager.updateRemoteTypeForURI(uri);
}
let triggeringPrincipal, referrerInfo, csp;
let parsedUri;
if (referrerSessionId) {
const referrerWindow = Services.ww.getWindowByName(
referrerSessionId,
@ -160,7 +155,7 @@ class GeckoViewNavigation extends GeckoViewModule {
);
} else {
try {
const parsedUri = Services.io.newURI(uri);
parsedUri = Services.io.newURI(uri);
if (
parsedUri.schemeIs("about") ||
parsedUri.schemeIs("data") ||
@ -226,7 +221,8 @@ class GeckoViewNavigation extends GeckoViewModule {
// referring session, the referrerInfo is null.
//
// csp is only present if we have a referring document, null otherwise.
this.browser.loadURI(uri, {
this.loadURI({
uri: parsedUri ? parsedUri.spec : uri,
flags: navFlags,
referrerInfo,
triggeringPrincipal,
@ -251,33 +247,37 @@ class GeckoViewNavigation extends GeckoViewModule {
}
}
// Message manager event handler.
receiveMessage(aMsg) {
debug`receiveMessage: ${aMsg.name}`;
async loadURI({
uri,
flags,
referrerInfo,
triggeringPrincipal,
headers,
csp,
}) {
if (!this.moduleManager.shouldLoadInThisProcess(uri)) {
referrerInfo = E10SUtils.serializeReferrerInfo(referrerInfo);
triggeringPrincipal = E10SUtils.serializePrincipal(triggeringPrincipal);
csp = E10SUtils.serializeCSP(csp);
switch (aMsg.name) {
case "Browser:LoadURI":
// This is triggered by E10SUtils.redirectLoad(), and means
// we may need to change the remoteness of our browser and
// load the URI.
const {
uri,
flags,
referrerInfo,
triggeringPrincipal,
} = aMsg.data.loadOptions;
this.moduleManager.updateRemoteTypeForURI(uri);
this.browser.loadURI(uri, {
flags,
referrerInfo: E10SUtils.deserializeReferrerInfo(referrerInfo),
triggeringPrincipal: E10SUtils.deserializePrincipal(
triggeringPrincipal
),
});
break;
this.moduleManager.updateRemoteAndNavigate(uri, {
referrerInfo,
triggeringPrincipal,
headers,
csp,
flags,
uri,
});
return;
}
this.browser.loadURI(uri, {
flags,
referrerInfo,
triggeringPrincipal,
csp,
headers,
});
}
waitAndSetupWindow(aSessionId, { opener, nextRemoteTabId, forceNotRemote }) {