Bug 1640019 - Part 1: Support toplevel process switches outside of tabbrowser, r=mattwoodrow

This new process switching behavior is only enabled for some browser elements,
which have specified a specific attribute. Turning this on for all browsers with
a `remote` attribute causes breakage in reftests.

The initial version does not handle switching from remote to parent or
vice-versa, that is covered in a later part.

Differential Revision: https://phabricator.services.mozilla.com/D78969
This commit is contained in:
Nika Layzell 2020-06-15 23:23:43 +00:00
Родитель 74ca6cc819
Коммит 212943c862
10 изменённых файлов: 352 добавлений и 181 удалений

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

@ -27,6 +27,7 @@
#include "nsNetUtil.h"
#include "nsSHistory.h"
#include "nsSecureBrowserUI.h"
#include "nsQueryObject.h"
using namespace mozilla::ipc;
@ -383,7 +384,7 @@ void CanonicalBrowsingContext::LoadURI(const nsAString& aURI,
LoadURI(loadState, true);
}
void CanonicalBrowsingContext::PendingRemotenessChange::Complete(
void CanonicalBrowsingContext::PendingRemotenessChange::ProcessReady(
ContentParent* aContentParent) {
if (!mPromise) {
return;
@ -395,6 +396,42 @@ void CanonicalBrowsingContext::PendingRemotenessChange::Complete(
return;
}
// If this BrowsingContext is embedded within the parent process, perform the
// process switch directly.
if (Element* browserElement = target->GetEmbedderElement()) {
MOZ_DIAGNOSTIC_ASSERT(target->IsTop(),
"We shouldn't be trying to change the remoteness of "
"non-remote iframes");
RefPtr<nsFrameLoaderOwner> frameLoaderOwner =
do_QueryObject(browserElement);
MOZ_RELEASE_ASSERT(frameLoaderOwner,
"embedder browser must be nsFrameLoaderOwner");
// The process has been created, hand off to nsFrameLoaderOwner to finish
// the process switch.
ErrorResult error;
frameLoaderOwner->ChangeRemotenessToProcess(
aContentParent, mPendingSwitchId, mReplaceBrowsingContext, error);
if (error.Failed()) {
Cancel(error.StealNSResult());
return;
}
// We did it! The process switch is complete.
RefPtr<nsFrameLoader> frameLoader = frameLoaderOwner->GetFrameLoader();
if (RefPtr<BrowserParent> newBrowser = frameLoader->GetBrowserParent()) {
mPromise->Resolve(newBrowser, __func__);
Clear();
return;
}
// Failed to create the BrowserParent somehow! Abort the process switch
// attempt.
Cancel(NS_ERROR_UNEXPECTED);
return;
}
RefPtr<WindowGlobalParent> embedderWindow = target->GetEmbedderWindowGlobal();
if (NS_WARN_IF(!embedderWindow) || NS_WARN_IF(!embedderWindow->CanSend())) {
Cancel(NS_ERROR_FAILURE);
@ -450,6 +487,7 @@ void CanonicalBrowsingContext::PendingRemotenessChange::Complete(
oldBrowser->Destroy();
}
MOZ_ASSERT(!mReplaceBrowsingContext, "Cannot replace BC for subframe");
nsCOMPtr<nsIPrincipal> initialPrincipal =
NullPrincipal::CreateWithInheritedAttributes(
target->OriginAttributesRef(),
@ -517,14 +555,27 @@ void CanonicalBrowsingContext::PendingRemotenessChange::Clear() {
mTarget = nullptr;
}
CanonicalBrowsingContext::PendingRemotenessChange::PendingRemotenessChange(
CanonicalBrowsingContext* aTarget, RemotenessPromise::Private* aPromise,
uint64_t aPendingSwitchId, bool aReplaceBrowsingContext)
: mTarget(aTarget),
mPromise(aPromise),
mPendingSwitchId(aPendingSwitchId),
mReplaceBrowsingContext(aReplaceBrowsingContext) {}
CanonicalBrowsingContext::PendingRemotenessChange::~PendingRemotenessChange() {
MOZ_ASSERT(!mPromise && !mTarget,
"should've already been Cancel() or Complete()-ed");
}
RefPtr<CanonicalBrowsingContext::RemotenessPromise>
CanonicalBrowsingContext::ChangeFrameRemoteness(const nsAString& aRemoteType,
uint64_t aPendingSwitchId) {
CanonicalBrowsingContext::ChangeRemoteness(const nsAString& aRemoteType,
uint64_t aPendingSwitchId,
bool aReplaceBrowsingContext) {
MOZ_DIAGNOSTIC_ASSERT(
!aReplaceBrowsingContext || (IsEmbeddedInProcess(0) && IsTopContent()),
"Cannot replace BrowsingContext for subframes");
// Ensure our embedder hasn't been destroyed already.
RefPtr<WindowGlobalParent> embedderWindowGlobal = GetEmbedderWindowGlobal();
if (!embedderWindowGlobal) {
@ -544,7 +595,8 @@ CanonicalBrowsingContext::ChangeFrameRemoteness(const nsAString& aRemoteType,
__func__);
}
if (aRemoteType.Equals(oldContent->GetRemoteType())) {
if (!aReplaceBrowsingContext &&
aRemoteType.Equals(oldContent->GetRemoteType())) {
NS_WARNING("Already in the correct process");
return RemotenessPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
}
@ -557,10 +609,9 @@ CanonicalBrowsingContext::ChangeFrameRemoteness(const nsAString& aRemoteType,
RefPtr<BrowserParent> embedderBrowser =
embedderWindowGlobal->GetBrowserParent();
MOZ_ASSERT(embedderBrowser);
// Switching to local. No new process, so perform switch sync.
if (aRemoteType.Equals(embedderBrowser->Manager()->GetRemoteType())) {
if (embedderBrowser &&
aRemoteType.Equals(embedderBrowser->Manager()->GetRemoteType())) {
if (GetCurrentWindowGlobal()) {
MOZ_DIAGNOSTIC_ASSERT(GetCurrentWindowGlobal()->IsProcessRoot());
RefPtr<BrowserParent> oldBrowser =
@ -576,6 +627,7 @@ CanonicalBrowsingContext::ChangeFrameRemoteness(const nsAString& aRemoteType,
oldBrowser->Destroy();
}
MOZ_ASSERT(!aReplaceBrowsingContext);
SetOwnerProcessId(embedderBrowser->Manager()->ChildID());
Unused << embedderWindowGlobal->SendMakeFrameLocal(this, aPendingSwitchId);
return RemotenessPromise::CreateAndResolve(embedderBrowser, __func__);
@ -583,8 +635,8 @@ CanonicalBrowsingContext::ChangeFrameRemoteness(const nsAString& aRemoteType,
// Switching to remote. Wait for new process to launch before switch.
auto promise = MakeRefPtr<RemotenessPromise::Private>(__func__);
RefPtr<PendingRemotenessChange> change =
new PendingRemotenessChange(this, promise, aPendingSwitchId);
RefPtr<PendingRemotenessChange> change = new PendingRemotenessChange(
this, promise, aPendingSwitchId, aReplaceBrowsingContext);
mPendingRemotenessChange = change;
ContentParent::GetNewOrUsedBrowserProcessAsync(
@ -596,7 +648,7 @@ CanonicalBrowsingContext::ChangeFrameRemoteness(const nsAString& aRemoteType,
->Then(
GetMainThreadSerialEventTarget(), __func__,
[change](ContentParent* aContentParent) {
change->Complete(aContentParent);
change->ProcessReady(aContentParent);
},
[change](LaunchError aError) { change->Cancel(NS_ERROR_FAILURE); });
return promise.forget();

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

@ -131,9 +131,15 @@ class CanonicalBrowsingContext final : public BrowsingContext {
void LoadURI(const nsAString& aURI, const LoadURIOptions& aOptions,
ErrorResult& aError);
// Internal method to change which process a BrowsingContext is being loaded
// in. The returned promise will resolve when the process switch is completed.
//
// A VoidString() aRemoteType argument will perform a process switch into the
// parent process, and the method will resolve with a null BrowserParent.
using RemotenessPromise = MozPromise<RefPtr<BrowserParent>, nsresult, false>;
RefPtr<RemotenessPromise> ChangeFrameRemoteness(const nsAString& aRemoteType,
uint64_t aPendingSwitchId);
RefPtr<RemotenessPromise> ChangeRemoteness(const nsAString& aRemoteType,
uint64_t aPendingSwitchId,
bool aReplaceBrowsingContext);
// Return a media controller from the top-level browsing context that can
// control all media belonging to this browsing context tree. Return nullptr
@ -175,13 +181,11 @@ class CanonicalBrowsingContext final : public BrowsingContext {
PendingRemotenessChange(CanonicalBrowsingContext* aTarget,
RemotenessPromise::Private* aPromise,
uint64_t aPendingSwitchId)
: mTarget(aTarget),
mPromise(aPromise),
mPendingSwitchId(aPendingSwitchId) {}
uint64_t aPendingSwitchId,
bool aReplaceBrowsingContext);
void Cancel(nsresult aRv);
void Complete(ContentParent* aContentParent);
void ProcessReady(ContentParent* aContentParent);
private:
~PendingRemotenessChange();
@ -191,6 +195,7 @@ class CanonicalBrowsingContext final : public BrowsingContext {
RefPtr<RemotenessPromise::Private> mPromise;
uint64_t mPendingSwitchId;
bool mReplaceBrowsingContext;
};
friend class net::DocumentLoadListener;

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

@ -100,6 +100,7 @@
#include "mozilla/dom/ChildSHistory.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/ContentProcessManager.h"
#include "mozilla/dom/BrowserBridgeChild.h"
#include "mozilla/dom/BrowserHost.h"
#include "mozilla/dom/BrowserBridgeHost.h"
@ -161,13 +162,13 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFrameLoader)
NS_INTERFACE_MAP_END
nsFrameLoader::nsFrameLoader(Element* aOwner, BrowsingContext* aBrowsingContext,
const nsAString& aRemoteType, bool aNetworkCreated)
bool aIsRemoteFrame, bool aNetworkCreated)
: mPendingBrowsingContext(aBrowsingContext),
mOwnerContent(aOwner),
mDetachedSubdocFrame(nullptr),
mPendingSwitchID(0),
mChildID(0),
mRemoteType(aRemoteType),
mRemoteType(VoidString()),
mDepthTooGreat(false),
mIsTopLevelContent(false),
mDestroyCalled(false),
@ -178,7 +179,7 @@ nsFrameLoader::nsFrameLoader(Element* aOwner, BrowsingContext* aBrowsingContext,
mNetworkCreated(aNetworkCreated),
mLoadingOriginalSrc(false),
mRemoteBrowserShown(false),
mIsRemoteFrame(!aRemoteType.IsEmpty()),
mIsRemoteFrame(aIsRemoteFrame),
mWillChangeProcess(false),
mObservingOwnerContent(false),
mTabProcessCrashFired(false) {}
@ -341,6 +342,42 @@ static bool InitialLoadIsRemote(Element* aOwner) {
nsGkAtoms::_true, eCaseMatters);
}
static void GetInitialRemoteTypeAndProcess(Element* aOwner,
nsAString& aRemoteType,
uint64_t* aChildID) {
MOZ_ASSERT(XRE_IsParentProcess());
*aChildID = 0;
// Check if there is an explicit `remoteType` attribute which we should use.
bool hasRemoteType =
aOwner->GetAttr(kNameSpaceID_None, nsGkAtoms::RemoteType, aRemoteType);
if (!hasRemoteType || aRemoteType.IsEmpty()) {
hasRemoteType = false;
aRemoteType.AssignLiteral(DEFAULT_REMOTE_TYPE);
}
// Check if `sameProcessAsFrameLoader` was used to override the process.
nsCOMPtr<nsIBrowser> browser = aOwner->AsBrowser();
if (!browser) {
return;
}
RefPtr<nsFrameLoader> otherLoader;
browser->GetSameProcessAsFrameLoader(getter_AddRefs(otherLoader));
if (!otherLoader) {
return;
}
BrowserParent* browserParent = BrowserParent::GetFrom(otherLoader);
if (!browserParent) {
return;
}
RefPtr<ContentParent> contentParent = browserParent->Manager();
MOZ_DIAGNOSTIC_ASSERT(
!hasRemoteType || contentParent->GetRemoteType() == aRemoteType,
"If specified, remoteType attribute must match sameProcessAsFrameLoader");
aRemoteType = contentParent->GetRemoteType();
*aChildID = contentParent->ChildID();
}
already_AddRefed<nsFrameLoader> nsFrameLoader::Create(
Element* aOwner, bool aNetworkCreated, nsIOpenWindowInfo* aOpenWindowInfo) {
NS_ENSURE_TRUE(aOwner, nullptr);
@ -375,30 +412,20 @@ already_AddRefed<nsFrameLoader> nsFrameLoader::Create(
CreateBrowsingContext(aOwner, aOpenWindowInfo);
NS_ENSURE_TRUE(context, nullptr);
// Determine the initial RemoteType from the load environment. An empty or
// void remote type denotes a non-remote frame, while a named remote type
// denotes a remote frame.
nsAutoString remoteType(VoidString());
if (InitialLoadIsRemote(aOwner)) {
// If the `remoteType` attribute is specified and valid, use it. Otherwise,
// use a default remote type.
bool hasRemoteType =
aOwner->GetAttr(kNameSpaceID_None, nsGkAtoms::RemoteType, remoteType);
if (!hasRemoteType || remoteType.IsEmpty()) {
remoteType.AssignLiteral(DEFAULT_REMOTE_TYPE);
}
}
bool isRemoteFrame = InitialLoadIsRemote(aOwner);
RefPtr<nsFrameLoader> fl =
new nsFrameLoader(aOwner, context, remoteType, aNetworkCreated);
new nsFrameLoader(aOwner, context, isRemoteFrame, aNetworkCreated);
fl->mOpenWindowInfo = aOpenWindowInfo;
if (isRemoteFrame) {
GetInitialRemoteTypeAndProcess(aOwner, fl->mRemoteType, &fl->mChildID);
}
return fl.forget();
}
/* static */
already_AddRefed<nsFrameLoader> nsFrameLoader::Recreate(
mozilla::dom::Element* aOwner, BrowsingContext* aContext,
const nsAString& aRemoteType, bool aNetworkCreated, bool aPreserveContext) {
mozilla::dom::Element* aOwner, BrowsingContext* aContext, bool aIsRemote,
bool aNetworkCreated, bool aPreserveContext) {
NS_ENSURE_TRUE(aOwner, nullptr);
#ifdef DEBUG
@ -417,7 +444,7 @@ already_AddRefed<nsFrameLoader> nsFrameLoader::Recreate(
NS_ENSURE_TRUE(context, nullptr);
RefPtr<nsFrameLoader> fl =
new nsFrameLoader(aOwner, context, aRemoteType, aNetworkCreated);
new nsFrameLoader(aOwner, context, aIsRemote, aNetworkCreated);
return fl.forget();
}
@ -482,6 +509,17 @@ void nsFrameLoader::LoadFrame(bool aOriginalSrc) {
}
}
void nsFrameLoader::ConfigRemoteProcess(const nsAString& aRemoteType,
ContentParent* aContentParent) {
MOZ_DIAGNOSTIC_ASSERT(IsRemoteFrame(), "Must be a remote frame");
MOZ_DIAGNOSTIC_ASSERT(!mRemoteBrowser, "Must not have a browser yet");
MOZ_DIAGNOSTIC_ASSERT_IF(aContentParent,
aContentParent->GetRemoteType() == aRemoteType);
mRemoteType = aRemoteType;
mChildID = aContentParent ? aContentParent->ChildID() : 0;
}
void nsFrameLoader::FireErrorEvent() {
if (!mOwnerContent) {
return;
@ -2386,26 +2424,6 @@ uint32_t nsFrameLoader::LazyHeight() const {
return lazyHeight;
}
static ContentParent* GetContentParent(Element* aBrowser) {
nsCOMPtr<nsIBrowser> browser = aBrowser ? aBrowser->AsBrowser() : nullptr;
if (!browser) {
return nullptr;
}
RefPtr<nsFrameLoader> otherLoader;
browser->GetSameProcessAsFrameLoader(getter_AddRefs(otherLoader));
if (!otherLoader) {
return nullptr;
}
BrowserParent* browserParent = BrowserParent::GetFrom(otherLoader);
if (browserParent) {
return browserParent->Manager();
}
return nullptr;
}
bool nsFrameLoader::EnsureRemoteBrowser() {
MOZ_ASSERT(IsRemoteFrame());
return mRemoteBrowser || TryRemoteBrowser();
@ -2414,10 +2432,9 @@ bool nsFrameLoader::EnsureRemoteBrowser() {
bool nsFrameLoader::TryRemoteBrowserInternal() {
NS_ASSERTION(!mRemoteBrowser,
"TryRemoteBrowser called with a remote browser already?");
MOZ_DIAGNOSTIC_ASSERT(
XRE_IsParentProcess(),
"Remote subframes should only be created using the "
"`CanonicalBrowsingContext::ChangeFrameRemoteness` API");
MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess(),
"Remote subframes should only be created using the "
"`CanonicalBrowsingContext::ChangeRemoteness` API");
AssertSafeToInit();
@ -2477,9 +2494,6 @@ bool nsFrameLoader::TryRemoteBrowserInternal() {
return false;
}
RefPtr<ContentParent> openerContentParent;
RefPtr<BrowserParent> sameTabGroupAs;
// <iframe mozbrowser> gets to skip these checks.
// iframes for JS plugins also get to skip these checks. We control the URL
// that gets loaded, but the load is triggered from the document containing
@ -2522,9 +2536,6 @@ bool nsFrameLoader::TryRemoteBrowserInternal() {
nsGkAtoms::content, eIgnoreCase)) {
return false;
}
// Try to get the related content parent from our browser element.
openerContentParent = GetContentParent(mOwnerContent);
}
uint32_t chromeFlags = 0;
@ -2556,9 +2567,14 @@ bool nsFrameLoader::TryRemoteBrowserInternal() {
}
nextRemoteBrowser->SetOwnerElement(ownerElement);
} else {
mRemoteBrowser = ContentParent::CreateBrowser(
context, ownerElement, mRemoteType, mPendingBrowsingContext,
openerContentParent);
RefPtr<ContentParent> contentParent;
if (mChildID != 0) {
ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
contentParent = cpm->GetContentProcessById(ContentParentId(mChildID));
}
mRemoteBrowser =
ContentParent::CreateBrowser(context, ownerElement, mRemoteType,
mPendingBrowsingContext, contentParent);
}
if (!mRemoteBrowser) {
return false;

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

@ -110,7 +110,7 @@ class nsFrameLoader final : public nsStubMutationObserver,
// FrameLoaders.
static already_AddRefed<nsFrameLoader> Recreate(Element* aOwner,
BrowsingContext* aContext,
const nsAString& aRemoteType,
bool aIsRemote,
bool aNetworkCreated,
bool aPreserveContext);
@ -391,6 +391,14 @@ class nsFrameLoader final : public nsStubMutationObserver,
void SetWillChangeProcess();
// Configure which remote process should be used to host the remote browser
// created in `TryRemoteBrowser`. This method _must_ be called before
// `TryRemoteBrowser`, and a script blocker must be on the stack.
//
// |aContentParent|, if set, must have the remote type |aRemoteType|.
void ConfigRemoteProcess(const nsAString& aRemoteType,
mozilla::dom::ContentParent* aContentParent);
void MaybeNotifyCrashed(mozilla::dom::BrowsingContext* aBrowsingContext,
mozilla::ipc::MessageChannel* aChannel);
@ -398,8 +406,8 @@ class nsFrameLoader final : public nsStubMutationObserver,
private:
nsFrameLoader(mozilla::dom::Element* aOwner,
mozilla::dom::BrowsingContext* aBrowsingContext,
const nsAString& aRemoteType, bool aNetworkCreated);
mozilla::dom::BrowsingContext* aBrowsingContext, bool aIsRemote,
bool aNetworkCreated);
~nsFrameLoader();
void SetOwnerContent(mozilla::dom::Element* aContent);

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

@ -56,33 +56,37 @@ bool nsFrameLoaderOwner::UseRemoteSubframes() {
return loadContext->UseRemoteSubframes();
}
bool nsFrameLoaderOwner::ShouldPreserveBrowsingContext(
const mozilla::dom::RemotenessOptions& aOptions) {
if (aOptions.mReplaceBrowsingContext) {
return false;
nsFrameLoaderOwner::ChangeRemotenessContextType
nsFrameLoaderOwner::ShouldPreserveBrowsingContext(
bool aIsRemote, bool aReplaceBrowsingContext) {
if (aReplaceBrowsingContext) {
return ChangeRemotenessContextType::DONT_PRESERVE_BUT_PROPAGATE;
}
if (XRE_IsParentProcess()) {
// Don't preserve for remote => parent loads.
if (aOptions.mRemoteType.IsVoid()) {
return false;
if (!aIsRemote) {
return ChangeRemotenessContextType::DONT_PRESERVE;
}
// Don't preserve for parent => remote loads.
if (mFrameLoader && !mFrameLoader->IsRemoteFrame()) {
return false;
return ChangeRemotenessContextType::DONT_PRESERVE;
}
}
// We will preserve our browsing context if either fission is enabled, or the
// `preserve_browsing_contexts` pref is active.
return UseRemoteSubframes() ||
StaticPrefs::fission_preserve_browsing_contexts();
if (UseRemoteSubframes() ||
StaticPrefs::fission_preserve_browsing_contexts()) {
return ChangeRemotenessContextType::PRESERVE;
}
return ChangeRemotenessContextType::DONT_PRESERVE;
}
void nsFrameLoaderOwner::ChangeRemotenessCommon(
const ChangeRemotenessContextType& aContextType,
bool aSwitchingInProgressLoad, const nsAString& aRemoteType,
bool aSwitchingInProgressLoad, bool aIsRemote,
std::function<void()>& aFrameLoaderInit, mozilla::ErrorResult& aRv) {
RefPtr<mozilla::dom::BrowsingContext> bc;
bool networkCreated = false;
@ -127,7 +131,7 @@ void nsFrameLoaderOwner::ChangeRemotenessCommon(
}
mFrameLoader = nsFrameLoader::Recreate(
owner, bc, aRemoteType, networkCreated,
owner, bc, aIsRemote, networkCreated,
aContextType == ChangeRemotenessContextType::PRESERVE);
if (NS_WARN_IF(!mFrameLoader)) {
aRv.Throw(NS_ERROR_FAILURE);
@ -186,7 +190,13 @@ void nsFrameLoaderOwner::ChangeRemotenessCommon(
void nsFrameLoaderOwner::ChangeRemoteness(
const mozilla::dom::RemotenessOptions& aOptions, mozilla::ErrorResult& rv) {
bool isRemote = !aOptions.mRemoteType.IsEmpty();
std::function<void()> frameLoaderInit = [&] {
if (isRemote) {
mFrameLoader->ConfigRemoteProcess(aOptions.mRemoteType, nullptr);
}
if (aOptions.mPendingSwitchID.WasPassed()) {
mFrameLoader->ResumeLoad(aOptions.mPendingSwitchID.Value());
} else {
@ -194,16 +204,10 @@ void nsFrameLoaderOwner::ChangeRemoteness(
}
};
ChangeRemotenessContextType preserveType =
ChangeRemotenessContextType::DONT_PRESERVE;
if (ShouldPreserveBrowsingContext(aOptions)) {
preserveType = ChangeRemotenessContextType::PRESERVE;
} else if (aOptions.mReplaceBrowsingContext) {
preserveType = ChangeRemotenessContextType::DONT_PRESERVE_BUT_PROPAGATE;
}
ChangeRemotenessCommon(preserveType, aOptions.mSwitchingInProgressLoad,
aOptions.mRemoteType, frameLoaderInit, rv);
auto shouldPreserve =
ShouldPreserveBrowsingContext(isRemote, aOptions.mReplaceBrowsingContext);
ChangeRemotenessCommon(shouldPreserve, aOptions.mSwitchingInProgressLoad,
isRemote, frameLoaderInit, rv);
}
void nsFrameLoaderOwner::ChangeRemotenessWithBridge(BrowserBridgeChild* aBridge,
@ -221,12 +225,26 @@ void nsFrameLoaderOwner::ChangeRemotenessWithBridge(BrowserBridgeChild* aBridge,
mFrameLoader->mRemoteBrowser = host;
};
// NOTE: We always use the DEFAULT_REMOTE_TYPE here, because we don't actually
// know the real remote type, and don't need to, as we're a content process.
ChangeRemotenessCommon(
/* preserve */ ChangeRemotenessContextType::PRESERVE,
/* switching in progress load */ true,
NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE), frameLoaderInit, rv);
ChangeRemotenessCommon(ChangeRemotenessContextType::PRESERVE,
/* inProgress */ true,
/* isRemote */ true, frameLoaderInit, rv);
}
void nsFrameLoaderOwner::ChangeRemotenessToProcess(
ContentParent* aContentParent, uint64_t aPendingSwitchId,
bool aReplaceBrowsingContext, mozilla::ErrorResult& rv) {
MOZ_ASSERT(XRE_IsParentProcess());
std::function<void()> frameLoaderInit = [&] {
mFrameLoader->ConfigRemoteProcess(aContentParent->GetRemoteType(),
aContentParent);
mFrameLoader->ResumeLoad(aPendingSwitchId);
};
auto shouldPreserve = ShouldPreserveBrowsingContext(
/* isRemote */ true, aReplaceBrowsingContext);
ChangeRemotenessCommon(shouldPreserve, /* inProgress */ true,
/* isRemote */ true, frameLoaderInit, rv);
}
void nsFrameLoaderOwner::SubframeCrashed() {
@ -253,6 +271,7 @@ void nsFrameLoaderOwner::SubframeCrashed() {
}));
};
ChangeRemotenessCommon(ChangeRemotenessContextType::PRESERVE, false,
VoidString(), frameLoaderInit, IgnoreErrors());
ChangeRemotenessCommon(ChangeRemotenessContextType::PRESERVE,
/* inProgress */ false,
/* isRemote */ false, frameLoaderInit, IgnoreErrors());
}

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

@ -46,23 +46,37 @@ class nsFrameLoaderOwner : public nsISupports {
mozilla::dom::BrowsingContext* GetExtantBrowsingContext();
// Destroy (if it exists) and recreate our frameloader, based on new
// remoteness requirements. This should follow the same path as
// tabbrowser.js's updateBrowserRemoteness, including running the same logic
// and firing the same events as unbinding a XULBrowserElement from the tree.
// However, this method is available from backend and does not manipulate the
// DOM.
// remoteness requirements.
//
// This method is called by frontend code when it wants to perform a
// remoteness update, and allows for behaviour such as preserving
// BrowsingContexts across process switches during navigation.
//
// See the WebIDL definition for more details.
void ChangeRemoteness(const mozilla::dom::RemotenessOptions& aOptions,
mozilla::ErrorResult& rv);
// Like `ChangeRemoteness` but switches to an already-created
// `BrowserBridgeChild`. This method is used when performing remote subframe
// process switches.
void ChangeRemotenessWithBridge(mozilla::dom::BrowserBridgeChild* aBridge,
mozilla::ErrorResult& rv);
// Like `ChangeRemoteness`, but switches into an already-created
// `ContentParent`. This method is used when performing toplevel process
// switches. If `aContentParent` is nullptr, switches into the parent process.
//
// 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);
void SubframeCrashed();
private:
bool UseRemoteSubframes();
bool ShouldPreserveBrowsingContext(
const mozilla::dom::RemotenessOptions& aOptions);
// The enum class for determine how to handle previous BrowsingContext during
// the change remoteness. It could be followings
@ -78,9 +92,11 @@ class nsFrameLoaderOwner : public nsISupports {
DONT_PRESERVE_BUT_PROPAGATE = 1,
PRESERVE = 2,
};
ChangeRemotenessContextType ShouldPreserveBrowsingContext(
bool aIsRemote, bool aReplaceBrowsingContext);
void ChangeRemotenessCommon(const ChangeRemotenessContextType& aContextType,
bool aSwitchingInProgressLoad,
const nsAString& aRemoteType,
bool aSwitchingInProgressLoad, bool aIsRemote,
std::function<void()>& aFrameLoaderInit,
mozilla::ErrorResult& aRv);

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

@ -160,6 +160,33 @@ interface nsIBrowser : nsISupports
in uint64_t aRequestContextID,
in AString aContentType);
/**
* Determine what process switching behavior this browser element should have.
*/
cenum ProcessBehavior : 8 {
// Gecko won't automatically change which process this frame, or it's
// subframes, are loaded in.
PROCESS_BEHAVIOR_DISABLED,
// If `useRemoteTabs` is enabled, Gecko will change which process this frame
// is loaded in automatically, without calling `performProcessSwitch`.
// When `useRemoteSubframes` is enabled, subframes will change processes.
PROCESS_BEHAVIOR_STANDARD,
// When `useRemoteTabs` is enabled, Gecko will call `performProcessSwitch`
// in order to change which process this frame is loaded in.
// When `useRemoteSubframes` is enabled, subframes will change processes.
PROCESS_BEHAVIOR_CUSTOM,
// Gecko won't automatically change which process this frame is loaded, but
// when `useRemoteSubframes` is enabled, subframes will change processes.
//
// NOTE: This configuration is included only for backwards compatibility,
// and will be removed, as it can easily lead to invalid behavior.
PROCESS_BEHAVIOR_SUBFRAME_ONLY,
};
readonly attribute nsIBrowser_ProcessBehavior processSwitchBehavior;
/**
* Called by Gecko when it wants to change the process which is currently
* being used to render content in this tab.
@ -167,6 +194,9 @@ interface nsIBrowser : nsISupports
* Returns a promise which will be resolved with the ChildID of the new
* process.
*
* NOTE: This method is only called if `processSwitchBehavior` is
* `PROCESS_BEHAVIOR_CUSTOM`.
*
* @param aRemoteType the new remote type to switch this browser into
* @param aPendingSwitchId unique identifier for the ongoing
* process-switching load
@ -177,7 +207,4 @@ interface nsIBrowser : nsISupports
Promise performProcessSwitch(in AString aRemoteType,
in uint64_t aPendingSwitchId,
in boolean aReplaceBrowsingContext);
/** If `true`, this browser supports the `performProcessSwitch` method */
readonly attribute bool canPerformProcessSwitch;
};

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

@ -1317,7 +1317,7 @@ already_AddRefed<RemoteBrowser> ContentParent::CreateBrowser(
RefPtr<ContentParent> constructorSender;
MOZ_RELEASE_ASSERT(XRE_IsParentProcess(),
"Cannot allocate BrowserParent in content process");
if (aOpenerContentParent) {
if (aOpenerContentParent && aOpenerContentParent->IsAlive()) {
constructorSender = aOpenerContentParent;
} else {
if (aContext.IsJSPlugin()) {

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

@ -1118,46 +1118,53 @@ bool DocumentLoadListener::MaybeTriggerProcessSwitch(
return false;
}
// We currently can't switch processes for toplevel loads unless they're
// loaded within a browser tab.
// FIXME: Ideally we won't do this in the future.
nsCOMPtr<nsIBrowser> browser;
bool isPreloadSwitch = false;
if (!browsingContext->GetParent()) {
Element* browserElement = browsingContext->GetEmbedderElement();
if (!browserElement) {
LOG(("Process Switch Abort: cannot get browser element"));
return false;
}
browser = browserElement->AsBrowser();
if (!browser) {
LOG(("Process Switch Abort: not loaded within nsIBrowser"));
return false;
}
bool loadedInTab = false;
if (NS_FAILED(browser->GetCanPerformProcessSwitch(&loadedInTab)) ||
!loadedInTab) {
LOG(("Process Switch Abort: browser is not loaded in a tab"));
return false;
}
// Determine what process switching behaviour is being requested by the root
// <browser> element.
Element* browserElement = browsingContext->Top()->GetEmbedderElement();
if (!browserElement) {
LOG(("Process Switch Abort: cannot get embedder element"));
return false;
}
nsCOMPtr<nsIBrowser> browser = browserElement->AsBrowser();
if (!browser) {
LOG(("Process Switch Abort: not loaded within nsIBrowser"));
return false;
}
// Leaving about:newtab from a used to be preloaded browser should run the
// process selecting algorithm again.
nsAutoString isPreloadBrowserStr;
if (browserElement->GetAttr(kNameSpaceID_None, nsGkAtoms::preloadedState,
isPreloadBrowserStr)) {
if (isPreloadBrowserStr.EqualsLiteral("consumed")) {
nsCOMPtr<nsIURI> originalURI;
if (NS_SUCCEEDED(
mChannel->GetOriginalURI(getter_AddRefs(originalURI)))) {
if (!originalURI->GetSpecOrDefault().EqualsLiteral("about:newtab")) {
LOG(("Process Switch: leaving preloaded browser"));
isPreloadSwitch = true;
browserElement->UnsetAttr(kNameSpaceID_None,
nsGkAtoms::preloadedState, true);
}
}
}
nsIBrowser::ProcessBehavior processBehavior =
nsIBrowser::PROCESS_BEHAVIOR_DISABLED;
nsresult rv = browser->GetProcessSwitchBehavior(&processBehavior);
if (NS_FAILED(rv)) {
MOZ_ASSERT_UNREACHABLE(
"nsIBrowser::GetProcessSwitchBehavior shouldn't fail");
LOG(("Process Switch Abort: failed to get process switch behavior"));
return false;
}
// Check if the process switch we're considering is disabled by the
// <browser>'s process behavior.
if (processBehavior == nsIBrowser::PROCESS_BEHAVIOR_DISABLED) {
LOG(("Process Switch Abort: switch disabled by <browser>"));
return false;
}
if (browsingContext->IsTop() &&
processBehavior == nsIBrowser::PROCESS_BEHAVIOR_SUBFRAME_ONLY) {
LOG(("Process Switch Abort: toplevel switch disabled by <browser>"));
return false;
}
bool isPreloadSwitch = false;
nsAutoString isPreloadBrowserStr;
if (browserElement->GetAttr(kNameSpaceID_None, nsGkAtoms::preloadedState,
isPreloadBrowserStr) &&
isPreloadBrowserStr.EqualsLiteral("consumed")) {
nsCOMPtr<nsIURI> originalURI;
if (NS_SUCCEEDED(mChannel->GetOriginalURI(getter_AddRefs(originalURI))) &&
!originalURI->GetSpecOrDefault().EqualsLiteral("about:newtab")) {
LOG(("Process Switch: leaving preloaded browser"));
isPreloadSwitch = true;
browserElement->UnsetAttr(kNameSpaceID_None, nsGkAtoms::preloadedState,
true);
}
}
@ -1173,7 +1180,7 @@ bool DocumentLoadListener::MaybeTriggerProcessSwitch(
// Get the final principal, used to select which process to load into.
nsCOMPtr<nsIPrincipal> resultPrincipal;
nsresult rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
mChannel, getter_AddRefs(resultPrincipal));
if (NS_FAILED(rv)) {
LOG(("Process Switch Abort: failed to get channel result principal"));
@ -1267,21 +1274,26 @@ bool DocumentLoadListener::MaybeTriggerProcessSwitch(
mDoingProcessSwitch = true;
RefPtr<DocumentLoadListener> self = this;
// At this point, we're actually going to perform a process switch, which
// involves calling into other logic.
if (browsingContext->GetParent()) {
LOG(("Process Switch: Calling ChangeFrameRemoteness"));
// If we're switching a subframe, ask BrowsingContext to do it for us.
MOZ_ASSERT(!isCOOPSwitch);
browsingContext
->ChangeFrameRemoteness(remoteType, mCrossProcessRedirectIdentifier)
// If <browser> has custom process switching behaviour, use that.
if (processBehavior == nsIBrowser::PROCESS_BEHAVIOR_CUSTOM &&
browsingContext->IsTop()) {
LOG(("Process Switch: Calling nsIBrowser::PerformProcessSwitch"));
// We're switching a toplevel BrowsingContext's process. This has to be done
// using nsIBrowser.
RefPtr<dom::Promise> domPromise;
browser->PerformProcessSwitch(remoteType, mCrossProcessRedirectIdentifier,
isCOOPSwitch, getter_AddRefs(domPromise));
MOZ_DIAGNOSTIC_ASSERT(domPromise,
"PerformProcessSwitch didn't return a promise");
MozPromise<uint64_t, nsresult, true>::FromDomPromise(domPromise)
->Then(
GetMainThreadSerialEventTarget(), __func__,
[self](BrowserParent* aBrowserParent) {
[self](uint64_t aCpId) {
MOZ_ASSERT(self->mChannel,
"Something went wrong, channel got cancelled");
self->TriggerRedirectToRealChannel(
Some(aBrowserParent->Manager()->ChildID()));
self->TriggerRedirectToRealChannel(Some(aCpId));
},
[self](nsresult aStatusCode) {
MOZ_ASSERT(NS_FAILED(aStatusCode), "Status should be error");
@ -1290,22 +1302,17 @@ bool DocumentLoadListener::MaybeTriggerProcessSwitch(
return true;
}
LOG(("Process Switch: Calling nsIBrowser::PerformProcessSwitch"));
// We're switching a toplevel BrowsingContext's process. This has to be done
// using nsIBrowser.
RefPtr<dom::Promise> domPromise;
browser->PerformProcessSwitch(remoteType, mCrossProcessRedirectIdentifier,
isCOOPSwitch, getter_AddRefs(domPromise));
MOZ_DIAGNOSTIC_ASSERT(domPromise,
"PerformProcessSwitch didn't return a promise");
MozPromise<uint64_t, nsresult, true>::FromDomPromise(domPromise)
LOG(("Process Switch: Calling ChangeRemoteness"));
browsingContext
->ChangeRemoteness(remoteType, mCrossProcessRedirectIdentifier,
isCOOPSwitch)
->Then(
GetMainThreadSerialEventTarget(), __func__,
[self](uint64_t aCpId) {
[self](BrowserParent* aBrowserParent) {
MOZ_ASSERT(self->mChannel,
"Something went wrong, channel got cancelled");
self->TriggerRedirectToRealChannel(Some(aCpId));
self->TriggerRedirectToRealChannel(
Some(aBrowserParent->Manager()->ChildID()));
},
[self](nsresult aStatusCode) {
MOZ_ASSERT(NS_FAILED(aStatusCode), "Status should be error");

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

@ -1946,8 +1946,29 @@
return origins;
}
get canPerformProcessSwitch() {
return this.getTabBrowser() != null;
get processSwitchBehavior() {
// If a `remotenessChangeHandler` is attached to this browser, it supports
// having its toplevel process switched dynamically in response to
// navigations.
if (this.hasAttribute("maychangeremoteness")) {
return Ci.nsIBrowser.PROCESS_BEHAVIOR_STANDARD;
}
// When loaded in a TabBrowser, use the custom behavior from
// `performProcessSwitch`.
if (this.getTabBrowser() != null) {
return Ci.nsIBrowser.PROCESS_BEHAVIOR_CUSTOM;
}
// For backwards compatibility, we need to mark remote, but
// non-`allowremote`, frames as `PROCESS_BEHAVIOR_SUBFRAME_ONLY`, as some
// tests rely on it.
// FIXME: Remove this?
if (this.isRemoteBrowser) {
return Ci.nsIBrowser.PROCESS_BEHAVIOR_SUBFRAME_ONLY;
}
// Otherwise, don't allow gecko-initiated toplevel process switches.
return Ci.nsIBrowser.PROCESS_BEHAVIOR_DISABLED;
}
performProcessSwitch(