Skip to content

Commit

Permalink
Bug 1539163 - Part 2: Support changing the process of subframes, r=qdot
Browse files Browse the repository at this point in the history
When a remote type mismatch is found for a subframe, this patch checks if
fission is enabled for that window. If it is, it triggers a process switch,
continuing the load in a new process.

With this patch, subframes will only change process when navigating to a HTML
subframe, and not when navigating to a non-HTML subframe. That will be fixed in
a follow-up. This patch also does not change the remote type selection logic,
so only very limited types of remote iframes are supported.

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

--HG--
extra : moz-landing-system : lando
  • Loading branch information
mystor committed Apr 17, 2019
1 parent bc80781 commit 50a5699
Show file tree
Hide file tree
Showing 7 changed files with 210 additions and 52 deletions.
119 changes: 67 additions & 52 deletions browser/components/sessionstore/SessionStore.jsm
Original file line number Diff line number Diff line change
Expand Up @@ -2276,12 +2276,7 @@ var SessionStoreInternal = {
}
},

/**
* Perform a destructive process switch into a distinct process.
* This method is asynchronous, as it requires multiple calls into content
* processes.
*/
async _doProcessSwitch(aBrowser, aRemoteType, aChannel, aSwitchId) {
async _doTabProcessSwitch(aBrowser, aRemoteType, aChannel, aSwitchId) {
debug(`[process-switch]: performing switch from ${aBrowser.remoteType} to ${aRemoteType}`);

// Don't try to switch tabs before delayed startup is completed.
Expand Down Expand Up @@ -2312,6 +2307,27 @@ var SessionStoreInternal = {
return tabParent;
},

/**
* Perform a destructive process switch into a distinct process.
* This method is asynchronous, as it requires multiple calls into content
* processes.
*/
async _doProcessSwitch(aBrowsingContext, aRemoteType, aChannel, aSwitchId) {
// There are two relevant cases when performing a process switch for a
// browsing context: in-process and out-of-process embedders.

// If our embedder is in-process (e.g. we're a xul:browser element embedded
// within <tabbrowser>), then we can perform a process switch using the
// traditional mechanism.
if (aBrowsingContext.embedderElement) {
return this._doTabProcessSwitch(aBrowsingContext.embedderElement,
aRemoteType, aChannel, aSwitchId);
}

let wg = aBrowsingContext.embedderWindowGlobal;
return wg.changeFrameRemoteness(aBrowsingContext, aRemoteType, aSwitchId);
},

// Examine the channel response to see if we should change the process
// performing the given load.
onMayChangeProcess(aChannel) {
Expand All @@ -2324,18 +2340,29 @@ var SessionStoreInternal = {
return; // Not a document load.
}

// Check that this is a toplevel document load.
let cpType = aChannel.loadInfo.externalContentPolicyType;
let toplevel = cpType == Ci.nsIContentPolicy.TYPE_DOCUMENT;
if (!toplevel) {
debug(`[process-switch]: non-toplevel - ignoring`);
return;
// Check that the document has a corresponding BrowsingContext.
let browsingContext;
let cp = aChannel.loadInfo.externalContentPolicyType;
if (cp == Ci.nsIContentPolicy.TYPE_DOCUMENT) {
browsingContext = aChannel.loadInfo.browsingContext;
} else {
browsingContext = aChannel.loadInfo.frameBrowsingContext;

let top = browsingContext.top;
if (!top.embedderElement) {
debug(`[process-switch]: no embedder for top - ignoring`);
return;
}

let docShell = top.embedderElement.ownerGlobal.docShell;
let loadContext = docShell.QueryInterface(Ci.nsILoadContext);

if (!loadContext.useRemoteSubframes) {
debug(`[process-switch]: remote subframes disabled - ignoring`);
return;
}
}

// Check that the document has a corresponding BrowsingContext.
let browsingContext = toplevel
? aChannel.loadInfo.browsingContext
: aChannel.loadInfo.frameBrowsingContext;
if (!browsingContext) {
debug(`[process-switch]: no BrowsingContext - ignoring`);
return;
Expand All @@ -2347,42 +2374,30 @@ var SessionStoreInternal = {
currentPrincipal = browsingContext.currentWindowGlobal.documentPrincipal;
}

// Ensure we have an nsIParentChannel listener for a remote load.
let parentChannel;
try {
parentChannel = aChannel.notificationCallbacks
.getInterface(Ci.nsIParentChannel);
} catch (e) {
debug(`[process-switch]: No nsIParentChannel callback - ignoring`);
return;
}

// Ensure we have a nsITabParent for our remote load.
let tabParent;
try {
tabParent = parentChannel.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsITabParent);
} catch (e) {
debug(`[process-switch]: No nsITabParent for channel - ignoring`);
return;
}

// Ensure we're loaded in a regular tabbrowser environment, and can swap processes.
let browser = tabParent.ownerElement;
if (!browser) {
debug(`[process-switch]: TabParent has no ownerElement - ignoring`);
return;
}
// We can only perform a process switch on in-process frames if they are
// embedded within a normal tab. We can't do one of these swaps for a
// cross-origin frame.
if (browsingContext.embedderElement) {
let tabbrowser = browsingContext.embedderElement.ownerGlobal.gBrowser;
if (!tabbrowser) {
debug(`[process-switch]: cannot find tabbrowser for loading tab - ignoring`);
return;
}

let tabbrowser = browser.ownerGlobal.gBrowser;
if (!tabbrowser) {
debug(`[process-switch]: cannot find tabbrowser for loading tab - ignoring`);
let tab = tabbrowser.getTabForBrowser(browsingContext.embedderElement);
if (!tab) {
debug(`[process-switch]: not a normal tab, so cannot swap processes - ignoring`);
return;
}
} else if (!browsingContext.parent) {
debug(`[process-switch] no parent or in-process embedder element - ignoring`);
return;
}

let tab = tabbrowser.getTabForBrowser(browser);
if (!tab) {
debug(`[process-switch]: not a normal tab, so cannot swap processes - ignoring`);
// Get the current remote type for the BrowsingContext.
let currentRemoteType = browsingContext.currentRemoteType;
if (currentRemoteType == E10SUtils.NOT_REMOTE) {
debug(`[process-switch]: currently not remote - ignoring`);
return;
}

Expand All @@ -2391,17 +2406,17 @@ var SessionStoreInternal = {
Services.scriptSecurityManager.getChannelResultPrincipal(aChannel);
let remoteType = E10SUtils.getRemoteTypeForPrincipal(resultPrincipal,
true,
browser.remoteType,
currentRemoteType,
currentPrincipal);
if (browser.remoteType == remoteType &&
if (currentRemoteType == remoteType &&
(!E10SUtils.useCrossOriginOpenerPolicy() ||
!aChannel.hasCrossOriginOpenerPolicyMismatch())) {
debug(`[process-switch]: type (${remoteType}) is compatible - ignoring`);
return;
}

if (remoteType == E10SUtils.NOT_REMOTE ||
browser.remoteType == E10SUtils.NOT_REMOTE) {
currentRemoteType == E10SUtils.NOT_REMOTE) {
debug(`[process-switch]: non-remote source/target - ignoring`);
return;
}
Expand All @@ -2411,7 +2426,7 @@ var SessionStoreInternal = {
// destructive.
// ------------------------------------------------------------------------
let identifier = ++this._switchIdMonotonic;
let tabPromise = this._doProcessSwitch(browser, remoteType,
let tabPromise = this._doProcessSwitch(browsingContext, remoteType,
aChannel, identifier);
aChannel.switchProcessTo(tabPromise, identifier);
},
Expand Down
5 changes: 5 additions & 0 deletions dom/chrome-webidl/WindowGlobalActors.webidl
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ interface WindowGlobalParent {

[Throws]
JSWindowActorParent getActor(DOMString name);

[Throws]
Promise<TabParent> changeFrameRemoteness(
BrowsingContext? bc, DOMString remoteType,
unsigned long long pendingSwitchId);
};

[Exposed=Window, ChromeOnly]
Expand Down
6 changes: 6 additions & 0 deletions dom/ipc/PWindowGlobal.ipdl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

include protocol PBrowser;
include protocol PInProcess;
include protocol PBrowserBridge;

include DOMTypes;

Expand All @@ -26,6 +27,11 @@ async protocol PWindowGlobal
child:
async __delete__();

async ChangeFrameRemoteness(BrowsingContext aFrameContext,
nsString aRemoteType,
uint64_t aSwitchId)
returns (nsresult rv, nullable PBrowserBridge bridge);

both:
async AsyncMessage(nsString aActorName, nsString aMessage, ClonedMessageData aData);

Expand Down
77 changes: 77 additions & 0 deletions dom/ipc/WindowGlobalChild.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include "mozilla/ClearOnShutdown.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/MozFrameLoaderOwnerBinding.h"
#include "mozilla/dom/TabChild.h"
#include "mozilla/dom/WindowGlobalActorsBinding.h"
#include "mozilla/dom/WindowGlobalParent.h"
Expand Down Expand Up @@ -140,6 +141,82 @@ void WindowGlobalChild::Destroy() {
mIPCClosed = true;
}

static nsresult ChangeFrameRemoteness(WindowGlobalChild* aWgc,
BrowsingContext* aBc,
const nsString& aRemoteType,
uint64_t aPendingSwitchId,
BrowserBridgeChild** aBridge) {
MOZ_ASSERT(XRE_IsContentProcess(), "This doesn't make sense in the parent");

// Get the target embedder's FrameLoaderOwner, and make sure we're in the
// right place.
RefPtr<Element> embedderElt = aBc->GetEmbedderElement();
if (!embedderElt) {
return NS_ERROR_NOT_AVAILABLE;
}

if (NS_WARN_IF(embedderElt->GetOwnerGlobal() != aWgc->WindowGlobal())) {
return NS_ERROR_UNEXPECTED;
}

RefPtr<nsFrameLoaderOwner> flo = do_QueryObject(embedderElt);
MOZ_ASSERT(flo, "Embedder must be a nsFrameLoaderOwner!");

MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());

// Actually perform the remoteness swap.
RemotenessOptions options;
options.mRemoteType.Construct(aRemoteType);
options.mPendingSwitchID.Construct(aPendingSwitchId);

ErrorResult error;
flo->ChangeRemoteness(options, error);
if (NS_WARN_IF(error.Failed())) {
return error.StealNSResult();
}

// Make sure we successfully created either an in-process nsDocShell or a
// cross-process BrowserBridgeChild. If we didn't, produce an error.
RefPtr<nsFrameLoader> frameLoader = flo->GetFrameLoader();
if (NS_WARN_IF(!frameLoader)) {
return NS_ERROR_FAILURE;
}

RefPtr<BrowserBridgeChild> bbc;
if (frameLoader->IsRemoteFrame()) {
bbc = frameLoader->GetBrowserBridgeChild();
if (NS_WARN_IF(!bbc)) {
return NS_ERROR_FAILURE;
}
} else {
nsDocShell* ds = frameLoader->GetDocShell(error);
if (NS_WARN_IF(error.Failed())) {
return error.StealNSResult();
}

if (NS_WARN_IF(!ds)) {
return NS_ERROR_FAILURE;
}
}

bbc.forget(aBridge);
return NS_OK;
}

IPCResult WindowGlobalChild::RecvChangeFrameRemoteness(
dom::BrowsingContext* aBc, const nsString& aRemoteType,
uint64_t aPendingSwitchId, ChangeFrameRemotenessResolver&& aResolver) {
MOZ_ASSERT(XRE_IsContentProcess(), "This doesn't make sense in the parent");

RefPtr<BrowserBridgeChild> bbc;
nsresult rv = ChangeFrameRemoteness(this, aBc, aRemoteType, aPendingSwitchId,
getter_AddRefs(bbc));

// To make the type system happy, we've gotta do some gymnastics.
aResolver(Tuple<const nsresult&, PBrowserBridgeChild*>(rv, bbc));
return IPC_OK();
}

IPCResult WindowGlobalChild::RecvAsyncMessage(const nsString& aActorName,
const nsString& aMessageName,
const ClonedMessageData& aData) {
Expand Down
4 changes: 4 additions & 0 deletions dom/ipc/WindowGlobalChild.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ class WindowGlobalChild : public nsWrapperCache, public PWindowGlobalChild {
const nsString& aMessage,
const ClonedMessageData& aData);

mozilla::ipc::IPCResult RecvChangeFrameRemoteness(
dom::BrowsingContext* aBc, const nsString& aRemoteType,
uint64_t aPendingSwitchId, ChangeFrameRemotenessResolver&& aResolver);

virtual void ActorDestroy(ActorDestroyReason aWhy) override;

private:
Expand Down
46 changes: 46 additions & 0 deletions dom/ipc/WindowGlobalParent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include "mozilla/ClearOnShutdown.h"
#include "mozilla/ipc/InProcessParent.h"
#include "mozilla/dom/BrowserBridgeParent.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/TabParent.h"
Expand Down Expand Up @@ -265,6 +266,51 @@ IPCResult WindowGlobalParent::RecvDidEmbedBrowsingContext(
return IPC_OK();
}

already_AddRefed<Promise> WindowGlobalParent::ChangeFrameRemoteness(
dom::BrowsingContext* aBc, const nsAString& aRemoteType,
uint64_t aPendingSwitchId, ErrorResult& aRv) {
RefPtr<TabParent> tabParent = GetTabParent();
if (NS_WARN_IF(!tabParent)) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}

nsIGlobalObject* global = xpc::NativeGlobal(xpc::PrivilegedJunkScope());
RefPtr<Promise> promise = Promise::Create(global, aRv);
if (aRv.Failed()) {
return nullptr;
}

// When the reply comes back from content, either resolve or reject.
auto resolve =
[=](mozilla::Tuple<nsresult, PBrowserBridgeParent*>&& aResult) {
nsresult rv = Get<0>(aResult);
RefPtr<BrowserBridgeParent> bridge =
static_cast<BrowserBridgeParent*>(Get<1>(aResult));
if (NS_FAILED(rv)) {
promise->MaybeReject(rv);
return;
}

// If we got a BrowserBridgeParent, the frame is out-of-process, so pull
// our target TabParent off of it. Otherwise, it's an in-process frame,
// so we can directly use ours.
if (bridge) {
promise->MaybeResolve(bridge->GetTabParent());
} else {
promise->MaybeResolve(tabParent);
}
};

auto reject = [=](ResponseRejectReason aReason) {
promise->MaybeReject(NS_ERROR_FAILURE);
};

SendChangeFrameRemoteness(aBc, PromiseFlatString(aRemoteType),
aPendingSwitchId, resolve, reject);
return promise.forget();
}

void WindowGlobalParent::ActorDestroy(ActorDestroyReason aWhy) {
mIPCClosed = true;
gWindowGlobalParentsById->Remove(mInnerWindowId);
Expand Down
5 changes: 5 additions & 0 deletions dom/ipc/WindowGlobalParent.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ class WindowGlobalParent final : public nsISupports,

bool IsCurrentGlobal();

already_AddRefed<Promise> ChangeFrameRemoteness(dom::BrowsingContext* aBc,
const nsAString& aRemoteType,
uint64_t aPendingSwitchId,
ErrorResult& aRv);

// Create a WindowGlobalParent from over IPC. This method should not be called
// from outside of the IPC constructors.
WindowGlobalParent(const WindowGlobalInit& aInit, bool aInProcess);
Expand Down

0 comments on commit 50a5699

Please sign in to comment.