/* -*- 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/extensions/StreamFilterParent.h" #include "mozilla/net/HttpBaseChannel.h" #include "mozilla/net/NeckoChild.h" #include "mozilla/ScopeExit.h" #include "nsHashPropertyBag.h" #include "nsIHttpChannelInternal.h" #include "nsIObjectLoadingContent.h" #include "nsIWritablePropertyBag.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 aIsXFOError) : DocumentChannel(aLoadState, aLoadInfo, aLoadFlags, aCacheKey, aUriModified, aIsXFOError) { 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 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 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 / it's the embedder doc's BC. RefPtr loadingContext = docShell->GetBrowsingContext(); if (!loadingContext || loadingContext->IsDiscarded()) { return NS_ERROR_FAILURE; } DocumentChannelCreationArgs args; args.loadState() = mLoadState->Serialize(); args.cacheKey() = mCacheKey; args.channelId() = mChannelId; args.asyncOpenTime() = mAsyncOpenTime; Maybe ipcClientInfo; if (mInitialClientInfo.isSome()) { ipcClientInfo.emplace(mInitialClientInfo.ref().ToIPC()); } args.initialClientInfo() = ipcClientInfo; if (mTiming) { args.timing() = Some(mTiming); } switch (mLoadInfo->GetExternalContentPolicyType()) { case ExtContentPolicy::TYPE_DOCUMENT: case ExtContentPolicy::TYPE_SUBDOCUMENT: { DocumentCreationArgs docArgs; docArgs.uriModified() = mUriModified; docArgs.isXFOError() = mIsXFOError; args.elementCreationArgs() = docArgs; break; } case ExtContentPolicy::TYPE_OBJECT: { ObjectCreationArgs objectArgs; objectArgs.embedderInnerWindowId() = InnerWindowIDForExtantDoc(docShell); objectArgs.loadFlags() = mLoadFlags; objectArgs.contentPolicyType() = mLoadInfo->InternalContentPolicyType(); objectArgs.isUrgentStart() = UserActivation::IsHandlingUserInput(); args.elementCreationArgs() = 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; } gNeckoChild->SendPDocumentChannelConstructor(this, loadingContext, args); mIsPending = true; mWasOpened = true; mListener = listener; return NS_OK; } IPCResult DocumentChannelChild::RecvFailedAsyncOpen( const nsresult& aStatusCode) { ShutdownListeners(aStatusCode); return IPC_OK(); } IPCResult DocumentChannelChild::RecvDisconnectChildListeners( const nsresult& aStatus, const nsresult& aLoadGroupStatus, bool aSwitchedProcess) { // If this is a normal failure, then we want to disconnect our listeners and // notify them of the failure. If this is a process switch, then we can just // ignore it silently, and trust that the switch will shut down our docshell // and cancel us when it's ready. if (!aSwitchedProcess) { DisconnectChildListeners(aStatus, aLoadGroupStatus); } return IPC_OK(); } IPCResult DocumentChannelChild::RecvRedirectToRealChannel( RedirectToRealChannelArgs&& aArgs, nsTArray>&& 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 cspToInheritLoadingDocument; nsCOMPtr policy = mLoadState->Csp(); if (policy) { nsWeakPtr ctx = static_cast(policy.get())->GetLoadingContext(); cspToInheritLoadingDocument = do_QueryReferent(ctx); } nsCOMPtr loadInfo; MOZ_ALWAYS_SUCCEEDS(LoadInfoArgsToLoadInfo( aArgs.loadInfo(), cspToInheritLoadingDocument, getter_AddRefs(loadInfo))); mRedirectResolver = std::move(aResolve); nsCOMPtr newChannel; MOZ_ASSERT((aArgs.loadStateLoadFlags() & 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); } // 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 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 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 bag = do_QueryInterface(newChannel)) { nsHashPropertyBag::CopyFrom(bag, aArgs.properties()); } // connect parent. nsCOMPtr 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(), GetMainThreadEventTarget()); 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 or 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 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; 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(aStatusCode))); nsCOMPtr 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 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) { if (mCanceled) { return NS_OK; } mCanceled = true; if (CanSend()) { SendCancel(aStatusCode); } ShutdownListeners(aStatusCode); return NS_OK; } } // namespace net } // namespace mozilla #undef LOG