summaryrefslogtreecommitdiffstats
path: root/netwerk/ipc/DocumentLoadListener.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/ipc/DocumentLoadListener.cpp')
-rw-r--r--netwerk/ipc/DocumentLoadListener.cpp2965
1 files changed, 2965 insertions, 0 deletions
diff --git a/netwerk/ipc/DocumentLoadListener.cpp b/netwerk/ipc/DocumentLoadListener.cpp
new file mode 100644
index 0000000000..715e00c056
--- /dev/null
+++ b/netwerk/ipc/DocumentLoadListener.cpp
@@ -0,0 +1,2965 @@
+/* -*- 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<nsIWritablePropertyBag2> 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<LoadInfo> {
+ uint32_t sandboxFlags = aBrowsingContext->GetSandboxFlags();
+ RefPtr<LoadInfo> 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<LoadInfo> {
+ RefPtr<WindowGlobalParent> wgp =
+ WindowGlobalParent::GetByInnerWindowId(aInnerWindowId);
+ MOZ_RELEASE_ASSERT(wgp);
+
+ auto securityFlags = SecurityFlagsForLoadInfo(aLoadState);
+
+ RefPtr<LoadInfo> 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<nsIStreamConverterService> 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<nsIStreamListener> 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<nsIMultiPartChannel> 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<DocumentLoadListener> doc = do_GetInterface(ToSupports(mListener));
+ MOZ_ASSERT(doc);
+ doc->DisconnectListeners(aStatus, aLoadGroupStatus);
+ mListener->SetListenerAfterRedirect(nullptr);
+ }
+
+ RefPtr<mozilla::dom::BrowsingContext> mBrowsingContext;
+ RefPtr<ParentChannelListener> 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<nsIURI> uri;
+ NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
+
+ nsCOMPtr<nsIURI> 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<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
+ if (httpChannel) {
+ Unused << httpChannel->GetResponseStatus(&responseStatus);
+ }
+
+ RefPtr<CanonicalBrowsingContext> browsingContext =
+ GetDocumentBrowsingContext();
+ nsCOMPtr<nsIWidget> 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<WindowGlobalParent> 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<nsIURI> 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<SessionHistoryEntry*, const char*> 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<uint64_t>& aChannelId,
+ const TimeStamp& aAsyncOpenTime,
+ nsDOMNavigationTiming* aTiming,
+ Maybe<ClientInfo>&& aInfo, bool aUrgentStart,
+ dom::ContentParent* aContentParent,
+ nsresult* aRv) -> RefPtr<OpenPromise> {
+ 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<SessionHistoryEntry> existingEntry;
+ if (SessionHistoryInParent() && aLoadState->LoadIsFromSessionHistory() &&
+ aLoadState->LoadType() != LOAD_ERROR_PAGE) {
+ Result<SessionHistoryEntry*, const char*> 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<nsIURI> uriBeingLoaded;
+ Unused << NS_WARN_IF(
+ NS_FAILED(mChannel->GetURI(getter_AddRefs(uriBeingLoaded))));
+
+ RefPtr<HttpBaseChannel> httpBaseChannel = do_QueryObject(mChannel, aRv);
+ if (uriBeingLoaded && httpBaseChannel) {
+ nsCOMPtr<nsIURI> 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<WindowGlobalParent> topWindow = AntiTrackingUtils::
+ GetTopWindowExcludingExtensionAccessibleContentFrames(
+ loadingContext, uriBeingLoaded)) {
+ nsCOMPtr<nsIPrincipal> topWindowPrincipal =
+ topWindow->DocumentPrincipal();
+ if (topWindowPrincipal && !topWindowPrincipal->GetIsNullPrincipal()) {
+ auto* basePrin = BasePrincipal::Cast(topWindowPrincipal);
+ basePrin->GetURI(getter_AddRefs(topWindowURI));
+ }
+ }
+ httpBaseChannel->SetTopWindowURI(topWindowURI);
+ }
+
+ nsCOMPtr<nsIIdentChannel> identChannel = do_QueryInterface(mChannel);
+ if (identChannel && aChannelId) {
+ Unused << identChannel->SetChannelId(*aChannelId);
+ }
+ mDocumentChannelId = aChannelId;
+
+ RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
+ if (httpChannelImpl) {
+ httpChannelImpl->SetWarningReporter(this);
+
+ if (mIsDocumentLoad && loadingContext->IsTop()) {
+ httpChannelImpl->SetEarlyHintObserver(this);
+ }
+ }
+
+ nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(mChannel);
+ if (timedChannel) {
+ timedChannel->SetAsyncOpen(aAsyncOpenTime);
+ }
+
+ if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel)) {
+ Unused << httpChannel->SetRequestContextID(
+ loadingContext->GetRequestContextId());
+
+ nsCOMPtr<nsIClassOfService> 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<ParentProcessDocumentOpenInfo> openInfo =
+ new ParentProcessDocumentOpenInfo(mParentChannelListener, openFlags,
+ loadingContext, mIsDocumentLoad);
+ openInfo->Prepare();
+
+#ifdef ANDROID
+ RefPtr<MozPromise<bool, bool, false>> promise;
+ if (documentContext && aLoadState->LoadType() != LOAD_ERROR_PAGE &&
+ !(aLoadState->HasInternalLoadFlags(
+ nsDocShell::INTERNAL_LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE)) &&
+ !(aLoadState->LoadType() & LOAD_HISTORY)) {
+ nsCOMPtr<nsIWidget> widget =
+ documentContext->GetParentProcessWidgetContaining();
+ RefPtr<nsWindow> 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<DocumentLoadListener> self = this;
+ promise->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [=](const MozPromise<bool, bool, false>::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<uint32_t>(*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<nsILoadInfo> 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<uint64_t>& aChannelId, const TimeStamp& aAsyncOpenTime,
+ nsDOMNavigationTiming* aTiming, Maybe<dom::ClientInfo>&& aInfo,
+ Maybe<bool> aUriModified, Maybe<bool> aIsXFOError,
+ dom::ContentParent* aContentParent, nsresult* aRv) -> RefPtr<OpenPromise> {
+ LOG(("DocumentLoadListener [%p] OpenDocument [uri=%s]", this,
+ aLoadState->URI()->GetSpecOrDefault().get()));
+
+ MOZ_ASSERT(mIsDocumentLoad);
+
+ RefPtr<CanonicalBrowsingContext> 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> 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<uint64_t>& aChannelId, const TimeStamp& aAsyncOpenTime,
+ nsDOMNavigationTiming* aTiming, Maybe<dom::ClientInfo>&& aInfo,
+ uint64_t aInnerWindowId, nsLoadFlags aLoadFlags,
+ nsContentPolicyType aContentPolicyType, bool aUrgentStart,
+ dom::ContentParent* aContentParent,
+ ObjectUpgradeHandler* aObjectUpgradeHandler, nsresult* aRv)
+ -> RefPtr<OpenPromise> {
+ LOG(("DocumentLoadListener [%p] OpenObject [uri=%s]", this,
+ aLoadState->URI()->GetSpecOrDefault().get()));
+
+ MOZ_ASSERT(!mIsDocumentLoad);
+
+ auto sandboxFlags = aLoadState->TriggeringSandboxFlags();
+
+ RefPtr<LoadInfo> 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<OpenPromise> {
+ 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<nsIContentSecurityPolicy> 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<nsDocShellLoadState> loadState = new nsDocShellLoadState(*aLoadState);
+ loadState->CalculateLoadURIFlags();
+
+ RefPtr<nsDOMNavigationTiming> 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<uint64_t> channelId = Nothing();
+
+ // Initial client info is only relevant for subdocument loads, which we're
+ // not supporting yet.
+ Maybe<dom::ClientInfo> 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> 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<nsIChannel> request = GetChannel();
+
+ RefPtr<BrowsingContextWebProgress> 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<nsIBrowser> browser;
+ if (RefPtr<Element> 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<DocumentLoadListener> load =
+ new DocumentLoadListener(aBrowsingContext, true);
+ RefPtr<DocumentLoadListener::OpenPromise> 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<DocumentLoadListener> 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<nsIRedirectChannelRegistrar> registrar =
+ RedirectChannelRegistrar::GetOrCreate();
+ uint64_t loadIdentifier = aLoadState->GetLoadIdentifier();
+ DebugOnly<nsresult> 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<nsIRedirectChannelRegistrar> registrar =
+ RedirectChannelRegistrar::GetOrCreate();
+
+ nsCOMPtr<nsIParentChannel> parentChannel;
+ registrar->GetParentChannel(aLoadIdent, getter_AddRefs(parentChannel));
+ RefPtr<DocumentLoadListener> 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<uint64_t> aChannelId)
+ -> RefPtr<OpenPromise> {
+ nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
+ RedirectChannelRegistrar::GetOrCreate();
+
+ nsCOMPtr<nsIParentChannel> parentChannel;
+ registrar->GetParentChannel(aLoadIdent, getter_AddRefs(parentChannel));
+ RefPtr<DocumentLoadListener> 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<nsHttpChannel> 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<uint32_t>(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<uint32_t>(aStatus),
+ static_cast<uint32_t>(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<uint32_t>(aRv)));
+ if (NS_FAILED(aRv)) {
+ FinishReplacementChannelSetup(aRv);
+ return;
+ }
+
+ // Wait for background channel ready on target channel
+ nsCOMPtr<nsIRedirectChannelRegistrar> redirectReg =
+ RedirectChannelRegistrar::GetOrCreate();
+ MOZ_ASSERT(redirectReg);
+
+ nsCOMPtr<nsIParentChannel> redirectParentChannel;
+ redirectReg->GetParentChannel(mRedirectChannelId,
+ getter_AddRefs(redirectParentChannel));
+ if (!redirectParentChannel) {
+ FinishReplacementChannelSetup(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsCOMPtr<nsIParentRedirectingChannel> 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<nsIRedirectChannelRegistrar> registrar =
+ RedirectChannelRegistrar::GetOrCreate();
+ MOZ_ASSERT(registrar);
+
+ nsCOMPtr<nsIParentChannel> 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<nsIParentChannel*>(this)));
+
+ redirectChannel->SetParentListener(mParentChannelListener);
+
+ ApplyPendingFunctions(redirectChannel);
+
+ if (!ResumeSuspendedChannel(redirectChannel)) {
+ nsCOMPtr<nsILoadGroup> 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<nsIParentChannel> 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<HttpChannelSecurityWarningReporter> reporter;
+ if (RefPtr<HttpChannelParent> httpParent = do_QueryObject(aChannel)) {
+ reporter = httpParent;
+ } else if (RefPtr<nsHttpChannel> 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<nsHttpChannel> 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<StreamListenerFunction> 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<EarlyHintConnectArgs>& aOutLinks) {
+ mEarlyHintsService.RegisterLinksAndGetConnectArgs(aCpId, aOutLinks);
+}
+
+void DocumentLoadListener::SerializeRedirectData(
+ RedirectToRealChannelArgs& aArgs, bool aIsCrossProcess,
+ uint32_t aRedirectFlags, uint32_t aLoadFlags, ContentParent* aParent,
+ nsTArray<EarlyHintConnectArgs>&& 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<nsILoadInfo> channelLoadInfo = mChannel->LoadInfo();
+ nsCOMPtr<nsIPrincipal> principalToInherit;
+ channelLoadInfo->GetPrincipalToInherit(getter_AddRefs(principalToInherit));
+
+ const RefPtr<nsHttpChannel> baseChannel = do_QueryObject(mChannel);
+
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(mChannel, loadContext);
+ nsCOMPtr<nsILoadInfo> 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<mozilla::net::LoadInfo*>(channelLoadInfo.get())->Clone();
+
+ redirectLoadInfo->AppendRedirectHistoryEntry(mChannel, true);
+ }
+
+ const Maybe<ClientInfo>& 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<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel)) {
+ MOZ_ALWAYS_SUCCEEDS(httpChannel->GetChannelId(&aArgs.channelId()));
+ }
+
+ aArgs.redirectMode() = nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW;
+ nsCOMPtr<nsIHttpChannelInternal> 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<nsIPropertyBag2> 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<nsIBrowser> 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
+ // <browser> element.
+ Element* browserElement = aBrowsingContext->Top()->GetEmbedderElement();
+ if (!browserElement) {
+ MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
+ ("Process Switch Abort: cannot get embedder element"));
+ return false;
+ }
+ nsCOMPtr<nsIBrowser> 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
+ // <browser>'s process behavior.
+ if (processBehavior ==
+ DocumentLoadListener::ProcessBehavior::PROCESS_BEHAVIOR_DISABLED) {
+ MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
+ ("Process Switch Abort: switch disabled by <browser>"));
+ return false;
+ }
+ if (!aParentWindow && processBehavior ==
+ DocumentLoadListener::ProcessBehavior::
+ PROCESS_BEHAVIOR_SUBFRAME_ONLY) {
+ MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
+ ("Process Switch Abort: toplevel switch disabled by <browser>"));
+ return false;
+ }
+
+ return true;
+}
+
+static RefPtr<dom::BrowsingContextCallbackReceivedPromise> SwitchToNewTab(
+ CanonicalBrowsingContext* aLoadingBrowsingContext, int32_t aWhere) {
+ MOZ_ASSERT(aWhere == nsIBrowserDOMWindow::OPEN_NEWTAB ||
+ aWhere == nsIBrowserDOMWindow::OPEN_NEWWINDOW,
+ "Unsupported open location");
+
+ auto promise =
+ MakeRefPtr<dom::BrowsingContextCallbackReceivedPromise::Private>(
+ __func__);
+
+ // Get the nsIBrowserDOMWindow for the given BrowsingContext's tab.
+ nsCOMPtr<nsIBrowserDOMWindow> 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<nsIPrincipal> triggeringPrincipal =
+ NullPrincipal::Create(aLoadingBrowsingContext->OriginAttributesRef());
+
+ RefPtr<nsOpenWindowInfo> 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<BrowsingContext> 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 <object>/<embed> load, we may be doing a document load at
+ // this point. We never need to do a process switch for a non-document
+ // <object> or <embed> 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
+ // <object> or <embed> 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 `<object>` or `<embed>`.
+ // Instead, check whether or not `parentWindow` is null.
+ RefPtr<CanonicalBrowsingContext> browsingContext =
+ GetLoadingBrowsingContext();
+ // If switching to a new tab, the final BC isn't a frame.
+ RefPtr<WindowGlobalParent> 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<nsIPrincipal> 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 <browser> to load in, which may be performed
+ // async.
+ if (switchToNewTab) {
+ SwitchToNewTab(browsingContext, where)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self = RefPtr{this},
+ options](const RefPtr<BrowsingContext>& 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 <object>/<embed> disabled"));
+ return false;
+ }
+
+ mObjectUpgradeHandler->UpgradeObjectLoad()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self = RefPtr{this}, options, parentWindow](
+ const RefPtr<CanonicalBrowsingContext>& 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<StreamFilterRequest> 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<PDocumentChannelParent::RedirectToRealChannelPromise>
+DocumentLoadListener::RedirectToParentProcess(uint32_t aRedirectFlags,
+ uint32_t aLoadFlags) {
+ // This is largely the same as ContentChild::RecvCrossProcessRedirect,
+ // except without needing to deserialize or create an nsIChildChannel.
+
+ RefPtr<nsDocShellLoadState> loadState;
+ nsDocShellLoadState::CreateFromPendingChannel(
+ mChannel, mLoadIdentifier, mRedirectChannelId, getter_AddRefs(loadState));
+
+ loadState->SetLoadFlags(mLoadStateExternalLoadFlags);
+ loadState->SetInternalLoadFlags(mLoadStateInternalLoadFlags);
+ loadState->SetLoadType(mLoadStateLoadType);
+ if (mLoadingSessionHistoryInfo) {
+ loadState->SetLoadingSessionHistoryInfo(*mLoadingSessionHistoryInfo);
+ }
+
+ // This is poorly named now.
+ RefPtr<ChildProcessChannelListener> processListener =
+ ChildProcessChannelListener::GetSingleton();
+
+ auto promise =
+ MakeRefPtr<PDocumentChannelParent::RedirectToRealChannelPromise::Private>(
+ __func__);
+ promise->UseDirectTaskDispatch(__func__);
+ auto resolve = [promise](nsresult aResult) {
+ promise->Resolve(aResult, __func__);
+ };
+
+ nsTArray<ipc::Endpoint<extensions::PStreamFilterParent>> endpoints;
+ processListener->OnChannelReady(loadState, mLoadIdentifier,
+ std::move(endpoints), mTiming,
+ std::move(resolve));
+
+ return promise;
+}
+
+RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise>
+DocumentLoadListener::RedirectToRealChannel(
+ uint32_t aRedirectFlags, uint32_t aLoadFlags,
+ const Maybe<ContentParent*>& aDestinationProcess,
+ nsTArray<ParentEndpoint>&& 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<nsIRedirectChannelRegistrar> registrar =
+ RedirectChannelRegistrar::GetOrCreate();
+ MOZ_ASSERT(registrar);
+ nsCOMPtr<nsIChannel> chan = mChannel;
+ if (nsCOMPtr<nsIViewSourceChannel> vsc = do_QueryInterface(chan)) {
+ chan = vsc->GetInnerChannel();
+ }
+ mRedirectChannelId = nsContentUtils::GenerateLoadIdentifier();
+ MOZ_ALWAYS_SUCCEEDS(registrar->RegisterChannel(chan, mRedirectChannelId));
+
+ if (aDestinationProcess) {
+ RefPtr<ContentParent> cp = *aDestinationProcess;
+ if (!cp) {
+ MOZ_ASSERT(aStreamFilterEndpoints.IsEmpty());
+ return RedirectToParentProcess(aRedirectFlags, aLoadFlags);
+ }
+
+ if (!cp->CanSend()) {
+ return PDocumentChannelParent::RedirectToRealChannelPromise::
+ CreateAndReject(ipc::ResponseRejectReason::SendError, __func__);
+ }
+
+ nsTArray<EarlyHintConnectArgs> 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<PDocumentChannelParent::RedirectToRealChannelPromise::Private>(
+ __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<ContentParent*>& aDestinationProcess,
+ nsTArray<StreamFilterRequest> 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<ParentEndpoint> 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<nsIURI> uri;
+ mChannel->GetURI(getter_AddRefs(uri));
+ if (uri && uri->SchemeIs("https")) {
+ newLoadFlags &= ~nsIRequest::INHIBIT_PERSISTENT_CACHING;
+ }
+
+ RefPtr<DocumentLoadListener> 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<WindowGlobalParent> 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 <object> or <embed> 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<CanonicalBrowsingContext> bc = GetDocumentBrowsingContext();
+ if (!bc) {
+ return false;
+ }
+
+ nsCOMPtr<nsIInputStream> newPostData;
+ nsCOMPtr<nsIURI> 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<nsDocShellLoadState> loadState = new nsDocShellLoadState(newURI);
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
+
+ nsCOMPtr<nsIContentSecurityPolicy> cspToInherit = loadInfo->GetCspToInherit();
+ loadState->SetCsp(cspToInherit);
+
+ nsCOMPtr<nsIPrincipal> 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<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
+ if (httpChannel) {
+ nsCOMPtr<nsIReferrerInfo> referrerInfo = httpChannel->GetReferrerInfo();
+ if (referrerInfo) {
+ ReferrerPolicy referrerPolicy = referrerInfo->ReferrerPolicy();
+ nsCOMPtr<nsIURI> originalReferrer = referrerInfo->GetOriginalReferrer();
+ if (originalReferrer) {
+ // Create new ReferrerInfo with the original referrer and the referrer
+ // policy.
+ nsCOMPtr<nsIReferrerInfo> 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<nsIMultiPartChannel> 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<nsHttpChannel> 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<nsILoadInfo> 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> 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<StreamFilterRequest> streamFilterRequests =
+ std::move(mStreamFilterRequests);
+ if (!mSupportsRedirectToRealChannel) {
+ RefPtr<BrowserParent> 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<nsIMultiPartChannel> 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<CanonicalBrowsingContext> 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<CanonicalBrowsingContext> 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<nsILoadInfo> loadInfoFromChannel = mChannel->LoadInfo();
+ MOZ_ASSERT(loadInfoFromChannel);
+ nsCOMPtr<nsIURI> 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<nsIHttpChannelInternal> 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<nsIURI> 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<nsIURI> uriBeingLoaded =
+ AntiTrackingUtils::MaybeGetDocumentURIBeingLoaded(mChannel);
+
+ RefPtr<MozPromise<bool, bool, false>> promise;
+ RefPtr<CanonicalBrowsingContext> bc =
+ mParentChannelListener->GetBrowsingContext();
+ nsCOMPtr<nsIWidget> widget =
+ bc ? bc->GetParentProcessWidgetContaining() : nullptr;
+ RefPtr<nsWindow> 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<nsIAsyncVerifyRedirectCallback> cb = aCallback;
+ promise->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [=](const MozPromise<bool, bool, false>::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<nsILoadInfo> channelLoadInfo = mChannel->LoadInfo();
+
+ nsCOMPtr<nsIURI> 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<nsIHttpChannelInternal> 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<ChildEndpointPromise> {
+ 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<nsIChannel> channel = mChannel;
+
+ RefPtr<BrowsingContextWebProgress> 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