Bug 1646892 - Allow DocumentChannel process switches into the parent process. r=jya,nika

Differential Revision: https://phabricator.services.mozilla.com/D80327
This commit is contained in:
Matt Woodrow 2020-06-27 04:10:23 +00:00
Родитель 8a6af22c34
Коммит 213fab51e4
15 изменённых файлов: 194 добавлений и 77 удалений

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

@ -40,6 +40,12 @@ void ChildProcessChannelListener::OnChannelReady(
}
}
ChildProcessChannelListener::~ChildProcessChannelListener() {
for (auto& args : mChannelArgs) {
args.GetData().mResolver(NS_ERROR_FAILURE);
}
}
already_AddRefed<ChildProcessChannelListener>
ChildProcessChannelListener::GetSingleton() {
if (!sCPCLSingleton) {

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

@ -37,7 +37,7 @@ class ChildProcessChannelListener final {
private:
ChildProcessChannelListener() = default;
~ChildProcessChannelListener() = default;
~ChildProcessChannelListener();
struct CallbackArgs {
RefPtr<nsDocShellLoadState> mLoadState;
nsTArray<Endpoint> mStreamFilterEndpoints;

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

@ -79,6 +79,7 @@
#include "mozilla/dom/JSWindowActorChild.h"
#include "mozilla/ipc/ProtocolUtils.h"
#include "mozilla/net/DocumentChannel.h"
#include "mozilla/net/ParentChannelWrapper.h"
#include "mozilla/net/UrlClassifierFeatureFactory.h"
#include "ReferrerInfo.h"
@ -206,6 +207,7 @@
#include "nsSHEntry.h"
#include "nsStructuredCloneContainer.h"
#include "nsSubDocumentFrame.h"
#include "nsURILoader.h"
#include "nsView.h"
#include "nsViewManager.h"
#include "nsViewSourceHandler.h"
@ -9436,8 +9438,7 @@ nsresult nsDocShell::DoURILoad(nsDocShellLoadState* aLoadState,
outRequest.forget(aRequest);
}
return OpenInitializedChannel(channel, uriLoader,
nsIURILoader::REDIRECTED_CHANNEL);
return OpenRedirectedChannel(aLoadState);
}
// There are two cases we care about:
@ -9723,15 +9724,10 @@ static nsresult AppendSegmentToString(nsIInputStream* aIn, void* aClosure,
return openFlags;
}
nsresult nsDocShell::OpenInitializedChannel(nsIChannel* aChannel,
nsIURILoader* aURILoader,
uint32_t aOpenFlags) {
nsresult rv = NS_OK;
void nsDocShell::UpdateMixedContentChannelForNewLoad(nsIChannel* aChannel) {
if (mLoadType == LOAD_NORMAL_ALLOW_MIXED_CONTENT ||
mLoadType == LOAD_RELOAD_ALLOW_MIXED_CONTENT) {
rv = SetMixedContentChannel(aChannel);
NS_ENSURE_SUCCESS(rv, rv);
SetMixedContentChannel(aChannel);
} else if (mMixedContentChannel) {
/*
* If the user "Disables Protection on This Page", we call
@ -9742,11 +9738,20 @@ nsresult nsDocShell::OpenInitializedChannel(nsIChannel* aChannel,
* This way, the user does not have to click the disable protection button
* over and over for browsing the same site.
*/
rv = nsContentUtils::CheckSameOrigin(mMixedContentChannel, aChannel);
nsresult rv =
nsContentUtils::CheckSameOrigin(mMixedContentChannel, aChannel);
if (NS_FAILED(rv) || NS_FAILED(SetMixedContentChannel(aChannel))) {
SetMixedContentChannel(nullptr);
}
}
}
nsresult nsDocShell::OpenInitializedChannel(nsIChannel* aChannel,
nsIURILoader* aURILoader,
uint32_t aOpenFlags) {
nsresult rv = NS_OK;
UpdateMixedContentChannelForNewLoad(aChannel);
// If anything fails here, make sure to clear our initial ClientSource.
auto cleanupInitialClient =
@ -9757,21 +9762,6 @@ nsresult nsDocShell::OpenInitializedChannel(nsIChannel* aChannel,
MaybeCreateInitialClientSource();
if (aOpenFlags & nsIURILoader::REDIRECTED_CHANNEL) {
nsCOMPtr<nsILoadInfo> loadInfo;
aChannel->GetLoadInfo(getter_AddRefs(loadInfo));
LoadInfo* li = static_cast<LoadInfo*>(loadInfo.get());
if (loadInfo->GetExternalContentPolicyType() ==
nsIContentPolicy::TYPE_DOCUMENT) {
li->UpdateBrowsingContextID(mBrowsingContext->Id());
} else if (loadInfo->GetExternalContentPolicyType() ==
nsIContentPolicy::TYPE_SUBDOCUMENT) {
li->UpdateFrameBrowsingContextID(mBrowsingContext->Id());
}
}
// TODO: more attributes need to be updated on the LoadInfo (bug 1561706)
// Let the client channel helper know if we are using DocumentChannel,
// since redirects get handled in the parent process in that case.
RefPtr<net::DocumentChannel> docChannel = do_QueryObject(aChannel);
@ -9794,12 +9784,6 @@ nsresult nsDocShell::OpenInitializedChannel(nsIChannel* aChannel,
rv = AddClientChannelHelperInChild(
aChannel, win->EventTargetFor(TaskCategory::Other));
docChannel->SetInitialClientInfo(GetInitialClientInfo());
} else if (aOpenFlags & nsIURILoader::REDIRECTED_CHANNEL) {
// If we did a process switch, then we should have an existing allocated
// ClientInfo, so we just need to allocate a corresponding ClientSource.
CreateReservedSourceIfNeeded(aChannel,
win->EventTargetFor(TaskCategory::Other));
rv = NS_OK;
} else {
rv = AddClientChannelHelper(aChannel, std::move(noReservedClient),
GetInitialClientInfo(),
@ -9821,6 +9805,81 @@ nsresult nsDocShell::OpenInitializedChannel(nsIChannel* aChannel,
return NS_OK;
}
nsresult nsDocShell::OpenRedirectedChannel(nsDocShellLoadState* aLoadState) {
nsCOMPtr<nsIChannel> channel = aLoadState->GetPendingRedirectedChannel();
MOZ_ASSERT(channel);
UpdateMixedContentChannelForNewLoad(channel);
// If anything fails here, make sure to clear our initial ClientSource.
auto cleanupInitialClient =
MakeScopeExit([&] { mInitialClientSource.reset(); });
nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow();
NS_ENSURE_TRUE(win, NS_ERROR_FAILURE);
MaybeCreateInitialClientSource();
nsCOMPtr<nsILoadInfo> loadInfo;
channel->GetLoadInfo(getter_AddRefs(loadInfo));
LoadInfo* li = static_cast<LoadInfo*>(loadInfo.get());
if (loadInfo->GetExternalContentPolicyType() ==
nsIContentPolicy::TYPE_DOCUMENT) {
li->UpdateBrowsingContextID(mBrowsingContext->Id());
} else if (loadInfo->GetExternalContentPolicyType() ==
nsIContentPolicy::TYPE_SUBDOCUMENT) {
li->UpdateFrameBrowsingContextID(mBrowsingContext->Id());
}
// TODO: more attributes need to be updated on the LoadInfo (bug 1561706)
// If we did a process switch, then we should have an existing allocated
// ClientInfo, so we just need to allocate a corresponding ClientSource.
CreateReservedSourceIfNeeded(channel,
win->EventTargetFor(TaskCategory::Other));
RefPtr<nsDocumentOpenInfo> loader =
new nsDocumentOpenInfo(this, nsIURILoader::DONT_RETARGET, nullptr);
channel->SetLoadGroup(mLoadGroup);
MOZ_ALWAYS_SUCCEEDS(loader->Prepare());
nsresult rv = NS_OK;
if (XRE_IsParentProcess()) {
// If we're in the parent, the we don't have an nsIChildChannel, just
// the original channel, which is already open in this process.
// DocumentLoadListener expects to get an nsIParentChannel, so
// we create a wrapper around the channel and nsIStreamListener
// that forwards functionality as needed, and then we register
// it under the provided identifier.
RefPtr<ParentChannelWrapper> wrapper =
new ParentChannelWrapper(channel, loader);
wrapper->Register(aLoadState->GetPendingRedirectChannelRegistrarId());
mLoadGroup->AddRequest(channel, nullptr);
} else if (nsCOMPtr<nsIChildChannel> childChannel =
do_QueryInterface(channel)) {
// Our channel was redirected from another process, so doesn't need to
// be opened again. However, it does need its listener hooked up
// correctly.
rv = childChannel->CompleteRedirectSetup(loader);
} else {
// It's possible for the redirected channel to not implement
// nsIChildChannel and be entirely local (like srcdoc). In that case we
// can just open the local instance and it will work.
rv = channel->AsyncOpen(loader);
}
if (rv == NS_ERROR_NO_CONTENT) {
return NS_OK;
}
NS_ENSURE_SUCCESS(rv, rv);
// Success. Keep the initial ClientSource if it exists.
cleanupInitialClient.release();
return NS_OK;
}
nsresult nsDocShell::ScrollToAnchor(bool aCurHasRef, bool aNewHasRef,
nsACString& aNewHash, uint32_t aLoadType) {
if (!mCurrentURI) {

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

@ -653,6 +653,9 @@ class nsDocShell final : public nsDocLoader,
nsresult OpenInitializedChannel(nsIChannel* aChannel,
nsIURILoader* aURILoader,
uint32_t aOpenFlags);
nsresult OpenRedirectedChannel(nsDocShellLoadState* aLoadState);
void UpdateMixedContentChannelForNewLoad(nsIChannel* aChannel);
MOZ_CAN_RUN_SCRIPT
nsresult ScrollToAnchor(bool aCurHasRef, bool aNewHasRef,

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

@ -183,7 +183,7 @@ nsDocShellLoadState::~nsDocShellLoadState() {}
nsresult nsDocShellLoadState::CreateFromPendingChannel(
nsIChannel* aPendingChannel, uint64_t aLoadIdentifier,
nsDocShellLoadState** aResult) {
uint64_t aRegistrarId, nsDocShellLoadState** aResult) {
// Create the nsDocShellLoadState object with default state pulled from the
// passed-in channel.
nsCOMPtr<nsIURI> uri;
@ -195,6 +195,7 @@ nsresult nsDocShellLoadState::CreateFromPendingChannel(
RefPtr<nsDocShellLoadState> loadState =
new nsDocShellLoadState(uri, aLoadIdentifier);
loadState->mPendingRedirectedChannel = aPendingChannel;
loadState->mChannelRegistrarId = aRegistrarId;
// Pull relevant state from the channel, and store it on the
// nsDocShellLoadState.

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

@ -50,6 +50,7 @@ class nsDocShellLoadState final {
static nsresult CreateFromPendingChannel(nsIChannel* aPendingChannel,
uint64_t aLoadIdentifier,
uint64_t aRegistarId,
nsDocShellLoadState** aResult);
static nsresult CreateFromLoadURIOptions(
@ -230,6 +231,10 @@ class nsDocShellLoadState final {
return mPendingRedirectedChannel;
}
uint64_t GetPendingRedirectChannelRegistrarId() const {
return mChannelRegistrarId;
}
void SetOriginalURIString(const nsCString& aOriginalURI) {
mOriginalURIString.emplace(aOriginalURI);
}
@ -426,6 +431,11 @@ class nsDocShellLoadState final {
// when initiating the load.
mozilla::Maybe<int32_t> mCancelContentJSEpoch;
// If mPendingRedirectChannel is set, then this is the identifier
// that the parent-process equivalent channel has been registered
// with using RedirectChannelRegistrar.
uint64_t mChannelRegistrarId;
// An identifier to make it possible to examine if two loads are
// equal, and which browsing context they belong to (see
// BrowsingContext::{Get, Set}CurrentLoadIdentifier)

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

@ -3447,7 +3447,8 @@ mozilla::ipc::IPCResult ContentChild::RecvCrossProcessRedirect(
RefPtr<nsDocShellLoadState> loadState;
rv = nsDocShellLoadState::CreateFromPendingChannel(
newChannel, aArgs.loadIdentifier(), getter_AddRefs(loadState));
newChannel, aArgs.loadIdentifier(), aArgs.registrarId(),
getter_AddRefs(loadState));
if (NS_WARN_IF(NS_FAILED(rv))) {
return IPC_OK();
}

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

@ -15,6 +15,7 @@
#include "mozilla/StaticPrefs_security.h"
#include "mozilla/dom/BrowsingContextGroup.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/ChildProcessChannelListener.h"
#include "mozilla/dom/ClientChannelHelper.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/ContentProcessManager.h"
@ -1312,8 +1313,9 @@ bool DocumentLoadListener::MaybeTriggerProcessSwitch(
NS_ConvertUTF16toUTF8(remoteType).get()));
return false;
}
if (NS_WARN_IF(remoteType.IsEmpty())) {
LOG(("Process Switch Abort: non-remote target process"));
if (NS_WARN_IF(!browsingContext->IsTop() && remoteType.IsEmpty())) {
LOG(("Process Switch Abort: non-remote target process for subframe"));
return false;
}
@ -1335,14 +1337,10 @@ bool DocumentLoadListener::MaybeTriggerProcessSwitch(
->Then(
GetMainThreadSerialEventTarget(), __func__,
[self = RefPtr{this}](BrowserParent* aBrowserParent) {
MOZ_DIAGNOSTIC_ASSERT(
aBrowserParent,
"Shouldn't have switched into the parent process, as we check "
"!remoteType.IsEmpty() earlier in MaybeTriggerProcessSwitch");
MOZ_ASSERT(self->mChannel,
"Something went wrong, channel got cancelled");
self->TriggerRedirectToRealChannel(
Some(aBrowserParent->Manager()->ChildID()));
self->TriggerRedirectToRealChannel(Some(
aBrowserParent ? aBrowserParent->Manager()->ChildID() : 0));
},
[self = RefPtr{this}](nsresult aStatusCode) {
MOZ_ASSERT(NS_FAILED(aStatusCode), "Status should be error");
@ -1351,6 +1349,42 @@ bool DocumentLoadListener::MaybeTriggerProcessSwitch(
return true;
}
RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise>
DocumentLoadListener::RedirectToParentProcess(uint32_t aRedirectFlags,
uint32_t aLoadFlags) {
// This is largely the same as ContentChild::RecvCrossProcessRedirect,
// except without needing to deserialize or create an nsIChildChannel.
RefPtr<nsDocShellLoadState> loadState;
nsDocShellLoadState::CreateFromPendingChannel(
mChannel, mLoadIdentifier, mRedirectChannelId, getter_AddRefs(loadState));
loadState->SetLoadFlags(mLoadStateLoadFlags);
loadState->SetLoadType(mLoadStateLoadType);
if (mSessionHistoryInfo) {
loadState->SetSessionHistoryInfo(*mSessionHistoryInfo);
}
// This is poorly named now.
RefPtr<ChildProcessChannelListener> processListener =
ChildProcessChannelListener::GetSingleton();
auto promise =
MakeRefPtr<PDocumentChannelParent::RedirectToRealChannelPromise::Private>(
__func__);
promise->UseDirectTaskDispatch(__func__);
auto resolve = [promise](nsresult aResult) {
promise->Resolve(aResult, __func__);
};
nsTArray<ipc::Endpoint<extensions::PStreamFilterParent>> endpoints;
processListener->OnChannelReady(loadState, mCrossProcessRedirectIdentifier,
std::move(endpoints), mTiming,
std::move(resolve));
return promise;
}
RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise>
DocumentLoadListener::RedirectToRealChannel(
uint32_t aRedirectFlags, uint32_t aLoadFlags,
@ -1379,6 +1413,10 @@ DocumentLoadListener::RedirectToRealChannel(
MOZ_ALWAYS_SUCCEEDS(registrar->RegisterChannel(mChannel, mRedirectChannelId));
if (aDestinationProcess) {
if (!*aDestinationProcess) {
MOZ_ASSERT(aStreamFilterEndpoints.IsEmpty());
return RedirectToParentProcess(aRedirectFlags, aLoadFlags);
}
dom::ContentParent* cp =
dom::ContentProcessManager::GetSingleton()->GetContentProcessById(
ContentParentId{*aDestinationProcess});
@ -1466,15 +1504,24 @@ void DocumentLoadListener::TriggerRedirectToRealChannel(
if (!mStreamFilterRequests.IsEmpty()) {
base::ProcessId pid = OtherPid();
if (aDestinationProcess) {
dom::ContentParent* cp =
dom::ContentProcessManager::GetSingleton()->GetContentProcessById(
ContentParentId(*aDestinationProcess));
if (cp) {
pid = cp->OtherPid();
if (*aDestinationProcess) {
dom::ContentParent* cp =
dom::ContentProcessManager::GetSingleton()->GetContentProcessById(
ContentParentId(*aDestinationProcess));
if (cp) {
pid = cp->OtherPid();
}
} else {
pid = 0;
}
}
for (StreamFilterRequest& request : mStreamFilterRequests) {
if (!pid) {
request.mPromise->Reject(false, __func__);
request.mPromise = nullptr;
continue;
}
ParentEndpoint parent;
nsresult rv = extensions::PStreamFilter::CreateEndpoints(
pid, request.mChildProcessId, &parent, &request.mChildEndpoint);

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

@ -271,6 +271,11 @@ class DocumentLoadListener : public nsIInterfaceRequestor,
const Maybe<uint64_t>& aDestinationProcess,
nsTArray<ParentEndpoint>&& aStreamFilterEndpoints);
// A helper for RedirectToRealChannel that handles the case where we started
// from a content process and are process switching into the parent process.
RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise>
RedirectToParentProcess(uint32_t aRedirectFlags, uint32_t aLoadFlags);
// Construct a LoadInfo object to use for the internal channel.
already_AddRefed<LoadInfo> CreateLoadInfo(
dom::CanonicalBrowsingContext* aBrowsingContext,

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

@ -7,6 +7,7 @@
#include "ParentChannelWrapper.h"
#include "mozilla/net/UrlClassifierCommon.h"
#include "nsIRedirectChannelRegistrar.h"
namespace mozilla {
namespace net {
@ -14,6 +15,15 @@ namespace net {
NS_IMPL_ISUPPORTS(ParentChannelWrapper, nsIParentChannel, nsIStreamListener,
nsIRequestObserver);
void ParentChannelWrapper::Register(uint64_t aRegistrarId) {
nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
RedirectChannelRegistrar::GetOrCreate();
nsCOMPtr<nsIChannel> dummy;
MOZ_ALWAYS_SUCCEEDS(
NS_LinkRedirectChannels(aRegistrarId, this, getter_AddRefs(dummy)));
MOZ_ASSERT(dummy == mChannel);
}
////////////////////////////////////////////////////////////////////////////////
// nsIParentChannel
////////////////////////////////////////////////////////////////////////////////

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

@ -18,6 +18,10 @@ class ParentChannelWrapper : public nsIParentChannel {
ParentChannelWrapper(nsIChannel* aChannel, nsIStreamListener* aListener)
: mChannel(aChannel), mListener(aListener) {}
// Registers this nsIParentChannel wrapper with the RedirectChannelRegistrar
// and holds a reference.
void Register(uint64_t aRegistrarId);
NS_DECL_ISUPPORTS
NS_DECL_NSIPARENTCHANNEL
NS_FORWARD_NSISTREAMLISTENER(mListener->)

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

@ -13,7 +13,6 @@
#include "nsDocShell.h"
#include "nsIObserverService.h"
#include "nsIClassifiedChannel.h"
#include "nsIRedirectChannelRegistrar.h"
extern mozilla::LazyLogModule gDocumentChannelLog;
#define LOG(fmt) MOZ_LOG(gDocumentChannelLog, mozilla::LogLevel::Verbose, fmt)
@ -118,13 +117,7 @@ ParentProcessDocumentChannel::OnRedirectVerifyCallback(nsresult aResult) {
RefPtr<ParentChannelWrapper> wrapper =
new ParentChannelWrapper(channel, mListener);
nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
RedirectChannelRegistrar::GetOrCreate();
nsCOMPtr<nsIChannel> dummy;
MOZ_ALWAYS_SUCCEEDS(
NS_LinkRedirectChannels(mDocumentLoadListener->GetRedirectChannelId(),
wrapper, getter_AddRefs(dummy)));
MOZ_ASSERT(dummy == channel);
wrapper->Register(mDocumentLoadListener->GetRedirectChannelId());
}
}

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

@ -824,7 +824,6 @@ var E10SUtils = {
// handled using DocumentChannel, then we can skip switching
// for now, and let DocumentChannel do it during the response.
if (
requiredRemoteType != NOT_REMOTE &&
uriObject &&
(remoteSubframes || documentChannel) &&
documentChannelPermittedForURI(uriObject)
@ -855,7 +854,6 @@ var E10SUtils = {
if (
(aRemoteSubframes || documentChannel) &&
wantRemoteType != NOT_REMOTE &&
documentChannelPermittedForURI(aURI)
) {
// We can switch later with documentchannel.
@ -893,7 +891,6 @@ var E10SUtils = {
if (
AppConstants.MOZ_WIDGET_TOOLKIT != "android" &&
(useRemoteSubframes || documentChannel) &&
wantRemoteType != NOT_REMOTE &&
documentChannelPermittedForURI(aURI)
) {
return true;

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

@ -53,12 +53,6 @@ interface nsIURILoader : nsISupports
* be indicated.
*/
const unsigned long DONT_RETARGET = 1 << 1;
/**
* If this flag is set, it means that aChannel has been redirected from
* another process, and should be reopened using CompleteRedirectSetup rather
* than AsyncOpen.
*/
const unsigned long REDIRECTED_CHANNEL = 1 << 2;
/* @} */
/**

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

@ -687,19 +687,6 @@ NS_IMETHODIMP nsURILoader::OpenURI(nsIChannel* channel, uint32_t aFlags,
}
}
if (aFlags & nsIURILoader::REDIRECTED_CHANNEL) {
// Our channel was redirected from another process, so doesn't need to
// be opened again. However, it does need its listener hooked up
// correctly.
if (nsCOMPtr<nsIChildChannel> childChannel = do_QueryInterface(channel)) {
return childChannel->CompleteRedirectSetup(loader);
}
// It's possible for the redirected channel to not implement
// nsIChildChannel and be entirely local (like srcdoc). In that case we
// can just open the local instance and it will work.
}
// This method is not complete. Eventually, we should first go
// to the content listener and ask them for a protocol handler...
// if they don't give us one, we need to go to the registry and get