Bug 1640019 - Part 2: Use new process switch logic in tabbrowser, r=mattwoodrow,Gijs

Add a series of extra hooks and methods to allow tabbrowser to use the new
process switching codepath. This duplicates some of the logic from
`updateBrowserRemoteness` into event handlers.

Differential Revision: https://phabricator.services.mozilla.com/D78970
This commit is contained in:
Nika Layzell 2020-06-15 23:23:46 +00:00
Родитель 212943c862
Коммит 0e74912c80
8 изменённых файлов: 403 добавлений и 54 удалений

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

@ -2056,8 +2056,14 @@
// doesn't keep the window alive.
b.permanentKey = new (Cu.getGlobalForObject(Services).Object)();
// Ensure that SessionStore has flushed any session history state from the
// content process before we this browser's remoteness.
b.prepareToChangeRemoteness = () =>
SessionStore.prepareToChangeRemoteness(b);
const defaultBrowserAttributes = {
contextmenu: "contentAreaContextMenu",
maychangeremoteness: "true",
message: "true",
messagemanagergroup: "browsers",
selectmenulist: "ContentSelectDropdown",
@ -2702,6 +2708,9 @@
gFissionBrowser,
preferredRemoteType
);
if (sameProcessAsFrameLoader) {
remoteType = sameProcessAsFrameLoader.messageManager.remoteType;
}
// If we open a new tab with the newtab URL in the default
// userContext, check if there is a preloaded browser ready.
@ -5708,6 +5717,138 @@
);
this.tabContainer.addEventListener("mouseover", tabContextFTLInserter);
this.tabContainer.addEventListener("focus", tabContextFTLInserter, true);
// Fired when Gecko has decided a <browser> element will change
// remoteness. This allows persisting some state on this element across
// process switches.
this.addEventListener("WillChangeBrowserRemoteness", event => {
let browser = event.originalTarget;
let tab = this.getTabForBrowser(browser);
if (!tab) {
return;
}
// Dispatch the `BeforeTabRemotenessChange` event, allowing other code
// to react to this tab's process switch.
let evt = document.createEvent("Events");
evt.initEvent("BeforeTabRemotenessChange", true, false);
tab.dispatchEvent(evt);
let wasActive = document.activeElement == browser;
// Unmap the old outerWindowId
this._outerWindowIDBrowserMap.delete(browser.outerWindowID);
// Unhook our progress listener.
let filter = this._tabFilters.get(tab);
let oldListener = this._tabListeners.get(tab);
browser.webProgress.removeProgressListener(filter);
filter.removeProgressListener(oldListener);
let stateFlags = oldListener.mStateFlags;
let requestCount = oldListener.mRequestCount;
// We'll be creating a new listener, so destroy the old one.
oldListener.destroy();
let oldDroppedLinkHandler = browser.droppedLinkHandler;
let oldUserTypedValue = browser.userTypedValue;
let hadStartedLoad = browser.didStartLoadSinceLastUserTyping();
let didChange = didChangeEvent => {
browser.userTypedValue = oldUserTypedValue;
if (hadStartedLoad) {
browser.urlbarChangeTracker.startedLoad();
}
browser.droppedLinkHandler = oldDroppedLinkHandler;
// Switching a browser's remoteness will create a new frameLoader.
// As frameLoaders start out with an active docShell we have to
// deactivate it if this is not the selected tab's browser or the
// browser window is minimized.
browser.docShellIsActive = this.shouldActivateDocShell(browser);
// Create a new tab progress listener for the new browser we just
// injected, since tab progress listeners have logic for handling the
// initial about:blank load
let listener = new TabProgressListener(
tab,
browser,
false,
false,
stateFlags,
requestCount
);
this._tabListeners.set(tab, listener);
filter.addProgressListener(listener, Ci.nsIWebProgress.NOTIFY_ALL);
// Restore the progress listener.
browser.webProgress.addProgressListener(
filter,
Ci.nsIWebProgress.NOTIFY_ALL
);
// Restore the securityUI state.
let securityUI = browser.securityUI;
let state = securityUI
? securityUI.state
: Ci.nsIWebProgressListener.STATE_IS_INSECURE;
this._callProgressListeners(
browser,
"onSecurityChange",
[browser.webProgress, null, state],
true,
false
);
let cbEvent = browser.getContentBlockingEvents();
// Include the true final argument to indicate that this event is
// simulated (instead of being observed by the webProgressListener).
this._callProgressListeners(
browser,
"onContentBlockingEvent",
[browser.webProgress, null, cbEvent, true],
true,
false
);
if (browser.isRemoteBrowser) {
// Switching the browser to be remote will connect to a new child
// process so the browser can no longer be considered to be
// crashed.
tab.removeAttribute("crashed");
} else {
browser.sendMessageToActor(
"Browser:AppTab",
{ isAppTab: tab.pinned },
"BrowserTab"
);
// Register the new outerWindowID.
this._outerWindowIDBrowserMap.set(browser.outerWindowID, browser);
}
if (wasActive) {
browser.focus();
}
if (this.isFindBarInitialized(tab)) {
this.getCachedFindBar(tab).browser = browser;
}
browser.sendMessageToActor(
"Browser:HasSiblings",
this.tabs.length > 1,
"BrowserTab"
);
evt = document.createEvent("Events");
evt.initEvent("TabRemotenessChange", true, false);
tab.dispatchEvent(evt);
};
browser.addEventListener("DidChangeBrowserRemoteness", didChange, {
once: true,
});
});
},
setSuccessor(aTab, successorTab) {
@ -5788,6 +5929,11 @@
E10SUtils.log().debug(`new tabID: ${remoteTab.tabId}`);
return remoteTab.contentProcessId;
},
finishBrowserRemotenessChange(aBrowser, aSwitchId) {
let tab = this.getTabForBrowser(aBrowser);
SessionStore.finishTabRemotenessChange(tab, aSwitchId);
},
};
/**

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

@ -511,6 +511,18 @@ var SessionStore = {
}
}
},
/**
* Prepares to change the remoteness of the given browser, by ensuring that
* the local instance of session history is up-to-date.
*/
async prepareToChangeRemoteness(aTab) {
await SessionStoreInternal.prepareToChangeRemoteness(aTab);
},
finishTabRemotenessChange(aTab, aSwitchId) {
SessionStoreInternal.finishTabRemotenessChange(aTab, aSwitchId);
},
};
// Freeze the SessionStore object. We don't want anyone to modify it.
@ -3870,13 +3882,9 @@ var SessionStoreInternal = {
let permanentKey = tab.linkedBrowser.permanentKey;
let browser = tab.linkedBrowser;
browser.messageManager.sendAsyncMessage(
"SessionStore:prepareForProcessChange"
);
// NOTE: This is currently the only async operation used, but this is likely
// to change in the future.
await TabStateFlusher.flush(browser);
await this.prepareToChangeRemoteness(browser);
// Now that we have flushed state, our loadArguments, etc. may have been
// overwritten by multiple calls to navigateAndRestore. Load the most
@ -4663,6 +4671,7 @@ var SessionStoreInternal = {
let window = tab.ownerGlobal;
let tabbrowser = window.gBrowser;
let forceOnDemand = options.forceOnDemand;
let isRemotenessUpdate = options.isRemotenessUpdate;
let willRestoreImmediately =
options.restoreImmediately || tabbrowser.selectedBrowser == browser;
@ -4792,7 +4801,12 @@ var SessionStoreInternal = {
// restoreTab will get called again when the browser is instantiated.
TAB_STATE_FOR_BROWSER.set(browser, TAB_STATE_NEEDS_RESTORE);
this._sendRestoreHistory(browser, { tabData, epoch, loadArguments });
this._sendRestoreHistory(browser, {
tabData,
epoch,
loadArguments,
isRemotenessUpdate,
});
// This could cause us to ignore MAX_CONCURRENT_TAB_RESTORES a bit, but
// it ensures each window will have its selected tab loaded.
@ -4874,44 +4888,48 @@ var SessionStoreInternal = {
this.markTabAsRestoring(aTab);
let newFrameloader = aOptions.newFrameloader;
let replaceBrowsingContext = aOptions.replaceBrowsingContext;
let redirectLoadSwitchId = aOptions.redirectLoadSwitchId;
let isRemotenessUpdate;
if (aOptions.remoteType !== undefined) {
// We already have a selected remote type so we update to that.
isRemotenessUpdate = tabbrowser.updateBrowserRemoteness(browser, {
remoteType: aOptions.remoteType,
newFrameloader,
replaceBrowsingContext,
redirectLoadSwitchId,
});
} else {
isRemotenessUpdate = tabbrowser.updateBrowserRemotenessByURL(
browser,
uri,
{
// If we aren't already updating the browser's remoteness, check if it's
// necessary.
let isRemotenessUpdate = aOptions.isRemotenessUpdate;
if (!isRemotenessUpdate) {
let newFrameloader = aOptions.newFrameloader;
let replaceBrowsingContext = aOptions.replaceBrowsingContext;
let redirectLoadSwitchId = aOptions.redirectLoadSwitchId;
if (aOptions.remoteType !== undefined) {
// We already have a selected remote type so we update to that.
isRemotenessUpdate = tabbrowser.updateBrowserRemoteness(browser, {
remoteType: aOptions.remoteType,
newFrameloader,
replaceBrowsingContext,
redirectLoadSwitchId,
}
);
}
});
} else {
isRemotenessUpdate = tabbrowser.updateBrowserRemotenessByURL(
browser,
uri,
{
newFrameloader,
replaceBrowsingContext,
redirectLoadSwitchId,
}
);
}
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);
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);
this._sendRestoreHistory(browser, {
tabData,
epoch,
loadArguments,
isRemotenessUpdate,
});
this._sendRestoreHistory(browser, {
tabData,
epoch,
loadArguments,
isRemotenessUpdate,
});
}
}
browser.messageManager.sendAsyncMessage("SessionStore:restoreTabContent", {
@ -6015,6 +6033,55 @@ var SessionStoreInternal = {
browser.frameLoader.requestEpochUpdate(options.epoch);
}
},
// Flush out session history state so that it can be used to restore the state
// into a new process in `finishTabRemotenessChange`.
//
// NOTE: This codepath is temporary while the Fission Session History rewrite
// is in process, and will be removed & replaced once that rewrite is
// complete. (bug 1645062)
async prepareToChangeRemoteness(aBrowser) {
aBrowser.messageManager.sendAsyncMessage(
"SessionStore:prepareForProcessChange"
);
await TabStateFlusher.flush(aBrowser);
},
// Handle finishing the remoteness change for a tab by restoring session
// history state into it, and resuming the ongoing network load.
//
// NOTE: This codepath is temporary while the Fission Session History rewrite
// is in process, and will be removed & replaced once that rewrite is
// complete. (bug 1645062)
finishTabRemotenessChange(aTab, aSwitchId) {
let window = aTab.ownerGlobal;
if (!window || !window.__SSi || window.closed) {
return;
}
let tabState = TabState.clone(aTab, TAB_CUSTOM_VALUES.get(aTab));
let options = {
restoreImmediately: true,
restoreContentReason: RESTORE_TAB_CONTENT_REASON.NAVIGATE_AND_RESTORE,
isRemotenessUpdate: true,
loadArguments: {
redirectLoadSwitchId: aSwitchId,
// As we're resuming a load which has been redirected from another
// process, record the history index which is currently being requested.
// It has to be offset by 1 to get back to native history indices from
// SessionStore history indicies.
redirectHistoryIndex: tabState.requestedIndex - 1,
},
};
// Need to reset restoring tabs.
if (TAB_STATE_FOR_BROWSER.has(aTab.linkedBrowser)) {
this._resetLocalTabRestoringState(aTab);
}
// Restore the state into the tab.
this.restoreTab(aTab, tabState, options);
},
};
/**

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

@ -22,12 +22,14 @@
#include "mozilla/ipc/ProtocolUtils.h"
#include "mozilla/net/DocumentLoadListener.h"
#include "mozilla/NullPrincipal.h"
#include "mozilla/MozPromiseInlines.h"
#include "nsGlobalWindowOuter.h"
#include "nsIWebBrowserChrome.h"
#include "nsNetUtil.h"
#include "nsSHistory.h"
#include "nsSecureBrowserUI.h"
#include "nsQueryObject.h"
#include "nsIBrowser.h"
using namespace mozilla::ipc;
@ -390,6 +392,26 @@ void CanonicalBrowsingContext::PendingRemotenessChange::ProcessReady(
return;
}
// Wait for our blocker promise to resolve, if present.
if (mPrepareToChangePromise) {
mPrepareToChangePromise->Then(
GetMainThreadSerialEventTarget(), __func__,
[self = RefPtr{this}, contentParent = RefPtr{aContentParent}](bool) {
self->Finish(contentParent);
},
[self = RefPtr{this}](nsresult aRv) { self->Cancel(aRv); });
return;
}
Finish(aContentParent);
}
void CanonicalBrowsingContext::PendingRemotenessChange::Finish(
ContentParent* aContentParent) {
if (!mPromise) {
return;
}
RefPtr<CanonicalBrowsingContext> target(mTarget);
if (target->IsDiscarded()) {
Cancel(NS_ERROR_FAILURE);
@ -403,16 +425,29 @@ void CanonicalBrowsingContext::PendingRemotenessChange::ProcessReady(
"We shouldn't be trying to change the remoteness of "
"non-remote iframes");
nsCOMPtr<nsIBrowser> browser = browserElement->AsBrowser();
if (!browser) {
Cancel(NS_ERROR_FAILURE);
return;
}
RefPtr<nsFrameLoaderOwner> frameLoaderOwner =
do_QueryObject(browserElement);
MOZ_RELEASE_ASSERT(frameLoaderOwner,
"embedder browser must be nsFrameLoaderOwner");
// Tell frontend code that this browser element is about to change process.
nsresult rv = browser->BeforeChangeRemoteness();
if (NS_FAILED(rv)) {
Cancel(rv);
return;
}
// The process has been created, hand off to nsFrameLoaderOwner to finish
// the process switch.
ErrorResult error;
frameLoaderOwner->ChangeRemotenessToProcess(
aContentParent, mPendingSwitchId, mReplaceBrowsingContext, error);
frameLoaderOwner->ChangeRemotenessToProcess(aContentParent,
mReplaceBrowsingContext, error);
if (error.Failed()) {
Cancel(error.StealNSResult());
return;
@ -420,15 +455,24 @@ void CanonicalBrowsingContext::PendingRemotenessChange::ProcessReady(
// We did it! The process switch is complete.
RefPtr<nsFrameLoader> frameLoader = frameLoaderOwner->GetFrameLoader();
if (RefPtr<BrowserParent> newBrowser = frameLoader->GetBrowserParent()) {
mPromise->Resolve(newBrowser, __func__);
Clear();
RefPtr<BrowserParent> newBrowser = frameLoader->GetBrowserParent();
if (!newBrowser) {
// Failed to create the BrowserParent somehow! Abort the process switch
// attempt.
Cancel(NS_ERROR_UNEXPECTED);
return;
}
// Failed to create the BrowserParent somehow! Abort the process switch
// attempt.
Cancel(NS_ERROR_UNEXPECTED);
// Tell frontend the load is done, and potentially resume the load ourselves
// if they don't do it.
bool loadResumed = false;
rv = browser->FinishChangeRemoteness(mPendingSwitchId, &loadResumed);
if (NS_FAILED(rv) || !loadResumed) {
newBrowser->ResumeLoad(mPendingSwitchId);
}
mPromise->Resolve(newBrowser, __func__);
Clear();
return;
}
@ -553,6 +597,7 @@ void CanonicalBrowsingContext::PendingRemotenessChange::Clear() {
mPromise = nullptr;
mTarget = nullptr;
mPrepareToChangePromise = nullptr;
}
CanonicalBrowsingContext::PendingRemotenessChange::PendingRemotenessChange(
@ -639,6 +684,24 @@ CanonicalBrowsingContext::ChangeRemoteness(const nsAString& aRemoteType,
this, promise, aPendingSwitchId, aReplaceBrowsingContext);
mPendingRemotenessChange = change;
// Call `prepareToChangeRemoteness` in parallel with starting a new process
// for <browser> loads.
if (IsTop() && GetEmbedderElement()) {
nsCOMPtr<nsIBrowser> browser = GetEmbedderElement()->AsBrowser();
if (!browser) {
change->Cancel(NS_ERROR_FAILURE);
return promise.forget();
}
RefPtr<Promise> blocker;
nsresult rv = browser->PrepareToChangeRemoteness(getter_AddRefs(blocker));
if (NS_FAILED(rv)) {
change->Cancel(rv);
return promise.forget();
}
change->mPrepareToChangePromise = GenericPromise::FromDomPromise(blocker);
}
ContentParent::GetNewOrUsedBrowserProcessAsync(
/* aFrameElement = */ nullptr,
/* aRemoteType = */ aRemoteType,

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

@ -16,11 +16,11 @@
#include "nsTArray.h"
#include "nsTHashtable.h"
#include "nsHashKeys.h"
class nsISHistory;
#include "nsISecureBrowserUI.h"
class nsISHistory;
class nsSecureBrowserUI;
namespace mozilla {
namespace net {
class DocumentLoadListener;
@ -185,14 +185,18 @@ class CanonicalBrowsingContext final : public BrowsingContext {
bool aReplaceBrowsingContext);
void Cancel(nsresult aRv);
void ProcessReady(ContentParent* aContentParent);
private:
friend class CanonicalBrowsingContext;
~PendingRemotenessChange();
void ProcessReady(ContentParent* aContentParent);
void Finish(ContentParent* aContentParent);
void Clear();
RefPtr<CanonicalBrowsingContext> mTarget;
RefPtr<RemotenessPromise::Private> mPromise;
RefPtr<GenericPromise> mPrepareToChangePromise;
uint64_t mPendingSwitchId;
bool mReplaceBrowsingContext;

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

@ -231,14 +231,22 @@ void nsFrameLoaderOwner::ChangeRemotenessWithBridge(BrowserBridgeChild* aBridge,
}
void nsFrameLoaderOwner::ChangeRemotenessToProcess(
ContentParent* aContentParent, uint64_t aPendingSwitchId,
bool aReplaceBrowsingContext, mozilla::ErrorResult& rv) {
ContentParent* aContentParent, bool aReplaceBrowsingContext,
mozilla::ErrorResult& rv) {
MOZ_ASSERT(XRE_IsParentProcess());
std::function<void()> frameLoaderInit = [&] {
mFrameLoader->ConfigRemoteProcess(aContentParent->GetRemoteType(),
aContentParent);
mFrameLoader->ResumeLoad(aPendingSwitchId);
// FIXME(bug 1644779): We'd like to stop triggering a load here, as this
// reads the attributes, such as `src`, on the <browser> element, and could
// start another load which will be clobbered shortly.
//
// This is OK for now, as we're mimicing the existing process switching
// behaviour, and <browser> elements created by tabbrowser don't have the
// `src` attribute specified.
mFrameLoader->LoadFrame(false);
};
auto shouldPreserve = ShouldPreserveBrowsingContext(

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

@ -69,7 +69,6 @@ class nsFrameLoaderOwner : public nsISupports {
// If `aReplaceBrowsingContext` is set, BrowsingContext preservation will be
// disabled for this process switch.
void ChangeRemotenessToProcess(mozilla::dom::ContentParent* aContentParent,
uint64_t aPendingSwitchId,
bool aReplaceBrowsingContext,
mozilla::ErrorResult& rv);

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

@ -207,4 +207,22 @@ interface nsIBrowser : nsISupports
Promise performProcessSwitch(in AString aRemoteType,
in uint64_t aPendingSwitchId,
in boolean aReplaceBrowsingContext);
/**
* Called to perform any async tasks which must be completed before changing
* remoteness. Gecko will wait for the returned promise to resolve before
* performing the process switch.
*/
Promise prepareToChangeRemoteness();
/** Called immediately before changing remoteness */
void beforeChangeRemoteness();
/**
* Called immediately after changing remoteness.
*
* If this method returns `true`, Gecko will assume frontend handled resuming
* the load, and will not attempt to resume the load itself.
*/
bool finishChangeRemoteness(in uint64_t aPendingSwitchId);
};

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

@ -1983,6 +1983,50 @@
replaceBrowsingContext
);
}
// This method is replaced by frontend code in order to delay performing the
// process switch until some async operatin is completed.
//
// This is used by tabbrowser to flush SessionStore before a process switch.
async prepareToChangeRemoteness() {
/* no-op unless replaced */
}
// Called by Gecko before the remoteness change happens, allowing for
// listeners, etc. to be stashed before the process switch.
beforeChangeRemoteness() {
// Fire the `WillChangeBrowserRemoteness` event, which may be hooked by
// frontend code for custom behaviour.
let event = document.createEvent("Events");
event.initEvent("WillChangeBrowserRemoteness", true, false);
this.dispatchEvent(event);
// Destroy ourselves to unregister from observer notifications
// FIXME: Can we get away with something less destructive here?
this.destroy();
}
finishChangeRemoteness(redirectLoadSwitchId) {
// Re-construct ourselves after the destroy in `beforeChangeRemoteness`.
this.construct();
// Fire the `DidChangeBrowserRemoteness` event, which may be hooked by
// frontend code for custom behaviour.
let event = document.createEvent("Events");
event.initEvent("DidChangeBrowserRemoteness", true, false);
this.dispatchEvent(event);
// If we have a tabbrowser, we need to let it handle restoring session
// history, and performing the `resumeRedirectedLoad`, in order to get
// sesssion state set up correctly.
// FIXME: This probably needs to be hookable by GeckoView.
let tabbrowser = this.getTabBrowser();
if (tabbrowser) {
tabbrowser.finishBrowserRemotenessChange(this, redirectLoadSwitchId);
return true;
}
return false;
}
}
MozXULElement.implementCustomInterface(MozBrowser, [Ci.nsIBrowser]);