diff options
Diffstat (limited to '')
-rw-r--r-- | netwerk/ipc/DocumentChannelChild.cpp | 483 |
1 files changed, 483 insertions, 0 deletions
diff --git a/netwerk/ipc/DocumentChannelChild.cpp b/netwerk/ipc/DocumentChannelChild.cpp new file mode 100644 index 0000000000..ead243eaa2 --- /dev/null +++ b/netwerk/ipc/DocumentChannelChild.cpp @@ -0,0 +1,483 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ + +/* 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/. */ + +#include "DocumentChannelChild.h" + +#include "mozilla/dom/Document.h" +#include "mozilla/dom/RemoteType.h" +#include "mozilla/extensions/StreamFilterParent.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/net/HttpBaseChannel.h" +#include "mozilla/net/NeckoChild.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/StaticPrefs_fission.h" +#include "nsHashPropertyBag.h" +#include "nsIHttpChannelInternal.h" +#include "nsIObjectLoadingContent.h" +#include "nsIXULRuntime.h" +#include "nsIWritablePropertyBag.h" +#include "nsFrameLoader.h" +#include "nsFrameLoaderOwner.h" +#include "nsQueryObject.h" +#include "nsDocShellLoadState.h" + +using namespace mozilla::dom; +using namespace mozilla::ipc; + +extern mozilla::LazyLogModule gDocumentChannelLog; +#define LOG(fmt) MOZ_LOG(gDocumentChannelLog, mozilla::LogLevel::Verbose, fmt) + +namespace mozilla { +namespace net { + +//----------------------------------------------------------------------------- +// DocumentChannelChild::nsISupports + +NS_INTERFACE_MAP_BEGIN(DocumentChannelChild) + NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback) +NS_INTERFACE_MAP_END_INHERITING(DocumentChannel) + +NS_IMPL_ADDREF_INHERITED(DocumentChannelChild, DocumentChannel) +NS_IMPL_RELEASE_INHERITED(DocumentChannelChild, DocumentChannel) + +DocumentChannelChild::DocumentChannelChild(nsDocShellLoadState* aLoadState, + net::LoadInfo* aLoadInfo, + nsLoadFlags aLoadFlags, + uint32_t aCacheKey, + bool aUriModified, + bool aIsEmbeddingBlockedError) + : DocumentChannel(aLoadState, aLoadInfo, aLoadFlags, aCacheKey, + aUriModified, aIsEmbeddingBlockedError) { + mLoadingContext = nullptr; + LOG(("DocumentChannelChild ctor [this=%p, uri=%s]", this, + aLoadState->URI()->GetSpecOrDefault().get())); +} + +DocumentChannelChild::~DocumentChannelChild() { + LOG(("DocumentChannelChild dtor [this=%p]", this)); +} + +NS_IMETHODIMP +DocumentChannelChild::AsyncOpen(nsIStreamListener* aListener) { + nsresult rv = NS_OK; + + nsCOMPtr<nsIStreamListener> listener = aListener; + + NS_ENSURE_TRUE(gNeckoChild, NS_ERROR_FAILURE); + NS_ENSURE_ARG_POINTER(listener); + NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS); + NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED); + + // Port checked in parent, but duplicate here so we can return with error + // immediately, as we've done since before e10s. + rv = NS_CheckPortSafety(mURI); + NS_ENSURE_SUCCESS(rv, rv); + + bool isNotDownload = mLoadState->FileName().IsVoid(); + + // If not a download, add ourselves to the load group + if (isNotDownload && mLoadGroup) { + // During this call, we can re-enter back into the DocumentChannelChild to + // call SetNavigationTiming. + mLoadGroup->AddRequest(this, nullptr); + } + + if (mCanceled) { + // We may have been canceled already, either by on-modify-request + // listeners or by load group observers; in that case, don't create IPDL + // connection. See nsHttpChannel::AsyncOpen(). + return mStatus; + } + + gHttpHandler->OnOpeningDocumentRequest(this); + + RefPtr<nsDocShell> docShell = GetDocShell(); + if (!docShell) { + return NS_ERROR_FAILURE; + } + + // `loadingContext` is the BC that is initiating the resource load. + // For normal subdocument loads, the BC is the one that the subdoc will load + // into. For <object>/<embed> it's the embedder doc's BC. + RefPtr<BrowsingContext> loadingContext = docShell->GetBrowsingContext(); + if (!loadingContext || loadingContext->IsDiscarded()) { + return NS_ERROR_FAILURE; + } + mLoadingContext = loadingContext; + + Maybe<IPCClientInfo> ipcClientInfo; + if (mInitialClientInfo.isSome()) { + ipcClientInfo.emplace(mInitialClientInfo.ref().ToIPC()); + } + + DocumentChannelElementCreationArgs ipcElementCreationArgs; + switch (mLoadInfo->GetExternalContentPolicyType()) { + case ExtContentPolicy::TYPE_DOCUMENT: + case ExtContentPolicy::TYPE_SUBDOCUMENT: { + DocumentCreationArgs docArgs; + docArgs.uriModified() = mUriModified; + docArgs.isEmbeddingBlockedError() = mIsEmbeddingBlockedError; + + ipcElementCreationArgs = docArgs; + break; + } + + case ExtContentPolicy::TYPE_OBJECT: { + ObjectCreationArgs objectArgs; + objectArgs.embedderInnerWindowId() = InnerWindowIDForExtantDoc(docShell); + objectArgs.loadFlags() = mLoadFlags; + objectArgs.contentPolicyType() = mLoadInfo->InternalContentPolicyType(); + objectArgs.isUrgentStart() = UserActivation::IsHandlingUserInput(); + + ipcElementCreationArgs = objectArgs; + break; + } + + default: + MOZ_ASSERT_UNREACHABLE("unsupported content policy type"); + return NS_ERROR_FAILURE; + } + + switch (mLoadInfo->GetExternalContentPolicyType()) { + case ExtContentPolicy::TYPE_DOCUMENT: + case ExtContentPolicy::TYPE_SUBDOCUMENT: + MOZ_ALWAYS_SUCCEEDS(loadingContext->SetCurrentLoadIdentifier( + Some(mLoadState->GetLoadIdentifier()))); + break; + + default: + break; + } + + mLoadState->AssertProcessCouldTriggerLoadIfSystem(); + + DocumentChannelCreationArgs args( + mozilla::WrapNotNull(mLoadState), TimeStamp::Now(), mChannelId, mCacheKey, + mTiming, ipcClientInfo, ipcElementCreationArgs, + loadingContext->GetParentInitiatedNavigationEpoch()); + + gNeckoChild->SendPDocumentChannelConstructor(this, loadingContext, args); + + mIsPending = true; + mWasOpened = true; + mListener = listener; + + return NS_OK; +} + +IPCResult DocumentChannelChild::RecvFailedAsyncOpen( + const nsresult& aStatusCode) { + if (aStatusCode == NS_ERROR_RECURSIVE_DOCUMENT_LOAD) { + // This exists so that we are able to fire an error event + // for when there are too many recursive iframe or object loads. + // This is an incomplete solution, because right now we don't have a unified + // way of firing error events due to errors in document channel. + // This should be fixed in bug 1629201. + MOZ_DIAGNOSTIC_ASSERT(mLoadingContext); + if (RefPtr<Element> embedder = mLoadingContext->GetEmbedderElement()) { + if (RefPtr<nsFrameLoaderOwner> flo = do_QueryObject(embedder)) { + if (RefPtr<nsFrameLoader> fl = flo->GetFrameLoader()) { + fl->FireErrorEvent(); + } + } + } + } + ShutdownListeners(aStatusCode); + return IPC_OK(); +} + +IPCResult DocumentChannelChild::RecvDisconnectChildListeners( + const nsresult& aStatus, const nsresult& aLoadGroupStatus, + bool aContinueNavigating) { + // If this disconnect is not due to a process switch, perform the disconnect + // immediately. + if (!aContinueNavigating) { + DisconnectChildListeners(aStatus, aLoadGroupStatus); + return IPC_OK(); + } + + // Otherwise, the disconnect will occur later using some other mechanism, + // depending on what's happening to the loading DocShell. If this is a + // toplevel navigation, and this BrowsingContext enters the BFCache, we will + // cancel this channel when the PageHide event is firing, whereas if it does + // not enter BFCache (e.g. due to being an object, subframe or non-bfcached + // toplevel navigation), we will cancel this channel when the DocShell is + // destroyed. + nsDocShell* shell = GetDocShell(); + if (mLoadInfo->GetExternalContentPolicyType() == + ExtContentPolicy::TYPE_DOCUMENT && + shell) { + MOZ_ASSERT(shell->GetBrowsingContext()->IsTop()); + if (mozilla::SessionHistoryInParent() && + shell->GetBrowsingContext()->IsInBFCache()) { + DisconnectChildListeners(aStatus, aLoadGroupStatus); + } else { + // Tell the DocShell which channel to cancel if it enters the BFCache. + shell->SetChannelToDisconnectOnPageHide(mChannelId); + } + } + + return IPC_OK(); +} + +IPCResult DocumentChannelChild::RecvRedirectToRealChannel( + RedirectToRealChannelArgs&& aArgs, + nsTArray<Endpoint<extensions::PStreamFilterParent>>&& aEndpoints, + RedirectToRealChannelResolver&& aResolve) { + LOG(("DocumentChannelChild RecvRedirectToRealChannel [this=%p, uri=%s]", this, + aArgs.uri()->GetSpecOrDefault().get())); + + // The document that created the cspToInherit. + // This is used when deserializing LoadInfo from the parent + // process, since we can't serialize Documents directly. + // TODO: For a fission OOP iframe this will be unavailable, + // as will the loadingContext computed in LoadInfoArgsToLoadInfo. + // Figure out if we need these for cross-origin subdocs. + RefPtr<dom::Document> cspToInheritLoadingDocument; + nsCOMPtr<nsIContentSecurityPolicy> policy = mLoadState->Csp(); + if (policy) { + nsWeakPtr ctx = + static_cast<nsCSPContext*>(policy.get())->GetLoadingContext(); + cspToInheritLoadingDocument = do_QueryReferent(ctx); + } + nsCOMPtr<nsILoadInfo> loadInfo; + MOZ_ALWAYS_SUCCEEDS(LoadInfoArgsToLoadInfo(aArgs.loadInfo(), NOT_REMOTE_TYPE, + cspToInheritLoadingDocument, + getter_AddRefs(loadInfo))); + + mRedirectResolver = std::move(aResolve); + + nsCOMPtr<nsIChannel> newChannel; + MOZ_ASSERT((aArgs.loadStateInternalLoadFlags() & + nsDocShell::InternalLoad::INTERNAL_LOAD_FLAGS_IS_SRCDOC) || + aArgs.srcdocData().IsVoid()); + nsresult rv = nsDocShell::CreateRealChannelForDocument( + getter_AddRefs(newChannel), aArgs.uri(), loadInfo, nullptr, + aArgs.newLoadFlags(), aArgs.srcdocData(), aArgs.baseUri()); + if (newChannel) { + newChannel->SetLoadGroup(mLoadGroup); + } + + if (RefPtr<HttpBaseChannel> httpChannel = do_QueryObject(newChannel)) { + httpChannel->SetEarlyHints(std::move(aArgs.earlyHints())); + httpChannel->SetEarlyHintLinkType(aArgs.earlyHintLinkType()); + } + + // This is used to report any errors back to the parent by calling + // CrossProcessRedirectFinished. + auto scopeExit = MakeScopeExit([&]() { + mRedirectResolver(rv); + mRedirectResolver = nullptr; + }); + + if (NS_FAILED(rv)) { + return IPC_OK(); + } + + if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel)) { + rv = httpChannel->SetChannelId(aArgs.channelId()); + } + if (NS_FAILED(rv)) { + return IPC_OK(); + } + + rv = newChannel->SetOriginalURI(aArgs.originalURI()); + if (NS_FAILED(rv)) { + return IPC_OK(); + } + + if (nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal = + do_QueryInterface(newChannel)) { + rv = httpChannelInternal->SetRedirectMode(aArgs.redirectMode()); + } + if (NS_FAILED(rv)) { + return IPC_OK(); + } + + newChannel->SetNotificationCallbacks(mCallbacks); + + if (aArgs.init()) { + HttpBaseChannel::ReplacementChannelConfig config(*aArgs.init()); + HttpBaseChannel::ConfigureReplacementChannel( + newChannel, config, + HttpBaseChannel::ReplacementReason::DocumentChannel); + } + + if (aArgs.contentDisposition()) { + newChannel->SetContentDisposition(*aArgs.contentDisposition()); + } + + if (aArgs.contentDispositionFilename()) { + newChannel->SetContentDispositionFilename( + *aArgs.contentDispositionFilename()); + } + + nsDocShell* docShell = GetDocShell(); + if (docShell && aArgs.loadingSessionHistoryInfo().isSome()) { + docShell->SetLoadingSessionHistoryInfo( + aArgs.loadingSessionHistoryInfo().ref()); + } + + // transfer any properties. This appears to be entirely a content-side + // interface and isn't copied across to the parent. Copying the values + // for this from this into the new actor will work, since the parent + // won't have the right details anyway. + // TODO: What about the process switch equivalent + // (ContentChild::RecvCrossProcessRedirect)? In that case there is no local + // existing actor in the destination process... We really need all information + // to go up to the parent, and then come down to the new child actor. + if (nsCOMPtr<nsIWritablePropertyBag> bag = do_QueryInterface(newChannel)) { + nsHashPropertyBag::CopyFrom(bag, aArgs.properties()); + } + + // connect parent. + nsCOMPtr<nsIChildChannel> childChannel = do_QueryInterface(newChannel); + if (childChannel) { + rv = childChannel->ConnectParent( + aArgs.registrarId()); // creates parent channel + if (NS_FAILED(rv)) { + return IPC_OK(); + } + } + mRedirectChannel = newChannel; + mStreamFilterEndpoints = std::move(aEndpoints); + + rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, + aArgs.redirectFlags(), + GetMainThreadSerialEventTarget()); + + if (NS_SUCCEEDED(rv)) { + scopeExit.release(); + } + + // scopeExit will call CrossProcessRedirectFinished(rv) here + return IPC_OK(); +} + +IPCResult DocumentChannelChild::RecvUpgradeObjectLoad( + UpgradeObjectLoadResolver&& aResolve) { + // We're doing a load for an <object> or <embed> element if we got here. + MOZ_ASSERT(mLoadFlags & nsIRequest::LOAD_HTML_OBJECT_DATA, + "Should have LOAD_HTML_OBJECT_DATA set"); + MOZ_ASSERT(!(mLoadFlags & nsIChannel::LOAD_DOCUMENT_URI), + "Shouldn't be a LOAD_DOCUMENT_URI load yet"); + MOZ_ASSERT(mLoadInfo->GetExternalContentPolicyType() == + ExtContentPolicy::TYPE_OBJECT, + "Should have the TYPE_OBJECT content policy type"); + + // If our load has already failed, or been cancelled, abort this attempt to + // upgade the load. + if (NS_FAILED(mStatus)) { + aResolve(nullptr); + return IPC_OK(); + } + + nsCOMPtr<nsIObjectLoadingContent> loadingContent; + NS_QueryNotificationCallbacks(this, loadingContent); + if (!loadingContent) { + return IPC_FAIL(this, "Channel is not for ObjectLoadingContent!"); + } + + // We're upgrading to a document channel now. Add the LOAD_DOCUMENT_URI flag + // after-the-fact. + mLoadFlags |= nsIChannel::LOAD_DOCUMENT_URI; + + RefPtr<BrowsingContext> browsingContext; + nsresult rv = loadingContent->UpgradeLoadToDocument( + this, getter_AddRefs(browsingContext)); + if (NS_FAILED(rv) || !browsingContext) { + // Oops! Looks like something went wrong, so let's bail out. + mLoadFlags &= ~nsIChannel::LOAD_DOCUMENT_URI; + aResolve(nullptr); + return IPC_OK(); + } + + aResolve(browsingContext); + return IPC_OK(); +} + +NS_IMETHODIMP +DocumentChannelChild::OnRedirectVerifyCallback(nsresult aStatusCode) { + LOG( + ("DocumentChannelChild OnRedirectVerifyCallback [this=%p, " + "aRv=0x%08" PRIx32 " ]", + this, static_cast<uint32_t>(aStatusCode))); + nsCOMPtr<nsIChannel> redirectChannel = std::move(mRedirectChannel); + RedirectToRealChannelResolver redirectResolver = std::move(mRedirectResolver); + + // If we've already shut down, then just notify the parent that + // we're done. + if (NS_FAILED(mStatus)) { + redirectChannel->SetNotificationCallbacks(nullptr); + redirectResolver(aStatusCode); + return NS_OK; + } + + nsresult rv = aStatusCode; + if (NS_SUCCEEDED(rv)) { + if (nsCOMPtr<nsIChildChannel> childChannel = + do_QueryInterface(redirectChannel)) { + rv = childChannel->CompleteRedirectSetup(mListener); + } else { + rv = redirectChannel->AsyncOpen(mListener); + } + } else { + redirectChannel->SetNotificationCallbacks(nullptr); + } + + for (auto& endpoint : mStreamFilterEndpoints) { + extensions::StreamFilterParent::Attach(redirectChannel, + std::move(endpoint)); + } + + redirectResolver(rv); + + if (NS_FAILED(rv)) { + ShutdownListeners(rv); + return NS_OK; + } + + if (mLoadGroup) { + mLoadGroup->RemoveRequest(this, nullptr, NS_BINDING_REDIRECTED); + } + mCallbacks = nullptr; + mListener = nullptr; + + // This calls NeckoChild::DeallocPDocumentChannel(), which deletes |this| if + // IPDL holds the last reference. Don't rely on |this| existing after here! + if (CanSend()) { + Send__delete__(this); + } + + return NS_OK; +} + +NS_IMETHODIMP +DocumentChannelChild::Cancel(nsresult aStatusCode) { + return CancelWithReason(aStatusCode, "DocumentChannelChild::Cancel"_ns); +} + +NS_IMETHODIMP DocumentChannelChild::CancelWithReason( + nsresult aStatusCode, const nsACString& aReason) { + if (mCanceled) { + return NS_OK; + } + + mCanceled = true; + if (CanSend()) { + SendCancel(aStatusCode, aReason); + } + + ShutdownListeners(aStatusCode); + + return NS_OK; +} + +} // namespace net +} // namespace mozilla + +#undef LOG |