зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
04971529b6
Коммит
64dab61eb5
|
@ -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 }) {
|
||||
|
|
Загрузка…
Ссылка в новой задаче