/* -*- 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 "DocumentLoadListener.h" #include "NeckoCommon.h" #include "mozilla/AntiTrackingUtils.h" #include "mozilla/DebugOnly.h" #include "mozilla/LoadInfo.h" #include "mozilla/MozPromiseInlines.h" // For MozPromise::FromDomPromise #include "mozilla/NullPrincipal.h" #include "mozilla/ResultVariant.h" #include "mozilla/ScopeExit.h" #include "mozilla/StaticPrefs_extensions.h" #include "mozilla/StaticPrefs_fission.h" #include "mozilla/StaticPrefs_security.h" #include "mozilla/dom/BrowserParent.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" #include "mozilla/dom/ProcessIsolation.h" #include "mozilla/dom/SessionHistoryEntry.h" #include "mozilla/dom/WindowGlobalParent.h" #include "mozilla/dom/ipc/IdType.h" #include "mozilla/net/CookieJarSettings.h" #include "mozilla/net/HttpChannelParent.h" #include "mozilla/net/RedirectChannelRegistrar.h" #include "nsContentSecurityUtils.h" #include "nsContentSecurityManager.h" #include "nsDocShell.h" #include "nsDocShellLoadState.h" #include "nsDocShellLoadTypes.h" #include "nsDOMNavigationTiming.h" #include "nsDSURIContentListener.h" #include "nsObjectLoadingContent.h" #include "nsOpenWindowInfo.h" #include "nsExternalHelperAppService.h" #include "nsHttpChannel.h" #include "nsIBrowser.h" #include "nsIHttpChannelInternal.h" #include "nsIStreamConverterService.h" #include "nsIViewSourceChannel.h" #include "nsImportModule.h" #include "nsIXULRuntime.h" #include "nsMimeTypes.h" #include "nsQueryObject.h" #include "nsRedirectHistoryEntry.h" #include "nsSandboxFlags.h" #include "nsSHistory.h" #include "nsStringStream.h" #include "nsURILoader.h" #include "nsWebNavigationInfo.h" #include "mozilla/dom/BrowserParent.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/nsHTTPSOnlyUtils.h" #include "mozilla/dom/ReferrerInfo.h" #include "mozilla/dom/RemoteWebProgressRequest.h" #include "mozilla/net/UrlClassifierFeatureFactory.h" #include "mozilla/ExtensionPolicyService.h" #ifdef ANDROID # include "mozilla/widget/nsWindow.h" #endif /* ANDROID */ mozilla::LazyLogModule gDocumentChannelLog("DocumentChannel"); #define LOG(fmt) MOZ_LOG(gDocumentChannelLog, mozilla::LogLevel::Verbose, fmt) extern mozilla::LazyLogModule gSHIPBFCacheLog; // Bug 136580: Limit to the number of nested content frames that can have the // same URL. This is to stop content that is recursively loading // itself. Note that "#foo" on the end of URL doesn't affect // whether it's considered identical, but "?foo" or ";foo" are // considered and compared. // Limit this to 2, like chromium does. static constexpr int kMaxSameURLContentFrames = 2; using namespace mozilla::dom; namespace mozilla { namespace net { static ContentParentId GetContentProcessId(ContentParent* aContentParent) { return aContentParent ? aContentParent->ChildID() : ContentParentId{0}; } static void SetNeedToAddURIVisit(nsIChannel* aChannel, bool aNeedToAddURIVisit) { nsCOMPtr props(do_QueryInterface(aChannel)); if (!props) { return; } props->SetPropertyAsBool(u"docshell.needToAddURIVisit"_ns, aNeedToAddURIVisit); } static auto SecurityFlagsForLoadInfo(nsDocShellLoadState* aLoadState) -> nsSecurityFlags { // TODO: Block copied from nsDocShell::DoURILoad, refactor out somewhere nsSecurityFlags securityFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL; if (aLoadState->LoadType() == LOAD_ERROR_PAGE) { securityFlags |= nsILoadInfo::SEC_LOAD_ERROR_PAGE; } if (aLoadState->PrincipalToInherit()) { bool isSrcdoc = aLoadState->HasInternalLoadFlags( nsDocShell::INTERNAL_LOAD_FLAGS_IS_SRCDOC); bool inheritAttrs = nsContentUtils::ChannelShouldInheritPrincipal( aLoadState->PrincipalToInherit(), aLoadState->URI(), true, // aInheritForAboutBlank isSrcdoc); bool isData = SchemeIsData(aLoadState->URI()); if (inheritAttrs && !isData) { securityFlags |= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL; } } return securityFlags; } // Construct a LoadInfo object to use when creating the internal channel for a // Document/SubDocument load. static auto CreateDocumentLoadInfo(CanonicalBrowsingContext* aBrowsingContext, nsDocShellLoadState* aLoadState) -> already_AddRefed { uint32_t sandboxFlags = aBrowsingContext->GetSandboxFlags(); RefPtr loadInfo; auto securityFlags = SecurityFlagsForLoadInfo(aLoadState); if (aBrowsingContext->GetParent()) { loadInfo = LoadInfo::CreateForFrame( aBrowsingContext, aLoadState->TriggeringPrincipal(), aLoadState->GetEffectiveTriggeringRemoteType(), securityFlags, sandboxFlags); } else { OriginAttributes attrs; aBrowsingContext->GetOriginAttributes(attrs); loadInfo = LoadInfo::CreateForDocument( aBrowsingContext, aLoadState->URI(), aLoadState->TriggeringPrincipal(), aLoadState->GetEffectiveTriggeringRemoteType(), attrs, securityFlags, sandboxFlags); } if (aLoadState->IsExemptFromHTTPSOnlyMode()) { uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus(); httpsOnlyStatus |= nsILoadInfo::HTTPS_ONLY_EXEMPT; loadInfo->SetHttpsOnlyStatus(httpsOnlyStatus); } loadInfo->SetTriggeringSandboxFlags(aLoadState->TriggeringSandboxFlags()); loadInfo->SetHasValidUserGestureActivation( aLoadState->HasValidUserGestureActivation()); loadInfo->SetIsMetaRefresh(aLoadState->IsMetaRefresh()); return loadInfo.forget(); } // Construct a LoadInfo object to use when creating the internal channel for an // Object/Embed load. static auto CreateObjectLoadInfo(nsDocShellLoadState* aLoadState, uint64_t aInnerWindowId, nsContentPolicyType aContentPolicyType, uint32_t aSandboxFlags) -> already_AddRefed { RefPtr wgp = WindowGlobalParent::GetByInnerWindowId(aInnerWindowId); MOZ_RELEASE_ASSERT(wgp); auto securityFlags = SecurityFlagsForLoadInfo(aLoadState); RefPtr loadInfo = LoadInfo::CreateForNonDocument( wgp, wgp->DocumentPrincipal(), aContentPolicyType, securityFlags, aSandboxFlags); loadInfo->SetHasValidUserGestureActivation( aLoadState->HasValidUserGestureActivation()); loadInfo->SetTriggeringSandboxFlags(aLoadState->TriggeringSandboxFlags()); loadInfo->SetIsMetaRefresh(aLoadState->IsMetaRefresh()); return loadInfo.forget(); } /** * An extension to nsDocumentOpenInfo that we run in the parent process, so * that we can make the decision to retarget to content handlers or the external * helper app, before we make process switching decisions. * * This modifies the behaviour of nsDocumentOpenInfo so that it can do * retargeting, but doesn't do stream conversion (but confirms that we will be * able to do so later). * * We still run nsDocumentOpenInfo in the content process, but disable * retargeting, so that it can only apply stream conversion, and then send data * to the docshell. */ class ParentProcessDocumentOpenInfo final : public nsDocumentOpenInfo, public nsIMultiPartChannelListener { public: ParentProcessDocumentOpenInfo(ParentChannelListener* aListener, uint32_t aFlags, mozilla::dom::BrowsingContext* aBrowsingContext, bool aIsDocumentLoad) : nsDocumentOpenInfo(aFlags, false), mBrowsingContext(aBrowsingContext), mListener(aListener), mIsDocumentLoad(aIsDocumentLoad) { LOG(("ParentProcessDocumentOpenInfo ctor [this=%p]", this)); } NS_DECL_ISUPPORTS_INHERITED // The default content listener is always a docshell, so this manually // implements the same checks, and if it succeeds, uses the parent // channel listener so that we forward onto DocumentLoadListener. bool TryDefaultContentListener(nsIChannel* aChannel, const nsCString& aContentType) { uint32_t canHandle = nsWebNavigationInfo::IsTypeSupported(aContentType); if (canHandle != nsIWebNavigationInfo::UNSUPPORTED) { m_targetStreamListener = mListener; nsLoadFlags loadFlags = 0; aChannel->GetLoadFlags(&loadFlags); aChannel->SetLoadFlags(loadFlags | nsIChannel::LOAD_TARGETED); return true; } return false; } bool TryDefaultContentListener(nsIChannel* aChannel) override { return TryDefaultContentListener(aChannel, mContentType); } // Generally we only support stream converters that can tell // use exactly what type they'll output. If we find one, then // we just target to our default listener directly (without // conversion), and the content process nsDocumentOpenInfo will // run and do the actual conversion. nsresult TryStreamConversion(nsIChannel* aChannel) override { // The one exception is nsUnknownDecoder, which works in the parent // (and we need to know what the content type is before we can // decide if it will be handled in the parent), so we run that here. if (mContentType.LowerCaseEqualsASCII(UNKNOWN_CONTENT_TYPE) || mContentType.IsEmpty()) { return nsDocumentOpenInfo::TryStreamConversion(aChannel); } nsresult rv; nsCOMPtr streamConvService = do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv); nsAutoCString str; rv = streamConvService->ConvertedType(mContentType, aChannel, str); NS_ENSURE_SUCCESS(rv, rv); // We only support passing data to the default content listener // (docshell), and we don't supported chaining converters. if (TryDefaultContentListener(aChannel, str)) { mContentType = str; return NS_OK; } // This is the same result as nsStreamConverterService uses when it // can't find a converter return NS_ERROR_FAILURE; } nsresult TryExternalHelperApp(nsIExternalHelperAppService* aHelperAppService, nsIChannel* aChannel) override { RefPtr listener; nsresult rv = aHelperAppService->CreateListener( mContentType, aChannel, mBrowsingContext, false, nullptr, getter_AddRefs(listener)); if (NS_SUCCEEDED(rv)) { m_targetStreamListener = listener; } return rv; } nsDocumentOpenInfo* Clone() override { mCloned = true; return new ParentProcessDocumentOpenInfo(mListener, mFlags, mBrowsingContext, mIsDocumentLoad); } nsresult OnDocumentStartRequest(nsIRequest* request) { LOG(("ParentProcessDocumentOpenInfo OnDocumentStartRequest [this=%p]", this)); nsresult rv = nsDocumentOpenInfo::OnStartRequest(request); // If we didn't find a content handler, // and we don't have a listener, then just forward to our // default listener. This happens when the channel is in // an error state, and we want to just forward that on to be // handled in the content process. if (NS_SUCCEEDED(rv) && !mUsedContentHandler && !m_targetStreamListener) { m_targetStreamListener = mListener; return m_targetStreamListener->OnStartRequest(request); } if (m_targetStreamListener != mListener) { LOG( ("ParentProcessDocumentOpenInfo targeted to non-default listener " "[this=%p]", this)); // If this is the only part, then we can immediately tell our listener // that it won't be getting any content and disconnect it. For multipart // channels we have to wait until we've handled all parts before we know. // This does mean that the content process can still Cancel() a multipart // response while the response is being handled externally, but this // matches the single-process behaviour. // If we got cloned, then we don't need to do this, as only the last link // needs to do it. // Multi-part channels are guaranteed to call OnAfterLastPart, which we // forward to the listeners, so it will handle disconnection at that // point. nsCOMPtr multiPartChannel = do_QueryInterface(request); if (!multiPartChannel && !mCloned) { DisconnectChildListeners(NS_FAILED(rv) ? rv : NS_BINDING_RETARGETED, rv); } } return rv; } nsresult OnObjectStartRequest(nsIRequest* request) { LOG(("ParentProcessDocumentOpenInfo OnObjectStartRequest [this=%p]", this)); // Just redirect to the nsObjectLoadingContent in the content process. m_targetStreamListener = mListener; return m_targetStreamListener->OnStartRequest(request); } NS_IMETHOD OnStartRequest(nsIRequest* request) override { LOG(("ParentProcessDocumentOpenInfo OnStartRequest [this=%p]", this)); if (mIsDocumentLoad) { return OnDocumentStartRequest(request); } return OnObjectStartRequest(request); } NS_IMETHOD OnAfterLastPart(nsresult aStatus) override { mListener->OnAfterLastPart(aStatus); return NS_OK; } private: virtual ~ParentProcessDocumentOpenInfo() { LOG(("ParentProcessDocumentOpenInfo dtor [this=%p]", this)); } void DisconnectChildListeners(nsresult aStatus, nsresult aLoadGroupStatus) { // Tell the DocumentLoadListener to notify the content process that it's // been entirely retargeted, and to stop waiting. // Clear mListener's pointer to the DocumentLoadListener to break the // reference cycle. RefPtr doc = do_GetInterface(ToSupports(mListener)); MOZ_ASSERT(doc); doc->DisconnectListeners(aStatus, aLoadGroupStatus); mListener->SetListenerAfterRedirect(nullptr); } RefPtr mBrowsingContext; RefPtr mListener; const bool mIsDocumentLoad; /** * Set to true if we got cloned to create a chained listener. */ bool mCloned = false; }; NS_IMPL_ADDREF_INHERITED(ParentProcessDocumentOpenInfo, nsDocumentOpenInfo) NS_IMPL_RELEASE_INHERITED(ParentProcessDocumentOpenInfo, nsDocumentOpenInfo) NS_INTERFACE_MAP_BEGIN(ParentProcessDocumentOpenInfo) NS_INTERFACE_MAP_ENTRY(nsIMultiPartChannelListener) NS_INTERFACE_MAP_END_INHERITING(nsDocumentOpenInfo) NS_IMPL_ADDREF(DocumentLoadListener) NS_IMPL_RELEASE(DocumentLoadListener) NS_INTERFACE_MAP_BEGIN(DocumentLoadListener) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInterfaceRequestor) NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) NS_INTERFACE_MAP_ENTRY(nsIStreamListener) NS_INTERFACE_MAP_ENTRY(nsIParentChannel) NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectReadyCallback) NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink) NS_INTERFACE_MAP_ENTRY(nsIMultiPartChannelListener) NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink) NS_INTERFACE_MAP_ENTRY(nsIEarlyHintObserver) NS_INTERFACE_MAP_ENTRY_CONCRETE(DocumentLoadListener) NS_INTERFACE_MAP_END DocumentLoadListener::DocumentLoadListener( CanonicalBrowsingContext* aLoadingBrowsingContext, bool aIsDocumentLoad) : mIsDocumentLoad(aIsDocumentLoad) { LOG(("DocumentLoadListener ctor [this=%p]", this)); mParentChannelListener = new ParentChannelListener(this, aLoadingBrowsingContext); } DocumentLoadListener::~DocumentLoadListener() { LOG(("DocumentLoadListener dtor [this=%p]", this)); } void DocumentLoadListener::AddURIVisit(nsIChannel* aChannel, uint32_t aLoadFlags) { if (mLoadStateLoadType == LOAD_ERROR_PAGE || mLoadStateLoadType == LOAD_BYPASS_HISTORY) { return; } nsCOMPtr uri; NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri)); nsCOMPtr previousURI; uint32_t previousFlags = 0; if (mLoadStateLoadType & nsIDocShell::LOAD_CMD_RELOAD) { previousURI = uri; } else { nsDocShell::ExtractLastVisit(aChannel, getter_AddRefs(previousURI), &previousFlags); } // Get the HTTP response code, if available. uint32_t responseStatus = 0; nsCOMPtr httpChannel = do_QueryInterface(aChannel); if (httpChannel) { Unused << httpChannel->GetResponseStatus(&responseStatus); } RefPtr browsingContext = GetDocumentBrowsingContext(); nsCOMPtr widget = browsingContext->GetParentProcessWidgetContaining(); nsDocShell::InternalAddURIVisit(uri, previousURI, previousFlags, responseStatus, browsingContext, widget, mLoadStateLoadType); } CanonicalBrowsingContext* DocumentLoadListener::GetLoadingBrowsingContext() const { return mParentChannelListener ? mParentChannelListener->GetBrowsingContext() : nullptr; } CanonicalBrowsingContext* DocumentLoadListener::GetDocumentBrowsingContext() const { return mIsDocumentLoad ? GetLoadingBrowsingContext() : nullptr; } CanonicalBrowsingContext* DocumentLoadListener::GetTopBrowsingContext() const { auto* loadingContext = GetLoadingBrowsingContext(); return loadingContext ? loadingContext->Top() : nullptr; } WindowGlobalParent* DocumentLoadListener::GetParentWindowContext() const { return mParentWindowContext; } bool CheckRecursiveLoad(CanonicalBrowsingContext* aLoadingContext, nsDocShellLoadState* aLoadState, bool aIsDocumentLoad) { // Bug 136580: Check for recursive frame loading excluding about:srcdoc URIs. // srcdoc URIs require their contents to be specified inline, so it isn't // possible for undesirable recursion to occur without the aid of a // non-srcdoc URI, which this method will block normally. // Besides, URI is not enough to guarantee uniqueness of srcdoc documents. nsAutoCString buffer; if (aLoadState->URI()->SchemeIs("about")) { nsresult rv = aLoadState->URI()->GetPathQueryRef(buffer); if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("srcdoc")) { // Duplicates allowed up to depth limits return true; } } RefPtr parent; if (!aIsDocumentLoad) { // object load parent = aLoadingContext->GetCurrentWindowGlobal(); } else { parent = aLoadingContext->GetParentWindowContext(); } int matchCount = 0; CanonicalBrowsingContext* ancestorBC; for (WindowGlobalParent* ancestorWGP = parent; ancestorWGP; ancestorWGP = ancestorBC->GetParentWindowContext()) { ancestorBC = ancestorWGP->BrowsingContext(); MOZ_ASSERT(ancestorBC); if (nsCOMPtr parentURI = ancestorWGP->GetDocumentURI()) { bool equal; nsresult rv = aLoadState->URI()->EqualsExceptRef(parentURI, &equal); NS_ENSURE_SUCCESS(rv, false); if (equal) { matchCount++; if (matchCount >= kMaxSameURLContentFrames) { NS_WARNING( "Too many nested content frames/objects have the same url " "(recursion?) " "so giving up"); return false; } } } } return true; } // Check that the load state, potentially received from a child process, appears // to be performing a load of the specified LoadingSessionHistoryInfo. // Returns a Result<…> containing the SessionHistoryEntry found for the // LoadingSessionHistoryInfo as success value if the validation succeeded, or a // static (telemetry-safe) string naming what did not match as a failure value // if the validation failed. static Result ValidateHistoryLoad( CanonicalBrowsingContext* aLoadingContext, nsDocShellLoadState* aLoadState) { MOZ_ASSERT(SessionHistoryInParent()); MOZ_ASSERT(aLoadState->LoadIsFromSessionHistory()); if (!aLoadState->GetLoadingSessionHistoryInfo()) { return Err("Missing LoadingSessionHistoryInfo"); } SessionHistoryEntry::LoadingEntry* loading = SessionHistoryEntry::GetByLoadId( aLoadState->GetLoadingSessionHistoryInfo()->mLoadId); if (!loading) { return Err("Missing SessionHistoryEntry"); } SessionHistoryInfo* snapshot = loading->mInfoSnapshotForValidation.get(); // History loads do not inherit principal. if (aLoadState->HasInternalLoadFlags( nsDocShell::INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL)) { return Err("LOAD_FLAGS_INHERIT_PRINCIPAL"); } auto uriEq = [](nsIURI* a, nsIURI* b) -> bool { bool eq = false; return a == b || (a && b && NS_SUCCEEDED(a->Equals(b, &eq)) && eq); }; auto principalEq = [](nsIPrincipal* a, nsIPrincipal* b) -> bool { return a == b || (a && b && a->Equals(b)); }; // XXX: Needing to do all of this validation manually is kinda gross. if (!uriEq(snapshot->GetURI(), aLoadState->URI())) { return Err("URI"); } if (!uriEq(snapshot->GetOriginalURI(), aLoadState->OriginalURI())) { return Err("OriginalURI"); } if (!aLoadState->ResultPrincipalURIIsSome() || !uriEq(snapshot->GetResultPrincipalURI(), aLoadState->ResultPrincipalURI())) { return Err("ResultPrincipalURI"); } if (!uriEq(snapshot->GetUnstrippedURI(), aLoadState->GetUnstrippedURI())) { return Err("UnstrippedURI"); } if (!principalEq(snapshot->GetTriggeringPrincipal(), aLoadState->TriggeringPrincipal())) { return Err("TriggeringPrincipal"); } if (!principalEq(snapshot->GetPrincipalToInherit(), aLoadState->PrincipalToInherit())) { return Err("PrincipalToInherit"); } if (!principalEq(snapshot->GetPartitionedPrincipalToInherit(), aLoadState->PartitionedPrincipalToInherit())) { return Err("PartitionedPrincipalToInherit"); } // Everything matches! return loading->mEntry; } auto DocumentLoadListener::Open(nsDocShellLoadState* aLoadState, LoadInfo* aLoadInfo, nsLoadFlags aLoadFlags, uint32_t aCacheKey, const Maybe& aChannelId, const TimeStamp& aAsyncOpenTime, nsDOMNavigationTiming* aTiming, Maybe&& aInfo, bool aUrgentStart, dom::ContentParent* aContentParent, nsresult* aRv) -> RefPtr { auto* loadingContext = GetLoadingBrowsingContext(); MOZ_DIAGNOSTIC_ASSERT_IF(loadingContext->GetParent(), loadingContext->GetParentWindowContext()); OriginAttributes attrs; loadingContext->GetOriginAttributes(attrs); mLoadIdentifier = aLoadState->GetLoadIdentifier(); // See description of mFileName in nsDocShellLoadState.h mIsDownload = !aLoadState->FileName().IsVoid(); mIsLoadingJSURI = net::SchemeIsJavascript(aLoadState->URI()); // Check for infinite recursive object or iframe loads if (aLoadState->OriginalFrameSrc() || !mIsDocumentLoad) { if (!CheckRecursiveLoad(loadingContext, aLoadState, mIsDocumentLoad)) { *aRv = NS_ERROR_RECURSIVE_DOCUMENT_LOAD; mParentChannelListener = nullptr; return nullptr; } } auto* documentContext = GetDocumentBrowsingContext(); // If we are using SHIP and this load is from session history, validate that // the load matches our local copy of the loading history entry. // // NOTE: Keep this check in-sync with the check in // `nsDocShellLoadState::GetEffectiveTriggeringRemoteType()`! RefPtr existingEntry; if (SessionHistoryInParent() && aLoadState->LoadIsFromSessionHistory() && aLoadState->LoadType() != LOAD_ERROR_PAGE) { Result result = ValidateHistoryLoad(loadingContext, aLoadState); if (result.isErr()) { const char* mismatch = result.unwrapErr(); LOG( ("DocumentLoadListener::Open with invalid loading history entry " "[this=%p, mismatch=%s]", this, mismatch)); #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED MOZ_CRASH_UNSAFE_PRINTF( "DocumentLoadListener::Open for invalid history entry due to " "mismatch of '%s'", mismatch); #endif *aRv = NS_ERROR_DOM_SECURITY_ERR; mParentChannelListener = nullptr; return nullptr; } existingEntry = result.unwrap(); if (!existingEntry->IsInSessionHistory() && !documentContext->HasLoadingHistoryEntry(existingEntry)) { SessionHistoryEntry::RemoveLoadId( aLoadState->GetLoadingSessionHistoryInfo()->mLoadId); LOG( ("DocumentLoadListener::Open with disconnected history entry " "[this=%p]", this)); *aRv = NS_BINDING_ABORTED; mParentChannelListener = nullptr; mChannel = nullptr; return nullptr; } } if (aLoadState->GetRemoteTypeOverride()) { if (!mIsDocumentLoad || !NS_IsAboutBlank(aLoadState->URI()) || !loadingContext->IsTopContent()) { LOG( ("DocumentLoadListener::Open with invalid remoteTypeOverride " "[this=%p]", this)); *aRv = NS_ERROR_DOM_SECURITY_ERR; mParentChannelListener = nullptr; return nullptr; } mRemoteTypeOverride = aLoadState->GetRemoteTypeOverride(); } if (NS_WARN_IF(!loadingContext->IsOwnedByProcess( GetContentProcessId(aContentParent)))) { LOG( ("DocumentLoadListener::Open called from non-current content process " "[this=%p, current=%" PRIu64 ", caller=%" PRIu64 "]", this, loadingContext->OwnerProcessId(), uint64_t(GetContentProcessId(aContentParent)))); *aRv = NS_BINDING_ABORTED; mParentChannelListener = nullptr; return nullptr; } if (mIsDocumentLoad && loadingContext->IsContent() && NS_WARN_IF(loadingContext->IsReplaced())) { LOG( ("DocumentLoadListener::Open called from replaced BrowsingContext " "[this=%p, browserid=%" PRIx64 ", bcid=%" PRIx64 "]", this, loadingContext->BrowserId(), loadingContext->Id())); *aRv = NS_BINDING_ABORTED; mParentChannelListener = nullptr; return nullptr; } if (!nsDocShell::CreateAndConfigureRealChannelForLoadState( loadingContext, aLoadState, aLoadInfo, mParentChannelListener, nullptr, attrs, aLoadFlags, aCacheKey, *aRv, getter_AddRefs(mChannel))) { LOG(("DocumentLoadListener::Open failed to create channel [this=%p]", this)); mParentChannelListener = nullptr; return nullptr; } if (documentContext && aLoadState->LoadType() != LOAD_ERROR_PAGE && mozilla::SessionHistoryInParent()) { // It's hard to know at this point whether session history will be enabled // in the browsing context, so we always create an entry for a load here. mLoadingSessionHistoryInfo = documentContext->CreateLoadingSessionHistoryEntryForLoad( aLoadState, existingEntry, mChannel); MOZ_ASSERT(mLoadingSessionHistoryInfo); } nsCOMPtr uriBeingLoaded; Unused << NS_WARN_IF( NS_FAILED(mChannel->GetURI(getter_AddRefs(uriBeingLoaded)))); RefPtr httpBaseChannel = do_QueryObject(mChannel, aRv); if (uriBeingLoaded && httpBaseChannel) { nsCOMPtr topWindowURI; if (mIsDocumentLoad && loadingContext->IsTop()) { // If this is for the top level loading, the top window URI should be the // URI which we are loading. topWindowURI = uriBeingLoaded; } else if (RefPtr topWindow = AntiTrackingUtils:: GetTopWindowExcludingExtensionAccessibleContentFrames( loadingContext, uriBeingLoaded)) { nsCOMPtr topWindowPrincipal = topWindow->DocumentPrincipal(); if (topWindowPrincipal && !topWindowPrincipal->GetIsNullPrincipal()) { auto* basePrin = BasePrincipal::Cast(topWindowPrincipal); basePrin->GetURI(getter_AddRefs(topWindowURI)); } } httpBaseChannel->SetTopWindowURI(topWindowURI); } nsCOMPtr identChannel = do_QueryInterface(mChannel); if (identChannel && aChannelId) { Unused << identChannel->SetChannelId(*aChannelId); } mDocumentChannelId = aChannelId; RefPtr httpChannelImpl = do_QueryObject(mChannel); if (httpChannelImpl) { httpChannelImpl->SetWarningReporter(this); if (mIsDocumentLoad && loadingContext->IsTop()) { httpChannelImpl->SetEarlyHintObserver(this); } } nsCOMPtr timedChannel = do_QueryInterface(mChannel); if (timedChannel) { timedChannel->SetAsyncOpen(aAsyncOpenTime); } if (nsCOMPtr httpChannel = do_QueryInterface(mChannel)) { Unused << httpChannel->SetRequestContextID( loadingContext->GetRequestContextId()); nsCOMPtr cos(do_QueryInterface(httpChannel)); if (cos && aUrgentStart) { cos->AddClassFlags(nsIClassOfService::UrgentStart); } } // Setup a ClientChannelHelper to watch for redirects, and copy // across any serviceworker related data between channels as needed. AddClientChannelHelperInParent(mChannel, std::move(aInfo)); if (documentContext && !documentContext->StartDocumentLoad(this)) { LOG(("DocumentLoadListener::Open failed StartDocumentLoad [this=%p]", this)); *aRv = NS_BINDING_ABORTED; mParentChannelListener = nullptr; mChannel = nullptr; return nullptr; } // Recalculate the openFlags, matching the logic in use in Content process. // NOTE: The only case not handled here to mirror Content process is // redirecting to re-use the channel. MOZ_ASSERT(!aLoadState->GetPendingRedirectedChannel()); uint32_t openFlags = nsDocShell::ComputeURILoaderFlags(loadingContext, aLoadState->LoadType()); RefPtr openInfo = new ParentProcessDocumentOpenInfo(mParentChannelListener, openFlags, loadingContext, mIsDocumentLoad); openInfo->Prepare(); #ifdef ANDROID RefPtr> promise; if (documentContext && aLoadState->LoadType() != LOAD_ERROR_PAGE && !(aLoadState->HasInternalLoadFlags( nsDocShell::INTERNAL_LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE)) && !(aLoadState->LoadType() & LOAD_HISTORY)) { nsCOMPtr widget = documentContext->GetParentProcessWidgetContaining(); RefPtr window = nsWindow::From(widget); if (window) { promise = window->OnLoadRequest( aLoadState->URI(), nsIBrowserDOMWindow::OPEN_CURRENTWINDOW, aLoadState->InternalLoadFlags(), aLoadState->TriggeringPrincipal(), aLoadState->HasValidUserGestureActivation(), documentContext->IsTopContent()); } } if (promise) { RefPtr self = this; promise->Then( GetCurrentSerialEventTarget(), __func__, [=](const MozPromise::ResolveOrRejectValue& aValue) { if (aValue.IsResolve()) { bool handled = aValue.ResolveValue(); if (handled) { self->DisconnectListeners(NS_ERROR_ABORT, NS_ERROR_ABORT); mParentChannelListener = nullptr; } else { nsresult rv = mChannel->AsyncOpen(openInfo); if (NS_FAILED(rv)) { self->DisconnectListeners(rv, rv); mParentChannelListener = nullptr; } } } }); } else #endif /* ANDROID */ { *aRv = mChannel->AsyncOpen(openInfo); if (NS_FAILED(*aRv)) { LOG(("DocumentLoadListener::Open failed AsyncOpen [this=%p rv=%" PRIx32 "]", this, static_cast(*aRv))); if (documentContext) { documentContext->EndDocumentLoad(false); } mParentChannelListener = nullptr; return nullptr; } } // HTTPS-Only Mode fights potential timeouts caused by upgrades. Instantly // after opening the document channel we have to kick off countermeasures. nsHTTPSOnlyUtils::PotentiallyFireHttpRequestToShortenTimout(this); nsCOMPtr loadInfo = mChannel->LoadInfo(); loadInfo->SetChannelCreationOriginalURI(aLoadState->URI()); mContentParent = aContentParent; mLoadStateExternalLoadFlags = aLoadState->LoadFlags(); mLoadStateInternalLoadFlags = aLoadState->InternalLoadFlags(); mLoadStateLoadType = aLoadState->LoadType(); mTiming = aTiming; mSrcdocData = aLoadState->SrcdocData(); mBaseURI = aLoadState->BaseURI(); mOriginalUriString = aLoadState->GetOriginalURIString(); if (documentContext) { mParentWindowContext = documentContext->GetParentWindowContext(); } else { mParentWindowContext = WindowGlobalParent::GetByInnerWindowId(aLoadInfo->GetInnerWindowID()); MOZ_RELEASE_ASSERT(mParentWindowContext->GetBrowsingContext() == GetLoadingBrowsingContext(), "mismatched parent window context?"); } // For content-initiated loads, this flag is set in nsDocShell::LoadURI. // For parent-initiated loads, we have to set it here. // Below comment is copied from nsDocShell::LoadURI - // If we have a system triggering principal, we can assume that this load was // triggered by some UI in the browser chrome, such as the URL bar or // bookmark bar. This should count as a user interaction for the current sh // entry, so that the user may navigate back to the current entry, from the // entry that is going to be added as part of this load. if (!mSupportsRedirectToRealChannel && aLoadState->TriggeringPrincipal() && aLoadState->TriggeringPrincipal()->IsSystemPrincipal()) { WindowContext* topWc = loadingContext->GetTopWindowContext(); if (topWc && !topWc->IsDiscarded()) { MOZ_ALWAYS_SUCCEEDS(topWc->SetSHEntryHasUserInteraction(true)); } } *aRv = NS_OK; mOpenPromise = new OpenPromise::Private(__func__); // We make the promise use direct task dispatch in order to reduce the number // of event loops iterations. mOpenPromise->UseDirectTaskDispatch(__func__); return mOpenPromise; } auto DocumentLoadListener::OpenDocument( nsDocShellLoadState* aLoadState, uint32_t aCacheKey, const Maybe& aChannelId, const TimeStamp& aAsyncOpenTime, nsDOMNavigationTiming* aTiming, Maybe&& aInfo, Maybe aUriModified, Maybe aIsXFOError, dom::ContentParent* aContentParent, nsresult* aRv) -> RefPtr { LOG(("DocumentLoadListener [%p] OpenDocument [uri=%s]", this, aLoadState->URI()->GetSpecOrDefault().get())); MOZ_ASSERT(mIsDocumentLoad); RefPtr browsingContext = GetDocumentBrowsingContext(); // If this is a top-level load, then rebuild the LoadInfo from scratch, // since the goal is to be able to initiate loads in the parent, where the // content process won't have provided us with an existing one. RefPtr loadInfo = CreateDocumentLoadInfo(browsingContext, aLoadState); nsLoadFlags loadFlags = aLoadState->CalculateChannelLoadFlags( browsingContext, std::move(aUriModified), std::move(aIsXFOError)); return Open(aLoadState, loadInfo, loadFlags, aCacheKey, aChannelId, aAsyncOpenTime, aTiming, std::move(aInfo), false, aContentParent, aRv); } auto DocumentLoadListener::OpenObject( nsDocShellLoadState* aLoadState, uint32_t aCacheKey, const Maybe& aChannelId, const TimeStamp& aAsyncOpenTime, nsDOMNavigationTiming* aTiming, Maybe&& aInfo, uint64_t aInnerWindowId, nsLoadFlags aLoadFlags, nsContentPolicyType aContentPolicyType, bool aUrgentStart, dom::ContentParent* aContentParent, ObjectUpgradeHandler* aObjectUpgradeHandler, nsresult* aRv) -> RefPtr { LOG(("DocumentLoadListener [%p] OpenObject [uri=%s]", this, aLoadState->URI()->GetSpecOrDefault().get())); MOZ_ASSERT(!mIsDocumentLoad); auto sandboxFlags = aLoadState->TriggeringSandboxFlags(); RefPtr loadInfo = CreateObjectLoadInfo( aLoadState, aInnerWindowId, aContentPolicyType, sandboxFlags); mObjectUpgradeHandler = aObjectUpgradeHandler; return Open(aLoadState, loadInfo, aLoadFlags, aCacheKey, aChannelId, aAsyncOpenTime, aTiming, std::move(aInfo), aUrgentStart, aContentParent, aRv); } auto DocumentLoadListener::OpenInParent(nsDocShellLoadState* aLoadState, bool aSupportsRedirectToRealChannel) -> RefPtr { MOZ_ASSERT(mIsDocumentLoad); // We currently only support passing nullptr for aLoadInfo for // top level browsing contexts. auto* browsingContext = GetDocumentBrowsingContext(); if (!browsingContext->IsTopContent() || !browsingContext->GetContentParent()) { LOG(("DocumentLoadListener::OpenInParent failed because of subdoc")); return nullptr; } if (nsCOMPtr csp = aLoadState->Csp()) { // Check CSP navigate-to bool allowsNavigateTo = false; nsresult rv = csp->GetAllowsNavigateTo(aLoadState->URI(), aLoadState->IsFormSubmission(), false, /* aWasRedirected */ false, /* aEnforceWhitelist */ &allowsNavigateTo); if (NS_FAILED(rv) || !allowsNavigateTo) { return nullptr; } } // Clone because this mutates the load flags in the load state, which // breaks nsDocShells expectations of being able to do it. RefPtr loadState = new nsDocShellLoadState(*aLoadState); loadState->CalculateLoadURIFlags(); RefPtr timing = new nsDOMNavigationTiming(nullptr); timing->NotifyNavigationStart( browsingContext->IsActive() ? nsDOMNavigationTiming::DocShellState::eActive : nsDOMNavigationTiming::DocShellState::eInactive); const mozilla::dom::LoadingSessionHistoryInfo* loadingInfo = loadState->GetLoadingSessionHistoryInfo(); uint32_t cacheKey = 0; auto loadType = aLoadState->LoadType(); if (loadType == LOAD_HISTORY || loadType == LOAD_RELOAD_NORMAL || loadType == LOAD_RELOAD_CHARSET_CHANGE || loadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE || loadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE) { if (loadingInfo) { cacheKey = loadingInfo->mInfo.GetCacheKey(); } } // Loads start in the content process might have exposed a channel id to // observers, so we need to preserve the value in the parent. That can't have // happened here, so Nothing() is fine. Maybe channelId = Nothing(); // Initial client info is only relevant for subdocument loads, which we're // not supporting yet. Maybe initialClientInfo; mSupportsRedirectToRealChannel = aSupportsRedirectToRealChannel; // This is a top-level load, so rebuild the LoadInfo from scratch, // since in the parent the // content process won't have provided us with an existing one. RefPtr loadInfo = CreateDocumentLoadInfo(browsingContext, aLoadState); nsLoadFlags loadFlags = loadState->CalculateChannelLoadFlags( browsingContext, Some(loadingInfo && loadingInfo->mInfo.GetURIWasModified()), Nothing()); nsresult rv; return Open(loadState, loadInfo, loadFlags, cacheKey, channelId, TimeStamp::Now(), timing, std::move(initialClientInfo), false, browsingContext->GetContentParent(), &rv); } base::ProcessId DocumentLoadListener::OtherPid() const { return mContentParent ? mContentParent->OtherPid() : base::ProcessId{0}; } void DocumentLoadListener::FireStateChange(uint32_t aStateFlags, nsresult aStatus) { nsCOMPtr request = GetChannel(); RefPtr webProgress = GetLoadingBrowsingContext()->GetWebProgress(); if (webProgress) { NS_DispatchToMainThread( NS_NewRunnableFunction("DocumentLoadListener::FireStateChange", [=]() { webProgress->OnStateChange(webProgress, request, aStateFlags, aStatus); })); } } static void SetNavigating(CanonicalBrowsingContext* aBrowsingContext, bool aNavigating) { nsCOMPtr browser; if (RefPtr currentElement = aBrowsingContext->GetEmbedderElement()) { browser = currentElement->AsBrowser(); } if (!browser) { return; } NS_DispatchToMainThread(NS_NewRunnableFunction( "DocumentLoadListener::SetNavigating", [browser, aNavigating]() { browser->SetIsNavigating(aNavigating); })); } /* static */ bool DocumentLoadListener::LoadInParent( CanonicalBrowsingContext* aBrowsingContext, nsDocShellLoadState* aLoadState, bool aSetNavigating) { SetNavigating(aBrowsingContext, aSetNavigating); RefPtr load = new DocumentLoadListener(aBrowsingContext, true); RefPtr promise = load->OpenInParent( aLoadState, /* aSupportsRedirectToRealChannel */ false); if (!promise) { SetNavigating(aBrowsingContext, false); return false; } // We passed false for aSupportsRedirectToRealChannel, so we should always // take the process switching path, and reject this promise. promise->Then( GetCurrentSerialEventTarget(), __func__, [load](DocumentLoadListener::OpenPromise::ResolveOrRejectValue&& aValue) { MOZ_ASSERT(aValue.IsReject()); DocumentLoadListener::OpenPromiseFailedType& rejectValue = aValue.RejectValue(); if (!rejectValue.mContinueNavigating) { // If we're not switching the load to a new process, then it is // finished (and failed), and we should fire a state change to notify // observers. Normally the docshell would fire this, and it would get // filtered out by BrowserParent if needed. load->FireStateChange(nsIWebProgressListener::STATE_STOP | nsIWebProgressListener::STATE_IS_WINDOW | nsIWebProgressListener::STATE_IS_NETWORK, rejectValue.mStatus); } }); load->FireStateChange(nsIWebProgressListener::STATE_START | nsIWebProgressListener::STATE_IS_DOCUMENT | nsIWebProgressListener::STATE_IS_REQUEST | nsIWebProgressListener::STATE_IS_WINDOW | nsIWebProgressListener::STATE_IS_NETWORK, NS_OK); SetNavigating(aBrowsingContext, false); return true; } /* static */ bool DocumentLoadListener::SpeculativeLoadInParent( dom::CanonicalBrowsingContext* aBrowsingContext, nsDocShellLoadState* aLoadState) { LOG(("DocumentLoadListener::OpenFromParent")); RefPtr listener = new DocumentLoadListener(aBrowsingContext, true); auto promise = listener->OpenInParent(aLoadState, true); if (promise) { // Create an entry in the redirect channel registrar to // allocate an identifier for this load. nsCOMPtr registrar = RedirectChannelRegistrar::GetOrCreate(); uint64_t loadIdentifier = aLoadState->GetLoadIdentifier(); DebugOnly rv = registrar->RegisterChannel(nullptr, loadIdentifier); MOZ_ASSERT(NS_SUCCEEDED(rv)); // Register listener (as an nsIParentChannel) under our new identifier. rv = registrar->LinkChannels(loadIdentifier, listener, nullptr); MOZ_ASSERT(NS_SUCCEEDED(rv)); } return !!promise; } void DocumentLoadListener::CleanupParentLoadAttempt(uint64_t aLoadIdent) { nsCOMPtr registrar = RedirectChannelRegistrar::GetOrCreate(); nsCOMPtr parentChannel; registrar->GetParentChannel(aLoadIdent, getter_AddRefs(parentChannel)); RefPtr loadListener = do_QueryObject(parentChannel); if (loadListener) { // If the load listener is still registered, then we must have failed // to connect DocumentChannel into it. Better cancel it! loadListener->NotifyDocumentChannelFailed(); } registrar->DeregisterChannels(aLoadIdent); } auto DocumentLoadListener::ClaimParentLoad(DocumentLoadListener** aListener, uint64_t aLoadIdent, Maybe aChannelId) -> RefPtr { nsCOMPtr registrar = RedirectChannelRegistrar::GetOrCreate(); nsCOMPtr parentChannel; registrar->GetParentChannel(aLoadIdent, getter_AddRefs(parentChannel)); RefPtr loadListener = do_QueryObject(parentChannel); registrar->DeregisterChannels(aLoadIdent); if (!loadListener) { // The parent went away unexpectedly. *aListener = nullptr; return nullptr; } loadListener->mDocumentChannelId = aChannelId; MOZ_DIAGNOSTIC_ASSERT(loadListener->mOpenPromise); loadListener.forget(aListener); return (*aListener)->mOpenPromise; } void DocumentLoadListener::NotifyDocumentChannelFailed() { LOG(("DocumentLoadListener NotifyDocumentChannelFailed [this=%p]", this)); // There's been no calls to ClaimParentLoad, and so no listeners have been // attached to mOpenPromise yet. As such we can run Then() on it. mOpenPromise->Then( GetMainThreadSerialEventTarget(), __func__, [](DocumentLoadListener::OpenPromiseSucceededType&& aResolveValue) { aResolveValue.mPromise->Resolve(NS_BINDING_ABORTED, __func__); }, []() {}); Cancel(NS_BINDING_ABORTED, "DocumentLoadListener::NotifyDocumentChannelFailed"_ns); } void DocumentLoadListener::Disconnect(bool aContinueNavigating) { LOG(("DocumentLoadListener Disconnect [this=%p, aContinueNavigating=%d]", this, aContinueNavigating)); // The nsHttpChannel may have a reference to this parent, release it // to avoid circular references. RefPtr httpChannelImpl = do_QueryObject(mChannel); if (httpChannelImpl) { httpChannelImpl->SetWarningReporter(nullptr); httpChannelImpl->SetEarlyHintObserver(nullptr); } // Don't cancel ongoing early hints when continuing to load the web page. // Early hints are loaded earlier in the code and shouldn't get cancelled // here. See also: Bug 1765652 if (!aContinueNavigating) { mEarlyHintsService.Cancel("DocumentLoadListener::Disconnect"_ns); } if (auto* ctx = GetDocumentBrowsingContext()) { ctx->EndDocumentLoad(aContinueNavigating); } } void DocumentLoadListener::Cancel(const nsresult& aStatusCode, const nsACString& aReason) { LOG( ("DocumentLoadListener Cancel [this=%p, " "aStatusCode=%" PRIx32 " ]", this, static_cast(aStatusCode))); if (mOpenPromiseResolved) { return; } if (mChannel) { mChannel->CancelWithReason(aStatusCode, aReason); } DisconnectListeners(aStatusCode, aStatusCode); } void DocumentLoadListener::DisconnectListeners(nsresult aStatus, nsresult aLoadGroupStatus, bool aContinueNavigating) { LOG( ("DocumentLoadListener DisconnectListener [this=%p, " "aStatus=%" PRIx32 ", aLoadGroupStatus=%" PRIx32 ", aContinueNavigating=%d]", this, static_cast(aStatus), static_cast(aLoadGroupStatus), aContinueNavigating)); RejectOpenPromise(aStatus, aLoadGroupStatus, aContinueNavigating, __func__); Disconnect(aContinueNavigating); // Clear any pending stream filter requests. If we're going to be sending a // response to the content process due to a navigation, our caller will have // already stashed the array to be passed to `TriggerRedirectToRealChannel`, // so it's safe for us to clear here. // TODO: If we retargeted the stream to a non-default handler (e.g. to trigger // a download), we currently never attach a stream filter. Should we attach a // stream filter in those situations as well? mStreamFilterRequests.Clear(); } void DocumentLoadListener::RedirectToRealChannelFinished(nsresult aRv) { LOG( ("DocumentLoadListener RedirectToRealChannelFinished [this=%p, " "aRv=%" PRIx32 " ]", this, static_cast(aRv))); if (NS_FAILED(aRv)) { FinishReplacementChannelSetup(aRv); return; } // Wait for background channel ready on target channel nsCOMPtr redirectReg = RedirectChannelRegistrar::GetOrCreate(); MOZ_ASSERT(redirectReg); nsCOMPtr redirectParentChannel; redirectReg->GetParentChannel(mRedirectChannelId, getter_AddRefs(redirectParentChannel)); if (!redirectParentChannel) { FinishReplacementChannelSetup(NS_ERROR_FAILURE); return; } nsCOMPtr redirectingParent = do_QueryInterface(redirectParentChannel); if (!redirectingParent) { // Continue verification procedure if redirecting to non-Http protocol FinishReplacementChannelSetup(NS_OK); return; } // Ask redirected channel if verification can proceed. // ReadyToVerify will be invoked when redirected channel is ready. redirectingParent->ContinueVerification(this); } NS_IMETHODIMP DocumentLoadListener::ReadyToVerify(nsresult aResultCode) { FinishReplacementChannelSetup(aResultCode); return NS_OK; } void DocumentLoadListener::FinishReplacementChannelSetup(nsresult aResult) { LOG( ("DocumentLoadListener FinishReplacementChannelSetup [this=%p, " "aResult=%x]", this, int(aResult))); auto endDocumentLoad = MakeScopeExit([&]() { if (auto* ctx = GetDocumentBrowsingContext()) { ctx->EndDocumentLoad(false); } }); mStreamFilterRequests.Clear(); nsCOMPtr registrar = RedirectChannelRegistrar::GetOrCreate(); MOZ_ASSERT(registrar); nsCOMPtr redirectChannel; nsresult rv = registrar->GetParentChannel(mRedirectChannelId, getter_AddRefs(redirectChannel)); if (NS_FAILED(rv) || !redirectChannel) { aResult = NS_ERROR_FAILURE; } // Release all previously registered channels, they are no longer needed to // be kept in the registrar from this moment. registrar->DeregisterChannels(mRedirectChannelId); mRedirectChannelId = 0; if (NS_FAILED(aResult)) { if (redirectChannel) { redirectChannel->Delete(); } mChannel->Cancel(aResult); mChannel->Resume(); return; } MOZ_ASSERT( !SameCOMIdentity(redirectChannel, static_cast(this))); redirectChannel->SetParentListener(mParentChannelListener); ApplyPendingFunctions(redirectChannel); if (!ResumeSuspendedChannel(redirectChannel)) { nsCOMPtr loadGroup; mChannel->GetLoadGroup(getter_AddRefs(loadGroup)); if (loadGroup) { // We added ourselves to the load group, but attempting // to resume has notified us that the channel is already // finished. Better remove ourselves from the loadgroup // again. The only time the channel will be in a loadgroup // is if we're connected to the parent process. nsresult status = NS_OK; mChannel->GetStatus(&status); loadGroup->RemoveRequest(mChannel, nullptr, status); } } } void DocumentLoadListener::ApplyPendingFunctions( nsIParentChannel* aChannel) const { // We stored the values from all nsIParentChannel functions called since we // couldn't handle them. Copy them across to the real channel since it // should know what to do. nsCOMPtr parentChannel = aChannel; for (const auto& variant : mIParentChannelFunctions) { variant.match( [parentChannel](const ClassifierMatchedInfoParams& aParams) { parentChannel->SetClassifierMatchedInfo( aParams.mList, aParams.mProvider, aParams.mFullHash); }, [parentChannel](const ClassifierMatchedTrackingInfoParams& aParams) { parentChannel->SetClassifierMatchedTrackingInfo(aParams.mLists, aParams.mFullHashes); }, [parentChannel](const ClassificationFlagsParams& aParams) { parentChannel->NotifyClassificationFlags(aParams.mClassificationFlags, aParams.mIsThirdParty); }); } RefPtr reporter; if (RefPtr httpParent = do_QueryObject(aChannel)) { reporter = httpParent; } else if (RefPtr httpChannel = do_QueryObject(aChannel)) { reporter = httpChannel->GetWarningReporter(); } if (reporter) { for (const auto& variant : mSecurityWarningFunctions) { variant.match( [reporter](const ReportSecurityMessageParams& aParams) { Unused << reporter->ReportSecurityMessage(aParams.mMessageTag, aParams.mMessageCategory); }, [reporter](const LogBlockedCORSRequestParams& aParams) { Unused << reporter->LogBlockedCORSRequest( aParams.mMessage, aParams.mCategory, aParams.mIsWarning); }, [reporter](const LogMimeTypeMismatchParams& aParams) { Unused << reporter->LogMimeTypeMismatch( aParams.mMessageName, aParams.mWarning, aParams.mURL, aParams.mContentType); }); } } } bool DocumentLoadListener::ResumeSuspendedChannel( nsIStreamListener* aListener) { LOG(("DocumentLoadListener ResumeSuspendedChannel [this=%p]", this)); RefPtr httpChannel = do_QueryObject(mChannel); if (httpChannel) { httpChannel->SetApplyConversion(mOldApplyConversion); } if (!mIsFinished) { mParentChannelListener->SetListenerAfterRedirect(aListener); } // If we failed to suspend the channel, then we might have received // some messages while the redirected was being handled. // Manually send them on now. nsTArray streamListenerFunctions = std::move(mStreamListenerFunctions); if (!aListener) { streamListenerFunctions.Clear(); } ForwardStreamListenerFunctions(streamListenerFunctions, aListener); // We don't expect to get new stream listener functions added // via re-entrancy. If this ever happens, we should understand // exactly why before allowing it. NS_ASSERTION(mStreamListenerFunctions.IsEmpty(), "Should not have added new stream listener function!"); mChannel->Resume(); // Our caller will invoke `EndDocumentLoad` for us. return !mIsFinished; } void DocumentLoadListener::CancelEarlyHintPreloads() { mEarlyHintsService.Cancel("DocumentLoadListener::CancelEarlyHintPreloads"_ns); } void DocumentLoadListener::RegisterEarlyHintLinksAndGetConnectArgs( dom::ContentParentId aCpId, nsTArray& aOutLinks) { mEarlyHintsService.RegisterLinksAndGetConnectArgs(aCpId, aOutLinks); } void DocumentLoadListener::SerializeRedirectData( RedirectToRealChannelArgs& aArgs, bool aIsCrossProcess, uint32_t aRedirectFlags, uint32_t aLoadFlags, ContentParent* aParent, nsTArray&& aEarlyHints, uint32_t aEarlyHintLinkType) const { aArgs.uri() = GetChannelCreationURI(); aArgs.loadIdentifier() = mLoadIdentifier; aArgs.earlyHints() = std::move(aEarlyHints); aArgs.earlyHintLinkType() = aEarlyHintLinkType; // I previously used HttpBaseChannel::CloneLoadInfoForRedirect, but that // clears the principal to inherit, which fails tests (probably because this // 'redirect' is usually just an implementation detail). It's also http // only, and mChannel can be anything that we redirected to. nsCOMPtr channelLoadInfo = mChannel->LoadInfo(); nsCOMPtr principalToInherit; channelLoadInfo->GetPrincipalToInherit(getter_AddRefs(principalToInherit)); const RefPtr baseChannel = do_QueryObject(mChannel); nsCOMPtr loadContext; NS_QueryNotificationCallbacks(mChannel, loadContext); nsCOMPtr redirectLoadInfo; // Only use CloneLoadInfoForRedirect if we have a load context, // since it internally tries to pull OriginAttributes from the // the load context and asserts if they don't match the load info. // We can end up without a load context if the channel has been aborted // and the callbacks have been cleared. if (baseChannel && loadContext) { redirectLoadInfo = baseChannel->CloneLoadInfoForRedirect( aArgs.uri(), nsIChannelEventSink::REDIRECT_INTERNAL); redirectLoadInfo->SetResultPrincipalURI(aArgs.uri()); // The clone process clears this, and then we fail tests.. // docshell/test/mochitest/test_forceinheritprincipal_overrule_owner.html if (principalToInherit) { redirectLoadInfo->SetPrincipalToInherit(principalToInherit); } } else { redirectLoadInfo = static_cast(channelLoadInfo.get())->Clone(); redirectLoadInfo->AppendRedirectHistoryEntry(mChannel, true); } const Maybe& reservedClientInfo = channelLoadInfo->GetReservedClientInfo(); if (reservedClientInfo) { redirectLoadInfo->SetReservedClientInfo(*reservedClientInfo); } aArgs.registrarId() = mRedirectChannelId; MOZ_ALWAYS_SUCCEEDS( ipc::LoadInfoToLoadInfoArgs(redirectLoadInfo, &aArgs.loadInfo())); mChannel->GetOriginalURI(getter_AddRefs(aArgs.originalURI())); // mChannel can be a nsHttpChannel as well as InterceptedHttpChannel so we // can't use baseChannel here. if (nsCOMPtr httpChannel = do_QueryInterface(mChannel)) { MOZ_ALWAYS_SUCCEEDS(httpChannel->GetChannelId(&aArgs.channelId())); } aArgs.redirectMode() = nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW; nsCOMPtr httpChannelInternal = do_QueryInterface(mChannel); if (httpChannelInternal) { MOZ_ALWAYS_SUCCEEDS( httpChannelInternal->GetRedirectMode(&aArgs.redirectMode())); } if (baseChannel) { aArgs.init() = Some(baseChannel ->CloneReplacementChannelConfig( true, aRedirectFlags, HttpBaseChannel::ReplacementReason::DocumentChannel) .Serialize(aParent)); } uint32_t contentDispositionTemp; nsresult rv = mChannel->GetContentDisposition(&contentDispositionTemp); if (NS_SUCCEEDED(rv)) { aArgs.contentDisposition() = Some(contentDispositionTemp); } nsString contentDispositionFilenameTemp; rv = mChannel->GetContentDispositionFilename(contentDispositionFilenameTemp); if (NS_SUCCEEDED(rv)) { aArgs.contentDispositionFilename() = Some(contentDispositionFilenameTemp); } SetNeedToAddURIVisit(mChannel, false); aArgs.newLoadFlags() = aLoadFlags; aArgs.redirectFlags() = aRedirectFlags; aArgs.properties() = do_QueryObject(mChannel); aArgs.srcdocData() = mSrcdocData; aArgs.baseUri() = mBaseURI; aArgs.loadStateExternalLoadFlags() = mLoadStateExternalLoadFlags; aArgs.loadStateInternalLoadFlags() = mLoadStateInternalLoadFlags; aArgs.loadStateLoadType() = mLoadStateLoadType; aArgs.originalUriString() = mOriginalUriString; if (mLoadingSessionHistoryInfo) { aArgs.loadingSessionHistoryInfo().emplace(*mLoadingSessionHistoryInfo); } } static bool IsFirstLoadInWindow(nsIChannel* aChannel) { if (nsCOMPtr props = do_QueryInterface(aChannel)) { bool tmp = false; nsresult rv = props->GetPropertyAsBool(u"docshell.newWindowTarget"_ns, &tmp); return NS_SUCCEEDED(rv) && tmp; } return false; } // Get where the document loaded by this nsIChannel should be rendered. This // will be `OPEN_CURRENTWINDOW` unless we're loading an attachment which would // normally open in an external program, but we're instead choosing to render // internally. static int32_t GetWhereToOpen(nsIChannel* aChannel, bool aIsDocumentLoad) { // Ignore content disposition for loads from an object or embed element. if (!aIsDocumentLoad) { return nsIBrowserDOMWindow::OPEN_CURRENTWINDOW; } // Always continue in the same window if we're not loading an attachment. uint32_t disposition = nsIChannel::DISPOSITION_INLINE; if (NS_FAILED(aChannel->GetContentDisposition(&disposition)) || disposition != nsIChannel::DISPOSITION_ATTACHMENT) { return nsIBrowserDOMWindow::OPEN_CURRENTWINDOW; } // If the channel is for a new window target, continue in the same window. if (IsFirstLoadInWindow(aChannel)) { return nsIBrowserDOMWindow::OPEN_CURRENTWINDOW; } // Respect the user's preferences with browser.link.open_newwindow // FIXME: There should probably be a helper for this, as the logic is // duplicated in a few places. int32_t where = Preferences::GetInt("browser.link.open_newwindow", nsIBrowserDOMWindow::OPEN_NEWTAB); if (where == nsIBrowserDOMWindow::OPEN_CURRENTWINDOW || where == nsIBrowserDOMWindow::OPEN_NEWWINDOW || where == nsIBrowserDOMWindow::OPEN_NEWTAB) { return where; } return nsIBrowserDOMWindow::OPEN_NEWTAB; } static DocumentLoadListener::ProcessBehavior GetProcessSwitchBehavior( Element* aBrowserElement) { if (aBrowserElement->HasAttribute(u"maychangeremoteness"_ns)) { return DocumentLoadListener::ProcessBehavior::PROCESS_BEHAVIOR_STANDARD; } nsCOMPtr browser = aBrowserElement->AsBrowser(); bool isRemoteBrowser = false; browser->GetIsRemoteBrowser(&isRemoteBrowser); if (isRemoteBrowser) { return DocumentLoadListener::ProcessBehavior:: PROCESS_BEHAVIOR_SUBFRAME_ONLY; } return DocumentLoadListener::ProcessBehavior::PROCESS_BEHAVIOR_DISABLED; } static bool ContextCanProcessSwitch(CanonicalBrowsingContext* aBrowsingContext, WindowGlobalParent* aParentWindow, bool aSwitchToNewTab) { if (NS_WARN_IF(!aBrowsingContext)) { MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, ("Process Switch Abort: no browsing context")); return false; } if (!aBrowsingContext->IsContent()) { MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, ("Process Switch Abort: non-content browsing context")); return false; } // If we're switching into a new tab, we can skip the remaining checks, as // we're not actually changing the process of aBrowsingContext, so whether or // not it is allowed to process switch isn't relevant. if (aSwitchToNewTab) { return true; } if (aParentWindow && !aBrowsingContext->UseRemoteSubframes()) { MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, ("Process Switch Abort: remote subframes disabled")); return false; } if (aParentWindow && aParentWindow->IsInProcess()) { MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, ("Process Switch Abort: Subframe with in-process parent")); return false; } // Determine what process switching behaviour is being requested by the root // element. Element* browserElement = aBrowsingContext->Top()->GetEmbedderElement(); if (!browserElement) { MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, ("Process Switch Abort: cannot get embedder element")); return false; } nsCOMPtr browser = browserElement->AsBrowser(); if (!browser) { MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, ("Process Switch Abort: not loaded within nsIBrowser")); return false; } DocumentLoadListener::ProcessBehavior processBehavior = GetProcessSwitchBehavior(browserElement); // Check if the process switch we're considering is disabled by the // 's process behavior. if (processBehavior == DocumentLoadListener::ProcessBehavior::PROCESS_BEHAVIOR_DISABLED) { MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, ("Process Switch Abort: switch disabled by ")); return false; } if (!aParentWindow && processBehavior == DocumentLoadListener::ProcessBehavior:: PROCESS_BEHAVIOR_SUBFRAME_ONLY) { MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, ("Process Switch Abort: toplevel switch disabled by ")); return false; } return true; } static RefPtr SwitchToNewTab( CanonicalBrowsingContext* aLoadingBrowsingContext, int32_t aWhere) { MOZ_ASSERT(aWhere == nsIBrowserDOMWindow::OPEN_NEWTAB || aWhere == nsIBrowserDOMWindow::OPEN_NEWWINDOW, "Unsupported open location"); auto promise = MakeRefPtr( __func__); // Get the nsIBrowserDOMWindow for the given BrowsingContext's tab. nsCOMPtr browserDOMWindow = aLoadingBrowsingContext->GetBrowserDOMWindow(); if (NS_WARN_IF(!browserDOMWindow)) { MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, ("Process Switch Abort: Unable to get nsIBrowserDOMWindow")); promise->Reject(NS_ERROR_FAILURE, __func__); return promise; } // Open a new content tab by calling into frontend. We don't need to worry // about the triggering principal or CSP, as createContentWindow doesn't // actually start loading anything, but use a null principal anyway in case // something changes. nsCOMPtr triggeringPrincipal = NullPrincipal::Create(aLoadingBrowsingContext->OriginAttributesRef()); RefPtr openInfo = new nsOpenWindowInfo(); openInfo->mBrowsingContextReadyCallback = new nsBrowsingContextReadyCallback(promise); openInfo->mOriginAttributes = aLoadingBrowsingContext->OriginAttributesRef(); openInfo->mParent = aLoadingBrowsingContext; openInfo->mForceNoOpener = true; openInfo->mIsRemote = true; // Do the actual work to open a new tab or window async. nsresult rv = NS_DispatchToMainThread(NS_NewRunnableFunction( "DocumentLoadListener::SwitchToNewTab", [browserDOMWindow, openInfo, aWhere, triggeringPrincipal, promise] { RefPtr bc; nsresult rv = browserDOMWindow->CreateContentWindow( /* uri */ nullptr, openInfo, aWhere, nsIBrowserDOMWindow::OPEN_NO_REFERRER, triggeringPrincipal, /* csp */ nullptr, getter_AddRefs(bc)); if (NS_WARN_IF(NS_FAILED(rv))) { MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, ("Process Switch Abort: CreateContentWindow threw")); promise->Reject(rv, __func__); } if (bc) { promise->Resolve(bc, __func__); } })); if (NS_WARN_IF(NS_FAILED(rv))) { promise->Reject(NS_ERROR_UNEXPECTED, __func__); } return promise; } bool DocumentLoadListener::MaybeTriggerProcessSwitch( bool* aWillSwitchToRemote) { MOZ_ASSERT(XRE_IsParentProcess()); MOZ_DIAGNOSTIC_ASSERT(mChannel); MOZ_DIAGNOSTIC_ASSERT(mParentChannelListener); MOZ_DIAGNOSTIC_ASSERT(aWillSwitchToRemote); MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, ("DocumentLoadListener MaybeTriggerProcessSwitch [this=%p, uri=%s, " "browserid=%" PRIx64 "]", this, GetChannelCreationURI()->GetSpecOrDefault().get(), GetLoadingBrowsingContext()->Top()->BrowserId())); // If we're doing an / load, we may be doing a document load at // this point. We never need to do a process switch for a non-document // or load. if (!mIsDocumentLoad) { if (!mChannel->IsDocument()) { MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, ("Process Switch Abort: non-document load")); return false; } nsresult status; if (!nsObjectLoadingContent::IsSuccessfulRequest(mChannel, &status)) { MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, ("Process Switch Abort: error page")); return false; } } // Check if we should handle this load in a different tab or window. int32_t where = GetWhereToOpen(mChannel, mIsDocumentLoad); bool switchToNewTab = where != nsIBrowserDOMWindow::OPEN_CURRENTWINDOW; // Get the loading BrowsingContext. This may not be the context which will be // switching processes when switching to a new tab, and in the case of an // or element, as we don't create the final context until // after process selection. // // - /!\ WARNING /!\ - // Don't use `browsingContext->IsTop()` in this method! It will behave // incorrectly for non-document loads such as `` or ``. // Instead, check whether or not `parentWindow` is null. RefPtr browsingContext = GetLoadingBrowsingContext(); // If switching to a new tab, the final BC isn't a frame. RefPtr parentWindow = switchToNewTab ? nullptr : GetParentWindowContext(); if (!ContextCanProcessSwitch(browsingContext, parentWindow, switchToNewTab)) { return false; } if (!browsingContext->IsOwnedByProcess(GetContentProcessId(mContentParent))) { MOZ_LOG(gProcessIsolationLog, LogLevel::Error, ("Process Switch Abort: context no longer owned by creator")); Cancel(NS_BINDING_ABORTED, "Process Switch Abort: context no longer owned by creator"_ns); return false; } if (browsingContext->IsReplaced()) { MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, ("Process Switch Abort: replaced browsing context")); Cancel(NS_BINDING_ABORTED, "Process Switch Abort: replaced browsing context"_ns); return false; } nsAutoCString currentRemoteType(NOT_REMOTE_TYPE); if (mContentParent) { currentRemoteType = mContentParent->GetRemoteType(); } auto optionsResult = IsolationOptionsForNavigation( browsingContext->Top(), switchToNewTab ? nullptr : parentWindow.get(), GetChannelCreationURI(), mChannel, currentRemoteType, HasCrossOriginOpenerPolicyMismatch(), switchToNewTab, mLoadStateLoadType, mDocumentChannelId, mRemoteTypeOverride); if (optionsResult.isErr()) { MOZ_LOG(gProcessIsolationLog, LogLevel::Error, ("Process Switch Abort: CheckIsolationForNavigation Failed with %s", GetStaticErrorName(optionsResult.inspectErr()))); Cancel(optionsResult.unwrapErr(), "Process Switch Abort: CheckIsolationForNavigation Failed"_ns); return false; } NavigationIsolationOptions options = optionsResult.unwrap(); if (options.mTryUseBFCache) { MOZ_ASSERT(!parentWindow, "Can only BFCache toplevel windows"); MOZ_ASSERT(!switchToNewTab, "Can't BFCache for a tab switch"); bool sameOrigin = false; if (auto* wgp = browsingContext->GetCurrentWindowGlobal()) { nsCOMPtr resultPrincipal; MOZ_ALWAYS_SUCCEEDS( nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal( mChannel, getter_AddRefs(resultPrincipal))); sameOrigin = wgp->DocumentPrincipal()->EqualsConsideringDomain(resultPrincipal); } // We only reset the window name for content. mLoadingSessionHistoryInfo->mForceMaybeResetName.emplace( StaticPrefs::privacy_window_name_update_enabled() && browsingContext->IsContent() && !sameOrigin); } MOZ_LOG( gProcessIsolationLog, LogLevel::Verbose, ("CheckIsolationForNavigation -> current:(%s) remoteType:(%s) replace:%d " "group:%" PRIx64 " bfcache:%d shentry:%p newTab:%d", currentRemoteType.get(), options.mRemoteType.get(), options.mReplaceBrowsingContext, options.mSpecificGroupId, options.mTryUseBFCache, options.mActiveSessionHistoryEntry.get(), switchToNewTab)); // Check if a process switch is needed. if (currentRemoteType == options.mRemoteType && !options.mReplaceBrowsingContext && !switchToNewTab) { MOZ_LOG(gProcessIsolationLog, LogLevel::Info, ("Process Switch Abort: type (%s) is compatible", options.mRemoteType.get())); return false; } if (NS_WARN_IF(parentWindow && options.mRemoteType.IsEmpty())) { MOZ_LOG(gProcessIsolationLog, LogLevel::Error, ("Process Switch Abort: non-remote target process for subframe")); return false; } *aWillSwitchToRemote = !options.mRemoteType.IsEmpty(); // If we've decided to re-target this load into a new tab or window (see // `GetWhereToOpen`), do so before performing a process switch. This will // require creating the new to load in, which may be performed // async. if (switchToNewTab) { SwitchToNewTab(browsingContext, where) ->Then( GetMainThreadSerialEventTarget(), __func__, [self = RefPtr{this}, options](const RefPtr& aBrowsingContext) mutable { if (aBrowsingContext->IsDiscarded()) { MOZ_LOG( gProcessIsolationLog, LogLevel::Error, ("Process Switch: Got invalid new-tab BrowsingContext")); self->RedirectToRealChannelFinished(NS_ERROR_FAILURE); return; } MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, ("Process Switch: Redirected load to new tab")); self->TriggerProcessSwitch(aBrowsingContext->Canonical(), options, /* aIsNewTab */ true); }, [self = RefPtr{this}](const CopyableErrorResult&) { MOZ_LOG(gProcessIsolationLog, LogLevel::Error, ("Process Switch: SwitchToNewTab failed")); self->RedirectToRealChannelFinished(NS_ERROR_FAILURE); }); return true; } // If we're doing a document load, we can immediately perform a process // switch. if (mIsDocumentLoad) { TriggerProcessSwitch(browsingContext, options); return true; } // We're not doing a document load, which means we must be performing an // object load. We need a BrowsingContext to perform the switch in, so will // trigger an upgrade. if (!mObjectUpgradeHandler) { MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, ("Process Switch Abort: no object upgrade handler")); return false; } if (!StaticPrefs::fission_remoteObjectEmbed()) { MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, ("Process Switch Abort: remote / disabled")); return false; } mObjectUpgradeHandler->UpgradeObjectLoad()->Then( GetMainThreadSerialEventTarget(), __func__, [self = RefPtr{this}, options, parentWindow]( const RefPtr& aBrowsingContext) mutable { if (aBrowsingContext->IsDiscarded() || parentWindow != aBrowsingContext->GetParentWindowContext()) { MOZ_LOG(gProcessIsolationLog, LogLevel::Error, ("Process Switch: Got invalid BrowsingContext from object " "upgrade!")); self->RedirectToRealChannelFinished(NS_ERROR_FAILURE); return; } MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, ("Process Switch: Upgraded Object to Document Load")); self->TriggerProcessSwitch(aBrowsingContext, options); }, [self = RefPtr{this}](nsresult aStatusCode) { MOZ_ASSERT(NS_FAILED(aStatusCode), "Status should be error"); self->RedirectToRealChannelFinished(aStatusCode); }); return true; } void DocumentLoadListener::TriggerProcessSwitch( CanonicalBrowsingContext* aContext, const NavigationIsolationOptions& aOptions, bool aIsNewTab) { MOZ_DIAGNOSTIC_ASSERT(aIsNewTab || aContext->IsOwnedByProcess( GetContentProcessId(mContentParent)), "not owned by creator process anymore?"); if (MOZ_LOG_TEST(gProcessIsolationLog, LogLevel::Info)) { nsCString currentRemoteType = "INVALID"_ns; aContext->GetCurrentRemoteType(currentRemoteType, IgnoreErrors()); MOZ_LOG(gProcessIsolationLog, LogLevel::Info, ("Process Switch: Changing Remoteness from '%s' to '%s'", currentRemoteType.get(), aOptions.mRemoteType.get())); } // Stash our stream filter requests to pass to TriggerRedirectToRealChannel, // as the call to `DisconnectListeners` will clear our list. nsTArray streamFilterRequests = std::move(mStreamFilterRequests); // We're now committing to a process switch, so we can disconnect from // the listeners in the old process. // As the navigation is continuing, we don't actually want to cancel the // request in the old process unless we're redirecting the load into a new // tab. DisconnectListeners(NS_BINDING_ABORTED, NS_BINDING_ABORTED, !aIsNewTab); MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, ("Process Switch: Calling ChangeRemoteness")); aContext->ChangeRemoteness(aOptions, mLoadIdentifier) ->Then( GetMainThreadSerialEventTarget(), __func__, [self = RefPtr{this}, requests = std::move(streamFilterRequests)]( BrowserParent* aBrowserParent) mutable { MOZ_ASSERT(self->mChannel, "Something went wrong, channel got cancelled"); self->TriggerRedirectToRealChannel( Some(aBrowserParent ? aBrowserParent->Manager() : nullptr), std::move(requests)); }, [self = RefPtr{this}](nsresult aStatusCode) { MOZ_ASSERT(NS_FAILED(aStatusCode), "Status should be error"); self->RedirectToRealChannelFinished(aStatusCode); }); } RefPtr 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 loadState; nsDocShellLoadState::CreateFromPendingChannel( mChannel, mLoadIdentifier, mRedirectChannelId, getter_AddRefs(loadState)); loadState->SetLoadFlags(mLoadStateExternalLoadFlags); loadState->SetInternalLoadFlags(mLoadStateInternalLoadFlags); loadState->SetLoadType(mLoadStateLoadType); if (mLoadingSessionHistoryInfo) { loadState->SetLoadingSessionHistoryInfo(*mLoadingSessionHistoryInfo); } // This is poorly named now. RefPtr processListener = ChildProcessChannelListener::GetSingleton(); auto promise = MakeRefPtr( __func__); promise->UseDirectTaskDispatch(__func__); auto resolve = [promise](nsresult aResult) { promise->Resolve(aResult, __func__); }; nsTArray> endpoints; processListener->OnChannelReady(loadState, mLoadIdentifier, std::move(endpoints), mTiming, std::move(resolve)); return promise; } RefPtr DocumentLoadListener::RedirectToRealChannel( uint32_t aRedirectFlags, uint32_t aLoadFlags, const Maybe& aDestinationProcess, nsTArray&& aStreamFilterEndpoints) { LOG( ("DocumentLoadListener RedirectToRealChannel [this=%p] " "aRedirectFlags=%" PRIx32 ", aLoadFlags=%" PRIx32, this, aRedirectFlags, aLoadFlags)); if (mIsDocumentLoad) { // TODO(djg): Add the last URI visit to history if success. Is there a // better place to handle this? Need access to the updated aLoadFlags. nsresult status = NS_OK; mChannel->GetStatus(&status); bool updateGHistory = nsDocShell::ShouldUpdateGlobalHistory(mLoadStateLoadType); if (NS_SUCCEEDED(status) && updateGHistory && !net::ChannelIsPost(mChannel)) { AddURIVisit(mChannel, aLoadFlags); } } // Register the new channel and obtain id for it nsCOMPtr registrar = RedirectChannelRegistrar::GetOrCreate(); MOZ_ASSERT(registrar); nsCOMPtr chan = mChannel; if (nsCOMPtr vsc = do_QueryInterface(chan)) { chan = vsc->GetInnerChannel(); } mRedirectChannelId = nsContentUtils::GenerateLoadIdentifier(); MOZ_ALWAYS_SUCCEEDS(registrar->RegisterChannel(chan, mRedirectChannelId)); if (aDestinationProcess) { RefPtr cp = *aDestinationProcess; if (!cp) { MOZ_ASSERT(aStreamFilterEndpoints.IsEmpty()); return RedirectToParentProcess(aRedirectFlags, aLoadFlags); } if (!cp->CanSend()) { return PDocumentChannelParent::RedirectToRealChannelPromise:: CreateAndReject(ipc::ResponseRejectReason::SendError, __func__); } nsTArray ehArgs; mEarlyHintsService.RegisterLinksAndGetConnectArgs(cp->ChildID(), ehArgs); RedirectToRealChannelArgs args; SerializeRedirectData(args, /* aIsCrossProcess */ true, aRedirectFlags, aLoadFlags, cp, std::move(ehArgs), mEarlyHintsService.LinkType()); if (mTiming) { mTiming->Anonymize(args.uri()); args.timing() = std::move(mTiming); } auto loadInfo = args.loadInfo(); if (loadInfo.isNothing()) { return PDocumentChannelParent::RedirectToRealChannelPromise:: CreateAndReject(ipc::ResponseRejectReason::SendError, __func__); } cp->TransmitBlobDataIfBlobURL(args.uri()); if (CanonicalBrowsingContext* bc = GetDocumentBrowsingContext()) { if (bc->IsTop() && bc->IsActive()) { nsContentUtils::RequestGeckoTaskBurst(); } } return cp->SendCrossProcessRedirect(args, std::move(aStreamFilterEndpoints)); } if (mOpenPromiseResolved) { LOG( ("DocumentLoadListener RedirectToRealChannel [this=%p] " "promise already resolved. Aborting.", this)); // The promise has already been resolved or aborted, so we have no way to // return a promise again to the listener which would cancel the operation. // Reject the promise immediately. return PDocumentChannelParent::RedirectToRealChannelPromise:: CreateAndResolve(NS_BINDING_ABORTED, __func__); } // This promise will be passed on the promise listener which will // resolve this promise for us. auto promise = MakeRefPtr( __func__); mOpenPromise->Resolve( OpenPromiseSucceededType({std::move(aStreamFilterEndpoints), aRedirectFlags, aLoadFlags, mEarlyHintsService.LinkType(), promise}), __func__); // There is no way we could come back here if the promise had been resolved // previously. But for clarity and to avoid all doubt, we set this boolean to // true. mOpenPromiseResolved = true; return promise; } void DocumentLoadListener::TriggerRedirectToRealChannel( const Maybe& aDestinationProcess, nsTArray aStreamFilterRequests) { LOG(( "DocumentLoadListener::TriggerRedirectToRealChannel [this=%p] " "aDestinationProcess=%" PRId64, this, aDestinationProcess ? int64_t(*aDestinationProcess) : int64_t(-1))); // This initiates replacing the current DocumentChannel with a // protocol specific 'real' channel, maybe in a different process than // the current DocumentChannelChild, if aDestinationProces is set. // It registers the current mChannel with the registrar to get an ID // so that the remote end can setup a new IPDL channel and lookup // the same underlying channel. // We expect this process to finish with FinishReplacementChannelSetup // (for both in-process and process switch cases), where we cleanup // the registrar and copy across any needed state to the replacing // IPDL parent object. nsTArray parentEndpoints(aStreamFilterRequests.Length()); if (!aStreamFilterRequests.IsEmpty()) { ContentParent* cp = aDestinationProcess.valueOr(mContentParent); base::ProcessId pid = cp ? cp->OtherPid() : base::ProcessId{0}; for (StreamFilterRequest& request : aStreamFilterRequests) { if (!pid) { request.mPromise->Reject(false, __func__); request.mPromise = nullptr; continue; } ParentEndpoint parent; nsresult rv = extensions::PStreamFilter::CreateEndpoints( &parent, &request.mChildEndpoint); if (NS_FAILED(rv)) { request.mPromise->Reject(false, __func__); request.mPromise = nullptr; } else { parentEndpoints.AppendElement(std::move(parent)); } } } // If we didn't have any redirects, then we pass the REDIRECT_INTERNAL flag // for this channel switch so that it isn't recorded in session history etc. // If there were redirect(s), then we want this switch to be recorded as a // real one, since we have a new URI. uint32_t redirectFlags = 0; if (!mHaveVisibleRedirect) { redirectFlags = nsIChannelEventSink::REDIRECT_INTERNAL; } uint32_t newLoadFlags = nsIRequest::LOAD_NORMAL; MOZ_ALWAYS_SUCCEEDS(mChannel->GetLoadFlags(&newLoadFlags)); // We're pulling our flags from the inner channel, which may not have this // flag set on it. This is the case when loading a 'view-source' channel. if (mIsDocumentLoad || aDestinationProcess) { newLoadFlags |= nsIChannel::LOAD_DOCUMENT_URI; } if (!aDestinationProcess) { newLoadFlags |= nsIChannel::LOAD_REPLACE; } // INHIBIT_PERSISTENT_CACHING is clearing during http redirects (from // both parent and content process channel instances), but only ever // re-added to the parent-side nsHttpChannel. // To match that behaviour, we want to explicitly avoid copying this flag // back to our newly created content side channel, otherwise it can // affect sub-resources loads in the same load group. nsCOMPtr uri; mChannel->GetURI(getter_AddRefs(uri)); if (uri && uri->SchemeIs("https")) { newLoadFlags &= ~nsIRequest::INHIBIT_PERSISTENT_CACHING; } RefPtr self = this; RedirectToRealChannel(redirectFlags, newLoadFlags, aDestinationProcess, std::move(parentEndpoints)) ->Then( GetCurrentSerialEventTarget(), __func__, [self, requests = std::move(aStreamFilterRequests)]( const nsresult& aResponse) mutable { for (StreamFilterRequest& request : requests) { if (request.mPromise) { request.mPromise->Resolve(std::move(request.mChildEndpoint), __func__); request.mPromise = nullptr; } } self->RedirectToRealChannelFinished(aResponse); }, [self](const mozilla::ipc::ResponseRejectReason) { self->RedirectToRealChannelFinished(NS_ERROR_FAILURE); }); } void DocumentLoadListener::MaybeReportBlockedByURLClassifier(nsresult aStatus) { auto* browsingContext = GetDocumentBrowsingContext(); if (!browsingContext || browsingContext->IsTop() || !StaticPrefs::privacy_trackingprotection_testing_report_blocked_node()) { return; } if (!UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aStatus)) { return; } RefPtr parent = browsingContext->GetParentWindowContext(); if (parent) { Unused << parent->SendAddBlockedFrameNodeByClassifier(browsingContext); } } bool DocumentLoadListener::DocShellWillDisplayContent(nsresult aStatus) { if (NS_SUCCEEDED(aStatus)) { return true; } // Always return errored loads to the or element's process, // as load errors will not be rendered as documents. if (!mIsDocumentLoad) { return false; } // nsDocShell attempts urifixup on some failure types, // but also of those also display an error page if we don't // succeed with fixup, so we don't need to check for it // here. auto* loadingContext = GetLoadingBrowsingContext(); bool isInitialDocument = true; if (WindowGlobalParent* currentWindow = loadingContext->GetCurrentWindowGlobal()) { isInitialDocument = currentWindow->IsInitialDocument(); } nsresult rv = nsDocShell::FilterStatusForErrorPage( aStatus, mChannel, mLoadStateLoadType, loadingContext->IsTop(), loadingContext->GetUseErrorPages(), isInitialDocument, nullptr); if (NS_SUCCEEDED(rv)) { MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, ("Skipping process switch, as DocShell will not display content " "(status: %s) %s", GetStaticErrorName(aStatus), GetChannelCreationURI()->GetSpecOrDefault().get())); } // If filtering returned a failure code, then an error page will // be display for that code, so return true; return NS_FAILED(rv); } bool DocumentLoadListener::MaybeHandleLoadErrorWithURIFixup(nsresult aStatus) { RefPtr bc = GetDocumentBrowsingContext(); if (!bc) { return false; } nsCOMPtr newPostData; nsCOMPtr newURI = nsDocShell::AttemptURIFixup( mChannel, aStatus, mOriginalUriString, mLoadStateLoadType, bc->IsTop(), mLoadStateInternalLoadFlags & nsDocShell::INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP, bc->UsePrivateBrowsing(), true, getter_AddRefs(newPostData)); // Since aStatus will be NS_OK for 4xx and 5xx error codes we // have to check each request which was upgraded by https-first. // If an error (including 4xx and 5xx) occured, then let's check if // we can downgrade the scheme to HTTP again. bool isHTTPSFirstFixup = false; if (!newURI) { newURI = nsHTTPSOnlyUtils::PotentiallyDowngradeHttpsFirstRequest(mChannel, aStatus); isHTTPSFirstFixup = true; } if (!newURI) { return false; } // If we got a new URI, then we should initiate a load with that. // Notify the listeners that this load is complete (with a code that // won't trigger an error page), and then start the new one. DisconnectListeners(NS_BINDING_ABORTED, NS_BINDING_ABORTED); RefPtr loadState = new nsDocShellLoadState(newURI); nsCOMPtr loadInfo = mChannel->LoadInfo(); nsCOMPtr cspToInherit = loadInfo->GetCspToInherit(); loadState->SetCsp(cspToInherit); nsCOMPtr triggeringPrincipal = loadInfo->TriggeringPrincipal(); loadState->SetTriggeringPrincipal(triggeringPrincipal); loadState->SetPostDataStream(newPostData); if (isHTTPSFirstFixup) { // We have to exempt the load from HTTPS-First to prevent a // upgrade-downgrade loop. loadState->SetIsExemptFromHTTPSOnlyMode(true); } // Ensure to set referrer information in the fallback channel equally to the // not-upgraded original referrer info. // // A simply copy of the referrer info from the upgraded one leads to problems. // For example: // 1. https://some-site.com redirects to http://other-site.com with referrer // policy // "no-referrer-when-downgrade". // 2. https-first upgrades the redirection, so redirects to // https://other-site.com, // according to referrer policy the referrer will be send (https-> https) // 3. Assume other-site.com is not supporting https, https-first performs // fall- // back. // If the referrer info from the upgraded channel gets copied into the // http fallback channel, the referrer info would contain the referrer // (https://some-site.com). That would violate the policy // "no-referrer-when-downgrade". A recreation of the original referrer info // would ensure us that the referrer is set according to the referrer policy. nsCOMPtr httpChannel = do_QueryInterface(mChannel); if (httpChannel) { nsCOMPtr referrerInfo = httpChannel->GetReferrerInfo(); if (referrerInfo) { ReferrerPolicy referrerPolicy = referrerInfo->ReferrerPolicy(); nsCOMPtr originalReferrer = referrerInfo->GetOriginalReferrer(); if (originalReferrer) { // Create new ReferrerInfo with the original referrer and the referrer // policy. nsCOMPtr newReferrerInfo = new ReferrerInfo(originalReferrer, referrerPolicy); loadState->SetReferrerInfo(newReferrerInfo); } } } bc->LoadURI(loadState, false); return true; } NS_IMETHODIMP DocumentLoadListener::OnStartRequest(nsIRequest* aRequest) { LOG(("DocumentLoadListener OnStartRequest [this=%p]", this)); nsCOMPtr multiPartChannel = do_QueryInterface(aRequest); if (multiPartChannel) { multiPartChannel->GetBaseChannel(getter_AddRefs(mChannel)); } else { mChannel = do_QueryInterface(aRequest); } MOZ_DIAGNOSTIC_ASSERT(mChannel); if (mHaveVisibleRedirect && GetDocumentBrowsingContext() && mLoadingSessionHistoryInfo) { mLoadingSessionHistoryInfo = GetDocumentBrowsingContext()->ReplaceLoadingSessionHistoryEntryForLoad( mLoadingSessionHistoryInfo.get(), mChannel); } RefPtr httpChannel = do_QueryObject(mChannel); // Enforce CSP frame-ancestors and x-frame-options checks which // might cancel the channel. nsContentSecurityUtils::PerformCSPFrameAncestorAndXFOCheck(mChannel); // HTTPS-Only Mode tries to upgrade connections to https. Once loading // is in progress we set that flag so that timeout counter measures // do not kick in. if (httpChannel) { nsCOMPtr loadInfo = httpChannel->LoadInfo(); bool isPrivateWin = loadInfo->GetOriginAttributes().mPrivateBrowsingId > 0; if (nsHTTPSOnlyUtils::IsHttpsOnlyModeEnabled(isPrivateWin)) { uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus(); httpsOnlyStatus |= nsILoadInfo::HTTPS_ONLY_TOP_LEVEL_LOAD_IN_PROGRESS; loadInfo->SetHttpsOnlyStatus(httpsOnlyStatus); } if (mLoadingSessionHistoryInfo && nsDocShell::ShouldDiscardLayoutState(httpChannel)) { mLoadingSessionHistoryInfo->mInfo.SetSaveLayoutStateFlag(false); } } auto* loadingContext = GetLoadingBrowsingContext(); if (!loadingContext || loadingContext->IsDiscarded()) { Cancel(NS_ERROR_UNEXPECTED, "No valid LoadingBrowsingContext."_ns); return NS_ERROR_UNEXPECTED; } if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { Cancel(NS_ERROR_ILLEGAL_DURING_SHUTDOWN, "Aborting OnStartRequest after shutdown started."_ns); return NS_OK; } // Block top-level data URI navigations if triggered by the web. Logging is // performed in AllowTopLevelNavigationToDataURI. if (!nsContentSecurityManager::AllowTopLevelNavigationToDataURI(mChannel)) { mChannel->Cancel(NS_ERROR_DOM_BAD_URI); if (loadingContext) { RefPtr maybeCloseWindowHelper = new MaybeCloseWindowHelper(loadingContext); // If a new window was opened specifically for this request, close it // after blocking the navigation. maybeCloseWindowHelper->SetShouldCloseWindow( IsFirstLoadInWindow(mChannel)); Unused << maybeCloseWindowHelper->MaybeCloseWindow(); } DisconnectListeners(NS_ERROR_DOM_BAD_URI, NS_ERROR_DOM_BAD_URI); return NS_OK; } // Generally we want to switch to a real channel even if the request failed, // since the listener might want to access protocol-specific data (like http // response headers) in its error handling. // An exception to this is when nsExtProtocolChannel handled the request and // returned NS_ERROR_NO_CONTENT, since creating a real one in the content // process will attempt to handle the URI a second time. nsresult status = NS_OK; aRequest->GetStatus(&status); if (status == NS_ERROR_NO_CONTENT) { DisconnectListeners(status, status); return NS_OK; } // PerformCSPFrameAncestorAndXFOCheck may cancel a moz-extension request that // needs to be handled here. Without this, the resource would be loaded and // not blocked when the real channel is created in the content process. if (status == NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION && !httpChannel) { DisconnectListeners(status, status); return NS_OK; } // If this was a failed load and we want to try fixing the uri, then // this will initiate a new load (and disconnect this one), and we don't // need to do anything else. if (MaybeHandleLoadErrorWithURIFixup(status)) { return NS_OK; } mStreamListenerFunctions.AppendElement(StreamListenerFunction{ VariantIndex<0>{}, OnStartRequestParams{aRequest}}); if (mOpenPromiseResolved || mInitiatedRedirectToRealChannel) { // I we have already resolved the promise, there's no point to continue // attempting a process switch or redirecting to the real channel. // We can also have multiple calls to OnStartRequest when dealing with // multi-part content, but only want to redirect once. return NS_OK; } mChannel->Suspend(); mInitiatedRedirectToRealChannel = true; MaybeReportBlockedByURLClassifier(status); // Determine if a new process needs to be spawned. If it does, this will // trigger a cross process switch, and we should hold off on redirecting to // the real channel. // If the channel has failed, and the docshell isn't going to display an // error page for that failure, then don't allow process switching, since // we just want to keep our existing document. bool willBeRemote = false; if (!DocShellWillDisplayContent(status) || !MaybeTriggerProcessSwitch(&willBeRemote)) { // We're not going to be doing a process switch, so redirect to the real // channel within our current process. nsTArray streamFilterRequests = std::move(mStreamFilterRequests); if (!mSupportsRedirectToRealChannel) { RefPtr browserParent = loadingContext->GetBrowserParent(); if (browserParent->Manager() != mContentParent) { LOG( ("DocumentLoadListener::RedirectToRealChannel failed because " "browsingContext no longer owned by creator")); Cancel(NS_BINDING_ABORTED, "DocumentLoadListener::RedirectToRealChannel failed because " "browsingContext no longer owned by creator"_ns); return NS_OK; } MOZ_DIAGNOSTIC_ASSERT( browserParent->GetBrowsingContext() == loadingContext, "make sure the load is going to the right place"); // If the existing process is right for this load, but the bridge doesn't // support redirects, then we need to do it manually, by faking a process // switch. DisconnectListeners(NS_BINDING_ABORTED, NS_BINDING_ABORTED, /* aContinueNavigating */ true); // Notify the docshell that it should load using the newly connected // channel browserParent->ResumeLoad(mLoadIdentifier); // Use the current process ID to run the 'process switch' path and connect // the channel into the current process. TriggerRedirectToRealChannel(Some(mContentParent), std::move(streamFilterRequests)); } else { TriggerRedirectToRealChannel(Nothing(), std::move(streamFilterRequests)); } // If we're not switching, then check if we're currently remote. if (mContentParent) { willBeRemote = true; } } if (httpChannel) { uint32_t responseStatus = 0; Unused << httpChannel->GetResponseStatus(&responseStatus); mEarlyHintsService.FinalResponse(responseStatus); } else { mEarlyHintsService.Cancel( "DocumentLoadListener::OnStartRequest: no httpChannel"_ns); } // If we're going to be delivering this channel to a remote content // process, then we want to install any required content conversions // in the content process. // The caller of this OnStartRequest will install a conversion // helper after we return if we haven't disabled conversion. Normally // HttpChannelParent::OnStartRequest would disable conversion, but we're // defering calling that until later. Manually disable it now to prevent the // converter from being installed (since we want the child to do it), and // also save the value so that when we do call // HttpChannelParent::OnStartRequest, we can have the value as it originally // was. if (httpChannel) { Unused << httpChannel->GetApplyConversion(&mOldApplyConversion); if (willBeRemote) { httpChannel->SetApplyConversion(false); } } return NS_OK; } NS_IMETHODIMP DocumentLoadListener::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) { LOG(("DocumentLoadListener OnStopRequest [this=%p]", this)); mStreamListenerFunctions.AppendElement(StreamListenerFunction{ VariantIndex<2>{}, OnStopRequestParams{aRequest, aStatusCode}}); // If we're not a multi-part channel, then we're finished and we don't // expect any further events. If we are, then this might be called again, // so wait for OnAfterLastPart instead. nsCOMPtr multiPartChannel = do_QueryInterface(aRequest); if (!multiPartChannel) { mIsFinished = true; } mStreamFilterRequests.Clear(); return NS_OK; } NS_IMETHODIMP DocumentLoadListener::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInputStream, uint64_t aOffset, uint32_t aCount) { LOG(("DocumentLoadListener OnDataAvailable [this=%p]", this)); // This isn't supposed to happen, since we suspended the channel, but // sometimes Suspend just doesn't work. This can happen when we're routing // through nsUnknownDecoder to sniff the content type, and it doesn't handle // being suspended. Let's just store the data and manually forward it to our // redirected channel when it's ready. nsCString data; nsresult rv = NS_ReadInputStreamToString(aInputStream, data, aCount); NS_ENSURE_SUCCESS(rv, rv); mStreamListenerFunctions.AppendElement(StreamListenerFunction{ VariantIndex<1>{}, OnDataAvailableParams{aRequest, data, aOffset, aCount}}); return NS_OK; } //----------------------------------------------------------------------------- // DoucmentLoadListener::nsIMultiPartChannelListener //----------------------------------------------------------------------------- NS_IMETHODIMP DocumentLoadListener::OnAfterLastPart(nsresult aStatus) { LOG(("DocumentLoadListener OnAfterLastPart [this=%p]", this)); if (!mInitiatedRedirectToRealChannel) { // if we get here, and we haven't initiated a redirect to a real // channel, then it means we never got OnStartRequest (maybe a problem?) // and we retargeted everything. LOG(("DocumentLoadListener Disconnecting child")); DisconnectListeners(NS_BINDING_RETARGETED, NS_OK); return NS_OK; } mStreamListenerFunctions.AppendElement(StreamListenerFunction{ VariantIndex<3>{}, OnAfterLastPartParams{aStatus}}); mIsFinished = true; return NS_OK; } NS_IMETHODIMP DocumentLoadListener::GetInterface(const nsIID& aIID, void** result) { RefPtr browsingContext = GetLoadingBrowsingContext(); if (aIID.Equals(NS_GET_IID(nsILoadContext)) && browsingContext) { browsingContext.forget(result); return NS_OK; } return QueryInterface(aIID, result); } //////////////////////////////////////////////////////////////////////////////// // nsIParentChannel //////////////////////////////////////////////////////////////////////////////// NS_IMETHODIMP DocumentLoadListener::SetParentListener( mozilla::net::ParentChannelListener* listener) { // We don't need this (do we?) return NS_OK; } NS_IMETHODIMP DocumentLoadListener::SetClassifierMatchedInfo(const nsACString& aList, const nsACString& aProvider, const nsACString& aFullHash) { ClassifierMatchedInfoParams params; params.mList = aList; params.mProvider = aProvider; params.mFullHash = aFullHash; mIParentChannelFunctions.AppendElement( IParentChannelFunction{VariantIndex<0>{}, std::move(params)}); return NS_OK; } NS_IMETHODIMP DocumentLoadListener::SetClassifierMatchedTrackingInfo( const nsACString& aLists, const nsACString& aFullHash) { ClassifierMatchedTrackingInfoParams params; params.mLists = aLists; params.mFullHashes = aFullHash; mIParentChannelFunctions.AppendElement( IParentChannelFunction{VariantIndex<1>{}, std::move(params)}); return NS_OK; } NS_IMETHODIMP DocumentLoadListener::NotifyClassificationFlags(uint32_t aClassificationFlags, bool aIsThirdParty) { mIParentChannelFunctions.AppendElement(IParentChannelFunction{ VariantIndex<2>{}, ClassificationFlagsParams{aClassificationFlags, aIsThirdParty}}); return NS_OK; } NS_IMETHODIMP DocumentLoadListener::Delete() { MOZ_ASSERT_UNREACHABLE("This method is unused"); return NS_OK; } NS_IMETHODIMP DocumentLoadListener::GetRemoteType(nsACString& aRemoteType) { // FIXME: The remote type here should be pulled from the remote process used // to create this DLL, not from the current `browsingContext`. RefPtr browsingContext = GetDocumentBrowsingContext(); if (!browsingContext) { return NS_ERROR_UNEXPECTED; } ErrorResult error; browsingContext->GetCurrentRemoteType(aRemoteType, error); if (error.Failed()) { aRemoteType = NOT_REMOTE_TYPE; } return NS_OK; } //////////////////////////////////////////////////////////////////////////////// // nsIChannelEventSink //////////////////////////////////////////////////////////////////////////////// NS_IMETHODIMP DocumentLoadListener::AsyncOnChannelRedirect( nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags, nsIAsyncVerifyRedirectCallback* aCallback) { LOG(("DocumentLoadListener::AsyncOnChannelRedirect [this=%p flags=%" PRIu32 "]", this, aFlags)); // We generally don't want to notify the content process about redirects, // so just update our channel and tell the callback that we're good to go. mChannel = aNewChannel; // We need the original URI of the current channel to use to open the real // channel in the content process. Unfortunately we overwrite the original // uri of the new channel with the original pre-redirect URI, so grab // a copy of it now and save it on the loadInfo corresponding to the // new channel. nsCOMPtr loadInfoFromChannel = mChannel->LoadInfo(); MOZ_ASSERT(loadInfoFromChannel); nsCOMPtr uri; mChannel->GetOriginalURI(getter_AddRefs(uri)); loadInfoFromChannel->SetChannelCreationOriginalURI(uri); // Since we're redirecting away from aOldChannel, we should check if it // had a COOP mismatch, since we want the final result for this to // include the state of all channels we redirected through. nsCOMPtr httpChannel = do_QueryInterface(aOldChannel); if (httpChannel) { bool isCOOPMismatch = false; Unused << NS_WARN_IF(NS_FAILED( httpChannel->HasCrossOriginOpenerPolicyMismatch(&isCOOPMismatch))); mHasCrossOriginOpenerPolicyMismatch |= isCOOPMismatch; } // If HTTPS-Only mode is enabled, we need to check whether the exception-flag // needs to be removed or set, by asking the PermissionManager. nsHTTPSOnlyUtils::TestSitePermissionAndPotentiallyAddExemption(mChannel); // We don't need to confirm internal redirects or record any // history for them, so just immediately verify and return. if (aFlags & nsIChannelEventSink::REDIRECT_INTERNAL) { LOG( ("DocumentLoadListener AsyncOnChannelRedirect [this=%p] " "flags=REDIRECT_INTERNAL", this)); aCallback->OnRedirectVerifyCallback(NS_OK); return NS_OK; } // Cancel cross origin redirects as described by whatwg: // > Note: [The early hint reponse] is discarded if it is succeeded by a // > cross-origin redirect. // https://html.spec.whatwg.org/multipage/semantics.html#early-hints nsCOMPtr oldURI; aOldChannel->GetURI(getter_AddRefs(oldURI)); nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); nsresult rv = ssm->CheckSameOriginURI(oldURI, uri, false, false); if (NS_FAILED(rv)) { mEarlyHintsService.Cancel( "DocumentLoadListener::AsyncOnChannelRedirect: cors redirect"_ns); } if (GetDocumentBrowsingContext()) { if (!net::ChannelIsPost(aOldChannel)) { AddURIVisit(aOldChannel, 0); nsDocShell::SaveLastVisit(aNewChannel, oldURI, aFlags); } } mHaveVisibleRedirect |= true; LOG( ("DocumentLoadListener AsyncOnChannelRedirect [this=%p] " "mHaveVisibleRedirect=%c", this, mHaveVisibleRedirect ? 'T' : 'F')); // Clear out our nsIParentChannel functions, since a normal parent // channel would actually redirect and not have those values on the new one. // We expect the URI classifier to run on the redirected channel with // the new URI and set these again. mIParentChannelFunctions.Clear(); // If we had a remote type override, ensure it's been cleared after a // redirect, as it can't apply anymore. mRemoteTypeOverride.reset(); #ifdef ANDROID nsCOMPtr uriBeingLoaded = AntiTrackingUtils::MaybeGetDocumentURIBeingLoaded(mChannel); RefPtr> promise; RefPtr bc = mParentChannelListener->GetBrowsingContext(); nsCOMPtr widget = bc ? bc->GetParentProcessWidgetContaining() : nullptr; RefPtr window = nsWindow::From(widget); if (window) { promise = window->OnLoadRequest(uriBeingLoaded, nsIBrowserDOMWindow::OPEN_CURRENTWINDOW, nsIWebNavigation::LOAD_FLAGS_IS_REDIRECT, nullptr, false, bc->IsTopContent()); } if (promise) { RefPtr cb = aCallback; promise->Then( GetCurrentSerialEventTarget(), __func__, [=](const MozPromise::ResolveOrRejectValue& aValue) { if (aValue.IsResolve()) { bool handled = aValue.ResolveValue(); if (handled) { cb->OnRedirectVerifyCallback(NS_ERROR_ABORT); } else { cb->OnRedirectVerifyCallback(NS_OK); } } }); } else #endif /* ANDROID */ { aCallback->OnRedirectVerifyCallback(NS_OK); } return NS_OK; } nsIURI* DocumentLoadListener::GetChannelCreationURI() const { nsCOMPtr channelLoadInfo = mChannel->LoadInfo(); nsCOMPtr uri; channelLoadInfo->GetChannelCreationOriginalURI(getter_AddRefs(uri)); if (uri) { // See channelCreationOriginalURI for more info. We use this instead of the // originalURI of the channel to help us avoid the situation when we use // the URI of a redirect that has failed to happen. return uri; } // Otherwise, get the original URI from the channel. mChannel->GetOriginalURI(getter_AddRefs(uri)); return uri; } // This method returns the cached result of running the Cross-Origin-Opener // policy compare algorithm by calling ComputeCrossOriginOpenerPolicyMismatch bool DocumentLoadListener::HasCrossOriginOpenerPolicyMismatch() const { // If we found a COOP mismatch on an earlier channel and then // redirected away from that, we should use that result. if (mHasCrossOriginOpenerPolicyMismatch) { return true; } nsCOMPtr httpChannel = do_QueryInterface(mChannel); if (!httpChannel) { // Not an nsIHttpChannelInternal assume it's okay to switch. return false; } bool isCOOPMismatch = false; Unused << NS_WARN_IF(NS_FAILED( httpChannel->HasCrossOriginOpenerPolicyMismatch(&isCOOPMismatch))); return isCOOPMismatch; } auto DocumentLoadListener::AttachStreamFilter() -> RefPtr { LOG(("DocumentLoadListener AttachStreamFilter [this=%p]", this)); StreamFilterRequest* request = mStreamFilterRequests.AppendElement(); request->mPromise = new ChildEndpointPromise::Private(__func__); return request->mPromise; } NS_IMETHODIMP DocumentLoadListener::OnProgress(nsIRequest* aRequest, int64_t aProgress, int64_t aProgressMax) { return NS_OK; } NS_IMETHODIMP DocumentLoadListener::OnStatus(nsIRequest* aRequest, nsresult aStatus, const char16_t* aStatusArg) { nsCOMPtr channel = mChannel; RefPtr webProgress = GetLoadingBrowsingContext()->GetWebProgress(); const nsString message(aStatusArg); if (webProgress) { NS_DispatchToMainThread( NS_NewRunnableFunction("DocumentLoadListener::OnStatus", [=]() { webProgress->OnStatusChange(webProgress, channel, aStatus, message.get()); })); } return NS_OK; } NS_IMETHODIMP DocumentLoadListener::EarlyHint(const nsACString& aLinkHeader, const nsACString& aReferrerPolicy, const nsACString& aCSPHeader) { LOG(("DocumentLoadListener::EarlyHint.\n")); mEarlyHintsService.EarlyHint(aLinkHeader, GetChannelCreationURI(), mChannel, aReferrerPolicy, aCSPHeader); return NS_OK; } } // namespace net } // namespace mozilla #undef LOG