summaryrefslogtreecommitdiffstats
path: root/netwerk/protocol/http/HttpChannelChild.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /netwerk/protocol/http/HttpChannelChild.cpp
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'netwerk/protocol/http/HttpChannelChild.cpp')
-rw-r--r--netwerk/protocol/http/HttpChannelChild.cpp3391
1 files changed, 3391 insertions, 0 deletions
diff --git a/netwerk/protocol/http/HttpChannelChild.cpp b/netwerk/protocol/http/HttpChannelChild.cpp
new file mode 100644
index 0000000000..393d0aa37d
--- /dev/null
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -0,0 +1,3391 @@
+/* -*- 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/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "mozilla/net/PBackgroundDataBridge.h"
+#include "nsHttp.h"
+#include "nsICacheEntry.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/PerfStats.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/ServiceWorkerUtils.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/dom/LinkStyle.h"
+#include "mozilla/extensions/StreamFilterParent.h"
+#include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/net/HttpChannelChild.h"
+#include "mozilla/net/UrlClassifierCommon.h"
+#include "mozilla/net/UrlClassifierFeatureFactory.h"
+
+#include "AltDataOutputStreamChild.h"
+#include "CookieServiceChild.h"
+#include "HttpBackgroundChannelChild.h"
+#include "NetworkMarker.h"
+#include "nsCOMPtr.h"
+#include "nsContentPolicyUtils.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsStringStream.h"
+#include "nsHttpChannel.h"
+#include "nsHttpHandler.h"
+#include "nsQueryObject.h"
+#include "nsNetUtil.h"
+#include "nsSerializationHelper.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/dom/PerformanceStorage.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/net/SocketProcessBridgeChild.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "SerializedLoadContext.h"
+#include "nsInputStreamPump.h"
+#include "nsContentSecurityManager.h"
+#include "nsICompressConvStats.h"
+#include "mozilla/dom/Document.h"
+#include "nsIScriptError.h"
+#include "nsISerialEventTarget.h"
+#include "nsRedirectHistoryEntry.h"
+#include "nsSocketTransportService2.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include "nsCORSListenerProxy.h"
+#include "nsIOService.h"
+
+#include <functional>
+
+using namespace mozilla::dom;
+using namespace mozilla::ipc;
+
+namespace mozilla::net {
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild
+//-----------------------------------------------------------------------------
+
+HttpChannelChild::HttpChannelChild()
+ : HttpAsyncAborter<HttpChannelChild>(this),
+ NeckoTargetHolder(nullptr),
+ mCacheEntryAvailable(false),
+ mAltDataCacheEntryAvailable(false),
+ mSendResumeAt(false),
+ mKeptAlive(false),
+ mIPCActorDeleted(false),
+ mSuspendSent(false),
+ mIsFirstPartOfMultiPart(false),
+ mIsLastPartOfMultiPart(false),
+ mSuspendForWaitCompleteRedirectSetup(false),
+ mRecvOnStartRequestSentCalled(false),
+ mSuspendedByWaitingForPermissionCookie(false),
+ mAlreadyReleased(false) {
+ LOG(("Creating HttpChannelChild @%p\n", this));
+
+ mChannelCreationTime = PR_Now();
+ mChannelCreationTimestamp = TimeStamp::Now();
+ mLastStatusReported =
+ mChannelCreationTimestamp; // in case we enable the profiler after Init()
+ mAsyncOpenTime = TimeStamp::Now();
+ mEventQ = new ChannelEventQueue(static_cast<nsIHttpChannel*>(this));
+
+ // Ensure that the cookie service is initialized before the first
+ // IPC HTTP channel is created.
+ // We require that the parent cookie service actor exists while
+ // processing HTTP responses.
+ RefPtr<CookieServiceChild> cookieService = CookieServiceChild::GetSingleton();
+}
+
+HttpChannelChild::~HttpChannelChild() {
+ LOG(("Destroying HttpChannelChild @%p\n", this));
+
+ // See HttpChannelChild::Release, HttpChannelChild should be always destroyed
+ // on the main thread.
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ if (mDoDiagnosticAssertWhenOnStopNotCalledOnDestroy && mAsyncOpenSucceeded &&
+ !mSuccesfullyRedirected && !LoadOnStopRequestCalled()) {
+ bool emptyBgChildQueue, nullBgChild;
+ {
+ MutexAutoLock lock(mBgChildMutex);
+ nullBgChild = !mBgChild;
+ emptyBgChildQueue = !nullBgChild && mBgChild->IsQueueEmpty();
+ }
+
+ uint32_t flags =
+ (mRedirectChannelChild ? 1 << 0 : 0) |
+ (mEventQ->IsEmpty() ? 1 << 1 : 0) | (nullBgChild ? 1 << 2 : 0) |
+ (emptyBgChildQueue ? 1 << 3 : 0) |
+ (LoadOnStartRequestCalled() ? 1 << 4 : 0) |
+ (mBackgroundChildQueueFinalState == BCKCHILD_EMPTY ? 1 << 5 : 0) |
+ (mBackgroundChildQueueFinalState == BCKCHILD_NON_EMPTY ? 1 << 6 : 0) |
+ (mRemoteChannelExistedAtCancel ? 1 << 7 : 0) |
+ (mEverHadBgChildAtAsyncOpen ? 1 << 8 : 0) |
+ (mEverHadBgChildAtConnectParent ? 1 << 9 : 0) |
+ (mCreateBackgroundChannelFailed ? 1 << 10 : 0) |
+ (mBgInitFailCallbackTriggered ? 1 << 11 : 0) |
+ (mCanSendAtCancel ? 1 << 12 : 0) | (!!mSuspendCount ? 1 << 13 : 0) |
+ (!!mCallOnResume ? 1 << 14 : 0);
+ MOZ_CRASH_UNSAFE_PRINTF(
+ "~HttpChannelChild, LoadOnStopRequestCalled()=false, mStatus=0x%08x, "
+ "mActorDestroyReason=%d, 20200717 flags=%u",
+ static_cast<uint32_t>(nsresult(mStatus)),
+ static_cast<int32_t>(mActorDestroyReason ? *mActorDestroyReason : -1),
+ flags);
+ }
+#endif
+
+ mEventQ->NotifyReleasingOwner();
+
+ ReleaseMainThreadOnlyReferences();
+}
+
+void HttpChannelChild::ReleaseMainThreadOnlyReferences() {
+ if (NS_IsMainThread()) {
+ // Already on main thread, let dtor to
+ // take care of releasing references
+ return;
+ }
+
+ NS_ReleaseOnMainThread("HttpChannelChild::mRedirectChannelChild",
+ mRedirectChannelChild.forget());
+}
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(HttpChannelChild)
+
+NS_IMETHODIMP_(MozExternalRefCountType) HttpChannelChild::Release() {
+ if (!NS_IsMainThread()) {
+ nsrefcnt count = mRefCnt;
+ nsresult rv = NS_DispatchToMainThread(NewNonOwningRunnableMethod(
+ "HttpChannelChild::Release", this, &HttpChannelChild::Release));
+
+ // Continue Release procedure if failed to dispatch to main thread.
+ if (!NS_WARN_IF(NS_FAILED(rv))) {
+ return count - 1;
+ }
+ }
+
+ nsrefcnt count = --mRefCnt;
+ MOZ_ASSERT(int32_t(count) >= 0, "dup release");
+
+ // Normally we Send_delete in OnStopRequest, but when we need to retain the
+ // remote channel for security info IPDL itself holds 1 reference, so we
+ // Send_delete when refCnt==1. But if !CanSend(), then there's nobody to send
+ // to, so we fall through.
+ if (mKeptAlive && count == 1 && CanSend()) {
+ NS_LOG_RELEASE(this, 1, "HttpChannelChild");
+ mKeptAlive = false;
+ // We send a message to the parent, which calls SendDelete, and then the
+ // child calling Send__delete__() to finally drop the refcount to 0.
+ TrySendDeletingChannel();
+ return 1;
+ }
+
+ if (count == 0) {
+ mRefCnt = 1; /* stabilize */
+
+ // We don't have a listener when AsyncOpen has failed or when this channel
+ // has been sucessfully redirected.
+ if (MOZ_LIKELY(LoadOnStartRequestCalled() && LoadOnStopRequestCalled()) ||
+ !mListener || mAlreadyReleased) {
+ NS_LOG_RELEASE(this, 0, "HttpChannelChild");
+ delete this;
+ return 0;
+ }
+
+ // This ensures that when the refcount goes to 0 again, we don't dispatch
+ // yet another runnable and get in a loop.
+ mAlreadyReleased = true;
+
+ // This makes sure we fulfill the stream listener contract all the time.
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = NS_ERROR_ABORT;
+ }
+
+ // Turn the stabilization refcount into a regular strong reference.
+
+ // 1) We tell refcount logging about the "stabilization" AddRef, which
+ // will become the reference for |channel|. We do this first so that we
+ // don't tell refcount logging that the refcount has dropped to zero, which
+ // it will interpret as destroying the object.
+ NS_LOG_ADDREF(this, 2, "HttpChannelChild", sizeof(*this));
+
+ // 2) We tell refcount logging about the original call to Release().
+ NS_LOG_RELEASE(this, 1, "HttpChannelChild");
+
+ // 3) Finally, we turn the reference into a regular smart pointer.
+ RefPtr<HttpChannelChild> channel = dont_AddRef(this);
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "~HttpChannelChild>DoNotifyListener",
+ [chan = std::move(channel)] { chan->DoNotifyListener(false); }));
+ // If NS_DispatchToMainThread failed then we're going to leak the runnable,
+ // and thus the channel, so there's no need to do anything else.
+ return mRefCnt;
+ }
+
+ NS_LOG_RELEASE(this, count, "HttpChannelChild");
+ return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(HttpChannelChild)
+ NS_INTERFACE_MAP_ENTRY(nsIRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIHttpChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICacheInfoChannel,
+ !mMultiPartID.isSome())
+ NS_INTERFACE_MAP_ENTRY(nsIResumableChannel)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
+ NS_INTERFACE_MAP_ENTRY(nsIClassOfService)
+ NS_INTERFACE_MAP_ENTRY(nsIProxiedChannel)
+ NS_INTERFACE_MAP_ENTRY(nsITraceableChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIChildChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIHttpChannelChild)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIMultiPartChannel, mMultiPartID.isSome())
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIThreadRetargetableRequest,
+ !mMultiPartID.isSome())
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(HttpChannelChild)
+NS_INTERFACE_MAP_END_INHERITING(HttpBaseChannel)
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::PHttpChannelChild
+//-----------------------------------------------------------------------------
+
+void HttpChannelChild::OnBackgroundChildReady(
+ HttpBackgroundChannelChild* aBgChild) {
+ LOG(("HttpChannelChild::OnBackgroundChildReady [this=%p, bgChild=%p]\n", this,
+ aBgChild));
+ MOZ_ASSERT(OnSocketThread());
+
+ {
+ MutexAutoLock lock(mBgChildMutex);
+
+ // mBgChild might be removed or replaced while the original background
+ // channel is inited on STS thread.
+ if (mBgChild != aBgChild) {
+ return;
+ }
+
+ MOZ_ASSERT(mBgInitFailCallback);
+ mBgInitFailCallback = nullptr;
+ }
+}
+
+void HttpChannelChild::OnBackgroundChildDestroyed(
+ HttpBackgroundChannelChild* aBgChild) {
+ LOG(("HttpChannelChild::OnBackgroundChildDestroyed [this=%p]\n", this));
+ // This function might be called during shutdown phase, so OnSocketThread()
+ // might return false even on STS thread. Use IsOnCurrentThreadInfallible()
+ // to get correct information.
+ MOZ_ASSERT(gSocketTransportService);
+ MOZ_ASSERT(gSocketTransportService->IsOnCurrentThreadInfallible());
+
+ nsCOMPtr<nsIRunnable> callback;
+ {
+ MutexAutoLock lock(mBgChildMutex);
+
+ // mBgChild might be removed or replaced while the original background
+ // channel is destroyed on STS thread.
+ if (aBgChild != mBgChild) {
+ return;
+ }
+
+ mBgChild = nullptr;
+ callback = std::move(mBgInitFailCallback);
+ }
+
+ if (callback) {
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mBgInitFailCallbackTriggered = true;
+#endif
+ nsCOMPtr<nsISerialEventTarget> neckoTarget = GetNeckoTarget();
+ neckoTarget->Dispatch(callback, NS_DISPATCH_NORMAL);
+ }
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvOnStartRequestSent() {
+ LOG(("HttpChannelChild::RecvOnStartRequestSent [this=%p]\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mRecvOnStartRequestSentCalled);
+
+ mRecvOnStartRequestSentCalled = true;
+
+ if (mSuspendedByWaitingForPermissionCookie) {
+ mSuspendedByWaitingForPermissionCookie = false;
+ mEventQ->Resume();
+ }
+ return IPC_OK();
+}
+
+void HttpChannelChild::ProcessOnStartRequest(
+ const nsHttpResponseHead& aResponseHead, const bool& aUseResponseHead,
+ const nsHttpHeaderArray& aRequestHeaders,
+ const HttpChannelOnStartRequestArgs& aArgs,
+ const HttpChannelAltDataStream& aAltData,
+ const TimeStamp& aOnStartRequestStartTime) {
+ LOG(("HttpChannelChild::ProcessOnStartRequest [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+
+ TimeStamp start = TimeStamp::Now();
+
+ mAltDataInputStream = DeserializeIPCStream(aAltData.altDataInputStream());
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this), aResponseHead,
+ aUseResponseHead, aRequestHeaders, aArgs, start]() {
+ TimeDuration delay = TimeStamp::Now() - start;
+ glean::networking::http_content_onstart_delay.AccumulateRawDuration(
+ delay);
+
+ self->OnStartRequest(aResponseHead, aUseResponseHead, aRequestHeaders,
+ aArgs);
+ }));
+}
+
+static void ResourceTimingStructArgsToTimingsStruct(
+ const ResourceTimingStructArgs& aArgs, TimingStruct& aTimings) {
+ aTimings.domainLookupStart = aArgs.domainLookupStart();
+ aTimings.domainLookupEnd = aArgs.domainLookupEnd();
+ aTimings.connectStart = aArgs.connectStart();
+ aTimings.tcpConnectEnd = aArgs.tcpConnectEnd();
+ aTimings.secureConnectionStart = aArgs.secureConnectionStart();
+ aTimings.connectEnd = aArgs.connectEnd();
+ aTimings.requestStart = aArgs.requestStart();
+ aTimings.responseStart = aArgs.responseStart();
+ aTimings.responseEnd = aArgs.responseEnd();
+ aTimings.transactionPending = aArgs.transactionPending();
+}
+
+void HttpChannelChild::OnStartRequest(
+ const nsHttpResponseHead& aResponseHead, const bool& aUseResponseHead,
+ const nsHttpHeaderArray& aRequestHeaders,
+ const HttpChannelOnStartRequestArgs& aArgs) {
+ LOG(("HttpChannelChild::OnStartRequest [this=%p]\n", this));
+
+ // If this channel was aborted by ActorDestroy, then there may be other
+ // OnStartRequest/OnStopRequest/OnDataAvailable IPC messages that need to
+ // be handled. In that case we just ignore them to avoid calling the listener
+ // twice.
+ if (LoadOnStartRequestCalled() && mIPCActorDeleted) {
+ return;
+ }
+
+ // Copy arguments only. It's possible to handle other IPC between
+ // OnStartRequest and DoOnStartRequest.
+ mComputedCrossOriginOpenerPolicy = aArgs.openerPolicy();
+
+ if (!mCanceled && NS_SUCCEEDED(mStatus)) {
+ mStatus = aArgs.channelStatus();
+ }
+
+ // Cookies headers should not be visible to the child process
+ MOZ_ASSERT(!aRequestHeaders.HasHeader(nsHttp::Cookie));
+ MOZ_ASSERT(!nsHttpResponseHead(aResponseHead).HasHeader(nsHttp::Set_Cookie));
+
+ if (aUseResponseHead && !mCanceled) {
+ mResponseHead = MakeUnique<nsHttpResponseHead>(aResponseHead);
+ }
+
+ mSecurityInfo = aArgs.securityInfo();
+
+ ipc::MergeParentLoadInfoForwarder(aArgs.loadInfoForwarder(), mLoadInfo);
+
+ mIsFromCache = aArgs.isFromCache();
+ mIsRacing = aArgs.isRacing();
+ mCacheEntryAvailable = aArgs.cacheEntryAvailable();
+ mCacheEntryId = aArgs.cacheEntryId();
+ mCacheFetchCount = aArgs.cacheFetchCount();
+ mProtocolVersion = aArgs.protocolVersion();
+ mCacheExpirationTime = aArgs.cacheExpirationTime();
+ mSelfAddr = aArgs.selfAddr();
+ mPeerAddr = aArgs.peerAddr();
+
+ mRedirectCount = aArgs.redirectCount();
+ mAvailableCachedAltDataType = aArgs.altDataType();
+ StoreDeliveringAltData(aArgs.deliveringAltData());
+ mAltDataLength = aArgs.altDataLength();
+ StoreResolvedByTRR(aArgs.isResolvedByTRR());
+ mEffectiveTRRMode = aArgs.effectiveTRRMode();
+ mTRRSkipReason = aArgs.trrSkipReason();
+
+ SetApplyConversion(aArgs.applyConversion());
+
+ StoreAfterOnStartRequestBegun(true);
+ StoreHasHTTPSRR(aArgs.hasHTTPSRR());
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ mCacheKey = aArgs.cacheKey();
+
+ StoreIsProxyUsed(aArgs.isProxyUsed());
+
+ // replace our request headers with what actually got sent in the parent
+ mRequestHead.SetHeaders(aRequestHeaders);
+
+ // Note: this is where we would notify "http-on-examine-response" observers.
+ // We have deliberately disabled this for child processes (see bug 806753)
+ //
+ // gHttpHandler->OnExamineResponse(this);
+
+ ResourceTimingStructArgsToTimingsStruct(aArgs.timing(), mTransactionTimings);
+
+ nsAutoCString cosString;
+ ClassOfService::ToString(mClassOfService, cosString);
+ if (!mAsyncOpenTime.IsNull() &&
+ !aArgs.timing().transactionPending().IsNull()) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::NETWORK_ASYNC_OPEN_CHILD_TO_TRANSACTION_PENDING_EXP_MS,
+ cosString, mAsyncOpenTime, aArgs.timing().transactionPending());
+ PerfStats::RecordMeasurement(
+ PerfStats::Metric::HttpChannelAsyncOpenToTransactionPending,
+ aArgs.timing().transactionPending() - mAsyncOpenTime);
+ }
+
+ const TimeStamp now = TimeStamp::Now();
+ if (!aArgs.timing().responseStart().IsNull()) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::NETWORK_RESPONSE_START_PARENT_TO_CONTENT_EXP_MS, cosString,
+ aArgs.timing().responseStart(), now);
+ PerfStats::RecordMeasurement(
+ PerfStats::Metric::HttpChannelResponseStartParentToContent,
+ now - aArgs.timing().responseStart());
+ }
+ if (!mOnStartRequestStartTime.IsNull()) {
+ PerfStats::RecordMeasurement(PerfStats::Metric::OnStartRequestToContent,
+ now - mOnStartRequestStartTime);
+ }
+
+ StoreAllRedirectsSameOrigin(aArgs.allRedirectsSameOrigin());
+
+ mMultiPartID = aArgs.multiPartID();
+ mIsFirstPartOfMultiPart = aArgs.isFirstPartOfMultiPart();
+ mIsLastPartOfMultiPart = aArgs.isLastPartOfMultiPart();
+
+ if (aArgs.overrideReferrerInfo()) {
+ // The arguments passed to SetReferrerInfoInternal here should mirror the
+ // arguments passed in
+ // nsHttpChannel::ReEvaluateReferrerAfterTrackingStatusIsKnown(), except for
+ // aRespectBeforeConnect which we pass false here since we're intentionally
+ // overriding the referrer after BeginConnect().
+ Unused << SetReferrerInfoInternal(aArgs.overrideReferrerInfo(), false, true,
+ false);
+ }
+
+ if (!aArgs.cookie().IsEmpty()) {
+ SetCookie(aArgs.cookie());
+ }
+
+ if (aArgs.shouldWaitForOnStartRequestSent() &&
+ !mRecvOnStartRequestSentCalled) {
+ LOG((" > pending DoOnStartRequest until RecvOnStartRequestSent\n"));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mEventQ->Suspend();
+ mSuspendedByWaitingForPermissionCookie = true;
+ mEventQ->PrependEvent(MakeUnique<NeckoTargetChannelFunctionEvent>(
+ this, [self = UnsafePtr<HttpChannelChild>(this)]() {
+ self->DoOnStartRequest(self);
+ }));
+ return;
+ }
+
+ // Remember whether HTTP3 is supported
+ if (mResponseHead) {
+ mSupportsHTTP3 =
+ nsHttpHandler::IsHttp3SupportedByServer(mResponseHead.get());
+ }
+
+ DoOnStartRequest(this);
+}
+
+void HttpChannelChild::ProcessOnAfterLastPart(const nsresult& aStatus) {
+ LOG(("HttpChannelChild::ProcessOnAfterLastPart [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this), aStatus]() {
+ self->OnAfterLastPart(aStatus);
+ }));
+}
+
+void HttpChannelChild::OnAfterLastPart(const nsresult& aStatus) {
+ if (LoadOnStopRequestCalled()) {
+ return;
+ }
+ StoreOnStopRequestCalled(true);
+
+ // notify "http-on-stop-connect" observers
+ gHttpHandler->OnStopRequest(this);
+
+ ReleaseListeners();
+
+ // If a preferred alt-data type was set, the parent would hold a reference to
+ // the cache entry in case the child calls openAlternativeOutputStream().
+ // (see nsHttpChannel::OnStopRequest)
+ if (!mPreferredCachedAltDataTypes.IsEmpty()) {
+ mAltDataCacheEntryAvailable = mCacheEntryAvailable;
+ }
+ mCacheEntryAvailable = false;
+
+ if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+ CleanupBackgroundChannel();
+
+ if (mLoadFlags & LOAD_DOCUMENT_URI) {
+ // Keep IPDL channel open, but only for updating security info.
+ // If IPDL is already closed, then do nothing.
+ if (CanSend()) {
+ mKeptAlive = true;
+ SendDocumentChannelCleanup(true);
+ }
+ } else {
+ // The parent process will respond by sending a DeleteSelf message and
+ // making sure not to send any more messages after that.
+ TrySendDeletingChannel();
+ }
+}
+
+void HttpChannelChild::DoOnStartRequest(nsIRequest* aRequest) {
+ nsresult rv;
+
+ LOG(("HttpChannelChild::DoOnStartRequest [this=%p]\n", this));
+
+ // We handle all the listener chaining before OnStartRequest at this moment.
+ // Prevent additional listeners being added to the chain after the request
+ // as started.
+ StoreTracingEnabled(false);
+
+ // mListener could be null if the redirect setup is not completed.
+ MOZ_ASSERT(mListener || LoadOnStartRequestCalled());
+ if (!mListener) {
+ Cancel(NS_ERROR_FAILURE);
+ return;
+ }
+
+ if (mListener) {
+ nsCOMPtr<nsIStreamListener> listener(mListener);
+ StoreOnStartRequestCalled(true);
+ rv = listener->OnStartRequest(aRequest);
+ } else {
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ StoreOnStartRequestCalled(true);
+
+ if (NS_FAILED(rv)) {
+ CancelWithReason(rv, "HttpChannelChild listener->OnStartRequest failed"_ns);
+ return;
+ }
+
+ nsCOMPtr<nsIStreamListener> listener;
+ rv = DoApplyContentConversions(mListener, getter_AddRefs(listener), nullptr);
+ if (NS_FAILED(rv)) {
+ CancelWithReason(rv,
+ "HttpChannelChild DoApplyContentConversions failed"_ns);
+ } else if (listener) {
+ mListener = listener;
+ mCompressListener = listener;
+ }
+}
+
+void HttpChannelChild::ProcessOnTransportAndData(
+ const nsresult& aChannelStatus, const nsresult& aTransportStatus,
+ const uint64_t& aOffset, const uint32_t& aCount, const nsACString& aData,
+ const TimeStamp& aOnDataAvailableStartTime) {
+ LOG(("HttpChannelChild::ProcessOnTransportAndData [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+ mEventQ->RunOrEnqueue(new ChannelFunctionEvent(
+ [self = UnsafePtr<HttpChannelChild>(this)]() {
+ return self->GetODATarget();
+ },
+ [self = UnsafePtr<HttpChannelChild>(this), aChannelStatus,
+ aTransportStatus, aOffset, aCount, aData = nsCString(aData),
+ aOnDataAvailableStartTime]() {
+ self->mOnDataAvailableStartTime = aOnDataAvailableStartTime;
+ self->OnTransportAndData(aChannelStatus, aTransportStatus, aOffset,
+ aCount, aData);
+ }));
+}
+
+void HttpChannelChild::OnTransportAndData(const nsresult& aChannelStatus,
+ const nsresult& aTransportStatus,
+ const uint64_t& aOffset,
+ const uint32_t& aCount,
+ const nsACString& aData) {
+ LOG(("HttpChannelChild::OnTransportAndData [this=%p]\n", this));
+
+ if (!mCanceled && NS_SUCCEEDED(mStatus)) {
+ mStatus = aChannelStatus;
+ }
+
+ if (mCanceled || NS_FAILED(mStatus)) {
+ return;
+ }
+
+ if (!mOnDataAvailableStartTime.IsNull()) {
+ PerfStats::RecordMeasurement(PerfStats::Metric::OnDataAvailableToContent,
+ TimeStamp::Now() - mOnDataAvailableStartTime);
+ }
+
+ // Hold queue lock throughout all three calls, else we might process a later
+ // necko msg in between them.
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ int64_t progressMax;
+ if (NS_FAILED(GetContentLength(&progressMax))) {
+ progressMax = -1;
+ }
+
+ const int64_t progress = aOffset + aCount;
+
+ // OnTransportAndData will be run on retargeted thread if applicable, however
+ // OnStatus/OnProgress event can only be fired on main thread. We need to
+ // dispatch the status/progress event handling back to main thread with the
+ // appropriate event target for networking.
+ if (NS_IsMainThread()) {
+ DoOnStatus(this, aTransportStatus);
+ DoOnProgress(this, progress, progressMax);
+ } else {
+ RefPtr<HttpChannelChild> self = this;
+ nsCOMPtr<nsISerialEventTarget> neckoTarget = GetNeckoTarget();
+ MOZ_ASSERT(neckoTarget);
+
+ DebugOnly<nsresult> rv = neckoTarget->Dispatch(
+ NS_NewRunnableFunction(
+ "net::HttpChannelChild::OnTransportAndData",
+ [self, aTransportStatus, progress, progressMax]() {
+ self->DoOnStatus(self, aTransportStatus);
+ self->DoOnProgress(self, progress, progressMax);
+ }),
+ NS_DISPATCH_NORMAL);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ // OnDataAvailable
+ //
+ // NOTE: the OnDataAvailable contract requires the client to read all the data
+ // in the inputstream. This code relies on that ('data' will go away after
+ // this function). Apparently the previous, non-e10s behavior was to actually
+ // support only reading part of the data, allowing later calls to read the
+ // rest.
+ nsCOMPtr<nsIInputStream> stringStream;
+ nsresult rv =
+ NS_NewByteInputStream(getter_AddRefs(stringStream),
+ Span(aData).To(aCount), NS_ASSIGNMENT_DEPEND);
+ if (NS_FAILED(rv)) {
+ CancelWithReason(rv, "HttpChannelChild NS_NewByteInputStream failed"_ns);
+ return;
+ }
+
+ DoOnDataAvailable(this, stringStream, aOffset, aCount);
+ stringStream->Close();
+
+ // TODO: Bug 1523916 backpressure needs to take into account if the data is
+ // coming from the main process or from the socket process via PBackground.
+ if (NeedToReportBytesRead()) {
+ mUnreportBytesRead += aCount;
+ if (mUnreportBytesRead >= gHttpHandler->SendWindowSize() >> 2) {
+ if (NS_IsMainThread()) {
+ Unused << SendBytesRead(mUnreportBytesRead);
+ } else {
+ // PHttpChannel connects to the main thread
+ RefPtr<HttpChannelChild> self = this;
+ int32_t bytesRead = mUnreportBytesRead;
+ nsCOMPtr<nsISerialEventTarget> neckoTarget = GetNeckoTarget();
+ MOZ_ASSERT(neckoTarget);
+
+ DebugOnly<nsresult> rv = neckoTarget->Dispatch(
+ NS_NewRunnableFunction("net::HttpChannelChild::SendBytesRead",
+ [self, bytesRead]() {
+ Unused << self->SendBytesRead(bytesRead);
+ }),
+ NS_DISPATCH_NORMAL);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ mUnreportBytesRead = 0;
+ }
+ }
+}
+
+bool HttpChannelChild::NeedToReportBytesRead() {
+ if (mCacheNeedToReportBytesReadInitialized) {
+ return mNeedToReportBytesRead;
+ }
+
+ // Might notify parent for partial cache, and the IPC message is ignored by
+ // parent.
+ int64_t contentLength = -1;
+ if (gHttpHandler->SendWindowSize() == 0 || mIsFromCache ||
+ NS_FAILED(GetContentLength(&contentLength)) ||
+ contentLength < gHttpHandler->SendWindowSize()) {
+ mNeedToReportBytesRead = false;
+ }
+
+ mCacheNeedToReportBytesReadInitialized = true;
+ return mNeedToReportBytesRead;
+}
+
+void HttpChannelChild::DoOnStatus(nsIRequest* aRequest, nsresult status) {
+ LOG(("HttpChannelChild::DoOnStatus [this=%p]\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mCanceled) return;
+
+ // cache the progress sink so we don't have to query for it each time.
+ if (!mProgressSink) GetCallback(mProgressSink);
+
+ // block status/progress after Cancel or OnStopRequest has been called,
+ // or if channel has LOAD_BACKGROUND set.
+ if (mProgressSink && NS_SUCCEEDED(mStatus) && LoadIsPending() &&
+ !(mLoadFlags & LOAD_BACKGROUND)) {
+ nsAutoCString host;
+ mURI->GetHost(host);
+ mProgressSink->OnStatus(aRequest, status,
+ NS_ConvertUTF8toUTF16(host).get());
+ }
+}
+
+void HttpChannelChild::DoOnProgress(nsIRequest* aRequest, int64_t progress,
+ int64_t progressMax) {
+ LOG(("HttpChannelChild::DoOnProgress [this=%p]\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mCanceled) return;
+
+ // cache the progress sink so we don't have to query for it each time.
+ if (!mProgressSink) GetCallback(mProgressSink);
+
+ // block status/progress after Cancel or OnStopRequest has been called,
+ // or if channel has LOAD_BACKGROUND set.
+ if (mProgressSink && NS_SUCCEEDED(mStatus) && LoadIsPending()) {
+ // OnProgress
+ //
+ if (progress > 0) {
+ mProgressSink->OnProgress(aRequest, progress, progressMax);
+ }
+ }
+
+ // mOnProgressEventSent indicates we have flushed all the
+ // progress events on the main thread. It is needed if
+ // we do not want to dispatch OnDataFinished before sending
+ // all of the progress updates.
+ if (progress == progressMax) {
+ mOnProgressEventSent = true;
+ }
+}
+
+void HttpChannelChild::DoOnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aStream,
+ uint64_t aOffset, uint32_t aCount) {
+ AUTO_PROFILER_LABEL("HttpChannelChild::DoOnDataAvailable", NETWORK);
+ LOG(("HttpChannelChild::DoOnDataAvailable [this=%p]\n", this));
+ if (mCanceled) return;
+
+ if (mListener) {
+ nsCOMPtr<nsIStreamListener> listener(mListener);
+ nsresult rv = listener->OnDataAvailable(aRequest, aStream, aOffset, aCount);
+ if (NS_FAILED(rv)) {
+ CancelOnMainThread(rv, "HttpChannelChild OnDataAvailable failed"_ns);
+ }
+ }
+}
+
+void HttpChannelChild::SendOnDataFinished(const nsresult& aChannelStatus) {
+ LOG(("HttpChannelChild::SendOnDataFinished [this=%p]\n", this));
+
+ if (mCanceled) return;
+
+ // we need to ensure we OnDataFinished only after all the progress
+ // updates are dispatched on the main thread
+ if (StaticPrefs::network_send_OnDataFinished_after_progress_updates() &&
+ !mOnProgressEventSent) {
+ return;
+ }
+
+ if (mListener) {
+ nsCOMPtr<nsIThreadRetargetableStreamListener> omtEventListener =
+ do_QueryInterface(mListener);
+ if (omtEventListener) {
+ LOG(
+ ("HttpChannelChild::SendOnDataFinished sending data end "
+ "notification[this=%p]\n",
+ this));
+ // We want to calculate the delta time between this call and
+ // ProcessOnStopRequest. Complicating things is that OnStopRequest
+ // could come first, and that it will run on a different thread, so
+ // we need to synchronize and lock data.
+ omtEventListener->OnDataFinished(aChannelStatus);
+ } else {
+ LOG(
+ ("HttpChannelChild::SendOnDataFinished missing "
+ "nsIThreadRetargetableStreamListener "
+ "implementation [this=%p]\n",
+ this));
+ }
+ }
+}
+
+class RecordStopRequestDelta final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RecordStopRequestDelta);
+
+ TimeStamp mOnStopRequestTime;
+ TimeStamp mOnDataFinishedTime;
+
+ private:
+ ~RecordStopRequestDelta() {
+ if (mOnDataFinishedTime.IsNull() || mOnStopRequestTime.IsNull()) {
+ return;
+ }
+
+ TimeDuration delta = (mOnStopRequestTime - mOnDataFinishedTime);
+ if (delta.ToMilliseconds() < 0) {
+ // Because Telemetry can't handle negatives
+ delta = -delta;
+ glean::networking::http_content_ondatafinished_to_onstop_delay_negative
+ .AccumulateRawDuration(delta);
+ } else {
+ glean::networking::http_content_ondatafinished_to_onstop_delay
+ .AccumulateRawDuration(delta);
+ }
+ }
+};
+
+void HttpChannelChild::ProcessOnStopRequest(
+ const nsresult& aChannelStatus, const ResourceTimingStructArgs& aTiming,
+ const nsHttpHeaderArray& aResponseTrailers,
+ nsTArray<ConsoleReportCollected>&& aConsoleReports, bool aFromSocketProcess,
+ const TimeStamp& aOnStopRequestStartTime) {
+ LOG(
+ ("HttpChannelChild::ProcessOnStopRequest [this=%p, "
+ "aFromSocketProcess=%d]\n",
+ this, aFromSocketProcess));
+ MOZ_ASSERT(OnSocketThread());
+ { // assign some of the members that would be accessed by the listeners
+ // upon getting OnDataFinished notications
+ MutexAutoLock lock(mOnDataFinishedMutex);
+ mTransferSize = aTiming.transferSize();
+ mEncodedBodySize = aTiming.encodedBodySize();
+ }
+
+ RefPtr<RecordStopRequestDelta> timing;
+ TimeStamp start = TimeStamp::Now();
+ if (StaticPrefs::network_send_OnDataFinished()) {
+ timing = new RecordStopRequestDelta;
+ mEventQ->RunOrEnqueue(new ChannelFunctionEvent(
+ [self = UnsafePtr<HttpChannelChild>(this)]() {
+ return self->GetODATarget();
+ },
+ [self = UnsafePtr<HttpChannelChild>(this), status = aChannelStatus,
+ start, timing]() {
+ TimeStamp now = TimeStamp::Now();
+ TimeDuration delay = now - start;
+ glean::networking::http_content_ondatafinished_delay
+ .AccumulateRawDuration(delay);
+ timing->mOnDataFinishedTime = now;
+ self->SendOnDataFinished(status);
+ }));
+ }
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this), aChannelStatus, aTiming,
+ aResponseTrailers,
+ consoleReports = CopyableTArray{aConsoleReports.Clone()},
+ aFromSocketProcess, start, timing]() mutable {
+ TimeStamp now = TimeStamp::Now();
+ TimeDuration delay = now - start;
+ glean::networking::http_content_onstop_delay.AccumulateRawDuration(
+ delay);
+ if (timing) {
+ timing->mOnStopRequestTime = now;
+ }
+ self->OnStopRequest(aChannelStatus, aTiming, aResponseTrailers);
+ if (!aFromSocketProcess) {
+ self->DoOnConsoleReport(std::move(consoleReports));
+ self->ContinueOnStopRequest();
+ }
+ }));
+}
+
+void HttpChannelChild::ProcessOnConsoleReport(
+ nsTArray<ConsoleReportCollected>&& aConsoleReports) {
+ LOG(("HttpChannelChild::ProcessOnConsoleReport [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this,
+ [self = UnsafePtr<HttpChannelChild>(this),
+ consoleReports = CopyableTArray{aConsoleReports.Clone()}]() mutable {
+ self->DoOnConsoleReport(std::move(consoleReports));
+ self->ContinueOnStopRequest();
+ }));
+}
+
+void HttpChannelChild::DoOnConsoleReport(
+ nsTArray<ConsoleReportCollected>&& aConsoleReports) {
+ if (aConsoleReports.IsEmpty()) {
+ return;
+ }
+
+ for (ConsoleReportCollected& report : aConsoleReports) {
+ if (report.propertiesFile() <
+ nsContentUtils::PropertiesFile::PropertiesFile_COUNT) {
+ AddConsoleReport(report.errorFlags(), report.category(),
+ nsContentUtils::PropertiesFile(report.propertiesFile()),
+ report.sourceFileURI(), report.lineNumber(),
+ report.columnNumber(), report.messageName(),
+ report.stringParams());
+ }
+ }
+ MaybeFlushConsoleReports();
+}
+
+void HttpChannelChild::RecordChannelCompletionDurationForEarlyHint() {
+ if (!mLoadGroup) {
+ return;
+ }
+
+ uint32_t earlyHintType = 0;
+ nsCOMPtr<nsIRequest> req;
+ Unused << mLoadGroup->GetDefaultLoadRequest(getter_AddRefs(req));
+ if (nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(req)) {
+ Unused << httpChannel->GetEarlyHintLinkType(&earlyHintType);
+ }
+
+ if (!earlyHintType) {
+ return;
+ }
+
+ nsAutoCString earlyHintKey;
+ if (mIsFromCache) {
+ earlyHintKey.Append("cache_"_ns);
+ } else {
+ earlyHintKey.Append("net_"_ns);
+ }
+ if (earlyHintType & LinkStyle::ePRECONNECT) {
+ earlyHintKey.Append("preconnect_"_ns);
+ }
+ if (earlyHintType & LinkStyle::ePRELOAD) {
+ earlyHintKey.Append("preload_"_ns);
+ earlyHintKey.Append(mEarlyHintPreloaderId ? "1"_ns : "0"_ns);
+ }
+
+ Telemetry::AccumulateTimeDelta(Telemetry::EH_PERF_CHANNEL_COMPLETION_TIME,
+ earlyHintKey, mAsyncOpenTime,
+ TimeStamp::Now());
+}
+
+void HttpChannelChild::OnStopRequest(
+ const nsresult& aChannelStatus, const ResourceTimingStructArgs& aTiming,
+ const nsHttpHeaderArray& aResponseTrailers) {
+ LOG(("HttpChannelChild::OnStopRequest [this=%p status=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(aChannelStatus)));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // If this channel was aborted by ActorDestroy, then there may be other
+ // OnStartRequest/OnStopRequest/OnDataAvailable IPC messages that need to
+ // be handled. In that case we just ignore them to avoid calling the listener
+ // twice.
+ if (LoadOnStopRequestCalled() && mIPCActorDeleted) {
+ return;
+ }
+
+ nsCOMPtr<nsICompressConvStats> conv = do_QueryInterface(mCompressListener);
+ if (conv) {
+ conv->GetDecodedDataLength(&mDecodedBodySize);
+ }
+
+ ResourceTimingStructArgsToTimingsStruct(aTiming, mTransactionTimings);
+
+ // Do not overwrite or adjust the original mAsyncOpenTime by timing.fetchStart
+ // We must use the original child process time in order to account for child
+ // side work and IPC transit overhead.
+ // XXX: This depends on TimeStamp being equivalent across processes.
+ // This is true for modern hardware but for older platforms it is not always
+ // true.
+
+ mRedirectStartTimeStamp = aTiming.redirectStart();
+ mRedirectEndTimeStamp = aTiming.redirectEnd();
+ // mTransferSize and mEncodedBodySize are set in ProcessOnStopRequest
+ // TODO: check if we need to move assignments of other members to
+ // ProcessOnStopRequest
+
+ mCacheReadStart = aTiming.cacheReadStart();
+ mCacheReadEnd = aTiming.cacheReadEnd();
+
+ const TimeStamp now = TimeStamp::Now();
+
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+ nsAutoCString contentType;
+ if (mResponseHead) {
+ mResponseHead->ContentType(contentType);
+ }
+ int32_t priority = PRIORITY_NORMAL;
+ GetPriority(&priority);
+ profiler_add_network_marker(
+ mURI, requestMethod, priority, mChannelId, NetworkLoadType::LOAD_STOP,
+ mLastStatusReported, now, mTransferSize, kCacheUnknown,
+ mLoadInfo->GetInnerWindowID(),
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0,
+ &mTransactionTimings, std::move(mSource),
+ Some(nsDependentCString(contentType.get())));
+ }
+
+ RecordChannelCompletionDurationForEarlyHint();
+
+ TimeDuration channelCompletionDuration = now - mAsyncOpenTime;
+ if (mIsFromCache) {
+ PerfStats::RecordMeasurement(PerfStats::Metric::HttpChannelCompletion_Cache,
+ channelCompletionDuration);
+ } else {
+ PerfStats::RecordMeasurement(
+ PerfStats::Metric::HttpChannelCompletion_Network,
+ channelCompletionDuration);
+ }
+ PerfStats::RecordMeasurement(PerfStats::Metric::HttpChannelCompletion,
+ channelCompletionDuration);
+
+ if (!aTiming.responseEnd().IsNull()) {
+ nsAutoCString cosString;
+ ClassOfService::ToString(mClassOfService, cosString);
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::NETWORK_RESPONSE_END_PARENT_TO_CONTENT_MS, cosString,
+ aTiming.responseEnd(), now);
+ PerfStats::RecordMeasurement(
+ PerfStats::Metric::HttpChannelResponseEndParentToContent,
+ now - aTiming.responseEnd());
+ }
+
+ if (!mOnStopRequestStartTime.IsNull()) {
+ PerfStats::RecordMeasurement(PerfStats::Metric::OnStopRequestToContent,
+ now - mOnStopRequestStartTime);
+ }
+
+ mResponseTrailers = MakeUnique<nsHttpHeaderArray>(aResponseTrailers);
+
+ DoPreOnStopRequest(aChannelStatus);
+
+ { // We must flush the queue before we Send__delete__
+ // (although we really shouldn't receive any msgs after OnStop),
+ // so make sure this goes out of scope before then.
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ DoOnStopRequest(this, aChannelStatus);
+ // DoOnStopRequest() calls ReleaseListeners()
+ }
+}
+
+void HttpChannelChild::ContinueOnStopRequest() {
+ // If we're a multi-part stream, then don't cleanup yet, and we'll do so
+ // in OnAfterLastPart.
+ if (mMultiPartID) {
+ LOG(
+ ("HttpChannelChild::OnStopRequest - Expecting future parts on a "
+ "multipart channel postpone cleaning up."));
+ return;
+ }
+
+ CollectMixedContentTelemetry();
+
+ CleanupBackgroundChannel();
+
+ // If there is a possibility we might want to write alt data to the cache
+ // entry, we keep the channel alive. We still send the DocumentChannelCleanup
+ // message but request the cache entry to be kept by the parent.
+ // If the channel has failed, the cache entry is in a non-writtable state and
+ // we want to release it to not block following consumers.
+ if (NS_SUCCEEDED(mStatus) && !mPreferredCachedAltDataTypes.IsEmpty()) {
+ mKeptAlive = true;
+ SendDocumentChannelCleanup(false); // don't clear cache entry
+ return;
+ }
+
+ if (mLoadFlags & LOAD_DOCUMENT_URI) {
+ // Keep IPDL channel open, but only for updating security info.
+ // If IPDL is already closed, then do nothing.
+ if (CanSend()) {
+ mKeptAlive = true;
+ SendDocumentChannelCleanup(true);
+ }
+ } else {
+ // The parent process will respond by sending a DeleteSelf message and
+ // making sure not to send any more messages after that.
+ TrySendDeletingChannel();
+ }
+}
+
+void HttpChannelChild::DoPreOnStopRequest(nsresult aStatus) {
+ AUTO_PROFILER_LABEL("HttpChannelChild::DoPreOnStopRequest", NETWORK);
+ LOG(("HttpChannelChild::DoPreOnStopRequest [this=%p status=%" PRIx32 "]\n",
+ this, static_cast<uint32_t>(aStatus)));
+ StoreIsPending(false);
+
+ MaybeReportTimingData();
+
+ if (!mCanceled && NS_SUCCEEDED(mStatus)) {
+ mStatus = aStatus;
+ }
+
+ CollectOMTTelemetry();
+}
+
+void HttpChannelChild::CollectOMTTelemetry() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Only collect telemetry for HTTP channel that is loaded successfully and
+ // completely.
+ if (mCanceled || NS_FAILED(mStatus)) {
+ return;
+ }
+
+ // Use content policy type to accumulate data by usage.
+ nsAutoCString key(
+ NS_CP_ContentTypeName(mLoadInfo->InternalContentPolicyType()));
+
+ Telemetry::AccumulateCategoricalKeyed(
+ key, static_cast<LABELS_HTTP_CHILD_OMT_STATS>(mOMTResult));
+}
+
+void HttpChannelChild::CollectMixedContentTelemetry() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsContentPolicyType internalLoadType;
+ mLoadInfo->GetInternalContentPolicyType(&internalLoadType);
+ bool statusIsSuccess = NS_SUCCEEDED(mStatus);
+ RefPtr<Document> doc;
+ mLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
+ if (!doc) {
+ return;
+ }
+ if (internalLoadType == nsIContentPolicy::TYPE_INTERNAL_IMAGE ||
+ internalLoadType == nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD) {
+ if (mLoadInfo->GetBrowserUpgradeInsecureRequests()) {
+ doc->SetUseCounter(
+ statusIsSuccess
+ ? eUseCounter_custom_MixedContentUpgradedImageSuccess
+ : eUseCounter_custom_MixedContentUpgradedImageFailure);
+ } else if (mLoadInfo->GetBrowserWouldUpgradeInsecureRequests()) {
+ doc->SetUseCounter(
+ statusIsSuccess
+ ? eUseCounter_custom_MixedContentNotUpgradedImageSuccess
+ : eUseCounter_custom_MixedContentNotUpgradedImageFailure);
+ }
+ return;
+ }
+ if (internalLoadType == nsIContentPolicy::TYPE_INTERNAL_VIDEO) {
+ if (mLoadInfo->GetBrowserUpgradeInsecureRequests()) {
+ doc->SetUseCounter(
+ statusIsSuccess
+ ? eUseCounter_custom_MixedContentUpgradedVideoSuccess
+ : eUseCounter_custom_MixedContentUpgradedVideoFailure);
+ } else if (mLoadInfo->GetBrowserWouldUpgradeInsecureRequests()) {
+ doc->SetUseCounter(
+ statusIsSuccess
+ ? eUseCounter_custom_MixedContentNotUpgradedVideoSuccess
+ : eUseCounter_custom_MixedContentNotUpgradedVideoFailure);
+ }
+ return;
+ }
+ if (internalLoadType == nsIContentPolicy::TYPE_INTERNAL_AUDIO) {
+ if (mLoadInfo->GetBrowserUpgradeInsecureRequests()) {
+ doc->SetUseCounter(
+ statusIsSuccess
+ ? eUseCounter_custom_MixedContentUpgradedAudioSuccess
+ : eUseCounter_custom_MixedContentUpgradedAudioFailure);
+ } else if (mLoadInfo->GetBrowserWouldUpgradeInsecureRequests()) {
+ doc->SetUseCounter(
+ statusIsSuccess
+ ? eUseCounter_custom_MixedContentNotUpgradedAudioSuccess
+ : eUseCounter_custom_MixedContentNotUpgradedAudioFailure);
+ }
+ }
+}
+
+void HttpChannelChild::DoOnStopRequest(nsIRequest* aRequest,
+ nsresult aChannelStatus) {
+ AUTO_PROFILER_LABEL("HttpChannelChild::DoOnStopRequest", NETWORK);
+ LOG(("HttpChannelChild::DoOnStopRequest [this=%p]\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!LoadIsPending());
+
+ auto checkForBlockedContent = [&]() {
+ // NB: We use aChannelStatus here instead of mStatus because if there was an
+ // nsCORSListenerProxy on this request, it will override the tracking
+ // protection's return value.
+ if (UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(
+ aChannelStatus) ||
+ aChannelStatus == NS_ERROR_MALWARE_URI ||
+ aChannelStatus == NS_ERROR_UNWANTED_URI ||
+ aChannelStatus == NS_ERROR_BLOCKED_URI ||
+ aChannelStatus == NS_ERROR_HARMFUL_URI ||
+ aChannelStatus == NS_ERROR_PHISHING_URI) {
+ nsCString list, provider, fullhash;
+
+ nsresult rv = GetMatchedList(list);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ rv = GetMatchedProvider(provider);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ rv = GetMatchedFullHash(fullhash);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ UrlClassifierCommon::SetBlockedContent(this, aChannelStatus, list,
+ provider, fullhash);
+ }
+ };
+ checkForBlockedContent();
+
+ MaybeLogCOEPError(aChannelStatus);
+
+ // See bug 1587686. If the redirect setup is not completed, the post-redirect
+ // channel will be not opened and mListener will be null.
+ MOZ_ASSERT(mListener || !LoadWasOpened());
+ if (!mListener) {
+ return;
+ }
+
+ MOZ_ASSERT(!LoadOnStopRequestCalled(),
+ "We should not call OnStopRequest twice");
+
+ if (mListener) {
+ nsCOMPtr<nsIStreamListener> listener(mListener);
+ StoreOnStopRequestCalled(true);
+ listener->OnStopRequest(aRequest, mStatus);
+ }
+ StoreOnStopRequestCalled(true);
+
+ // If we're a multi-part stream, then don't cleanup yet, and we'll do so
+ // in OnAfterLastPart.
+ if (mMultiPartID) {
+ LOG(
+ ("HttpChannelChild::DoOnStopRequest - Expecting future parts on a "
+ "multipart channel not releasing listeners."));
+ StoreOnStopRequestCalled(false);
+ StoreOnStartRequestCalled(false);
+ return;
+ }
+
+ // notify "http-on-stop-connect" observers
+ gHttpHandler->OnStopRequest(this);
+
+ ReleaseListeners();
+
+ // If a preferred alt-data type was set, the parent would hold a reference to
+ // the cache entry in case the child calls openAlternativeOutputStream().
+ // (see nsHttpChannel::OnStopRequest)
+ if (!mPreferredCachedAltDataTypes.IsEmpty()) {
+ mAltDataCacheEntryAvailable = mCacheEntryAvailable;
+ }
+ mCacheEntryAvailable = false;
+
+ if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+}
+
+void HttpChannelChild::ProcessOnProgress(const int64_t& aProgress,
+ const int64_t& aProgressMax) {
+ MOZ_ASSERT(OnSocketThread());
+ LOG(("HttpChannelChild::ProcessOnProgress [this=%p]\n", this));
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this,
+ [self = UnsafePtr<HttpChannelChild>(this), aProgress, aProgressMax]() {
+ AutoEventEnqueuer ensureSerialDispatch(self->mEventQ);
+ self->DoOnProgress(self, aProgress, aProgressMax);
+ }));
+}
+
+void HttpChannelChild::ProcessOnStatus(const nsresult& aStatus) {
+ MOZ_ASSERT(OnSocketThread());
+ LOG(("HttpChannelChild::ProcessOnStatus [this=%p]\n", this));
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this), aStatus]() {
+ AutoEventEnqueuer ensureSerialDispatch(self->mEventQ);
+ self->DoOnStatus(self, aStatus);
+ }));
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvFailedAsyncOpen(
+ const nsresult& aStatus) {
+ LOG(("HttpChannelChild::RecvFailedAsyncOpen [this=%p]\n", this));
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this), aStatus]() {
+ self->FailedAsyncOpen(aStatus);
+ }));
+ return IPC_OK();
+}
+
+// We need to have an implementation of this function just so that we can keep
+// all references to mCallOnResume of type HttpChannelChild: it's not OK in C++
+// to set a member function ptr to a base class function.
+void HttpChannelChild::HandleAsyncAbort() {
+ HttpAsyncAborter<HttpChannelChild>::HandleAsyncAbort();
+
+ // Ignore all the messages from background channel after channel aborted.
+ CleanupBackgroundChannel();
+}
+
+void HttpChannelChild::FailedAsyncOpen(const nsresult& status) {
+ LOG(("HttpChannelChild::FailedAsyncOpen [this=%p status=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(status)));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Might be called twice in race condition in theory.
+ // (one by RecvFailedAsyncOpen, another by
+ // HttpBackgroundChannelChild::ActorFailed)
+ if (LoadOnStartRequestCalled()) {
+ return;
+ }
+
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = status;
+ }
+
+ // We're already being called from IPDL, therefore already "async"
+ HandleAsyncAbort();
+
+ if (CanSend()) {
+ TrySendDeletingChannel();
+ }
+}
+
+void HttpChannelChild::CleanupBackgroundChannel() {
+ MutexAutoLock lock(mBgChildMutex);
+
+ AUTO_PROFILER_LABEL("HttpChannelChild::CleanupBackgroundChannel", NETWORK);
+ LOG(("HttpChannelChild::CleanupBackgroundChannel [this=%p bgChild=%p]\n",
+ this, mBgChild.get()));
+
+ mBgInitFailCallback = nullptr;
+
+ if (!mBgChild) {
+ return;
+ }
+
+ RefPtr<HttpBackgroundChannelChild> bgChild = std::move(mBgChild);
+
+ MOZ_RELEASE_ASSERT(gSocketTransportService);
+ if (!OnSocketThread()) {
+ gSocketTransportService->Dispatch(
+ NewRunnableMethod("HttpBackgroundChannelChild::OnChannelClosed",
+ bgChild,
+ &HttpBackgroundChannelChild::OnChannelClosed),
+ NS_DISPATCH_NORMAL);
+ } else {
+ bgChild->OnChannelClosed();
+ }
+}
+
+void HttpChannelChild::DoNotifyListenerCleanup() {
+ LOG(("HttpChannelChild::DoNotifyListenerCleanup [this=%p]\n", this));
+}
+
+void HttpChannelChild::DoAsyncAbort(nsresult aStatus) {
+ Unused << AsyncAbort(aStatus);
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvDeleteSelf() {
+ LOG(("HttpChannelChild::RecvDeleteSelf [this=%p]\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // The redirection is vetoed. No need to suspend the event queue.
+ if (mSuspendForWaitCompleteRedirectSetup) {
+ mSuspendForWaitCompleteRedirectSetup = false;
+ mEventQ->Resume();
+ }
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this,
+ [self = UnsafePtr<HttpChannelChild>(this)]() { self->DeleteSelf(); }));
+ return IPC_OK();
+}
+
+void HttpChannelChild::DeleteSelf() { Send__delete__(this); }
+
+void HttpChannelChild::NotifyOrReleaseListeners(nsresult rv) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (NS_SUCCEEDED(rv) ||
+ (LoadOnStartRequestCalled() && LoadOnStopRequestCalled())) {
+ ReleaseListeners();
+ return;
+ }
+
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = rv;
+ }
+
+ // This is enough what we need. Undelivered notifications will be pushed.
+ // DoNotifyListener ensures the call to ReleaseListeners when done.
+ DoNotifyListener();
+}
+
+void HttpChannelChild::DoNotifyListener(bool aUseEventQueue) {
+ LOG(("HttpChannelChild::DoNotifyListener this=%p", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // In case nsHttpChannel::OnStartRequest wasn't called (e.g. due to flag
+ // LOAD_ONLY_IF_MODIFIED) we want to set LoadAfterOnStartRequestBegun() to
+ // true before notifying listener.
+ if (!LoadAfterOnStartRequestBegun()) {
+ StoreAfterOnStartRequestBegun(true);
+ }
+
+ if (mListener && !LoadOnStartRequestCalled()) {
+ nsCOMPtr<nsIStreamListener> listener = mListener;
+ // avoid reentrancy bugs by setting this now
+ StoreOnStartRequestCalled(true);
+ listener->OnStartRequest(this);
+ }
+ StoreOnStartRequestCalled(true);
+
+ if (aUseEventQueue) {
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this)] {
+ self->ContinueDoNotifyListener();
+ }));
+ } else {
+ ContinueDoNotifyListener();
+ }
+}
+
+void HttpChannelChild::ContinueDoNotifyListener() {
+ LOG(("HttpChannelChild::ContinueDoNotifyListener this=%p", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Make sure IsPending is set to false. At this moment we are done from
+ // the point of view of our consumer and we have to report our self
+ // as not-pending.
+ StoreIsPending(false);
+
+ if (mListener && !LoadOnStopRequestCalled()) {
+ nsCOMPtr<nsIStreamListener> listener = mListener;
+ StoreOnStopRequestCalled(true);
+ listener->OnStopRequest(this, mStatus);
+ }
+ StoreOnStopRequestCalled(true);
+
+ // notify "http-on-stop-request" observers
+ gHttpHandler->OnStopRequest(this);
+
+ // This channel has finished its job, potentially release any tail-blocked
+ // requests with this.
+ RemoveAsNonTailRequest();
+
+ // We have to make sure to drop the references to listeners and callbacks
+ // no longer needed.
+ ReleaseListeners();
+
+ DoNotifyListenerCleanup();
+
+ // If this is a navigation, then we must let the docshell flush the reports
+ // to the console later. The LoadDocument() is pointing at the detached
+ // document that started the navigation. We want to show the reports on the
+ // new document. Otherwise the console is wiped and the user never sees
+ // the information.
+ if (!IsNavigation()) {
+ if (mLoadGroup) {
+ FlushConsoleReports(mLoadGroup);
+ } else {
+ RefPtr<dom::Document> doc;
+ mLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
+ FlushConsoleReports(doc);
+ }
+ }
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvReportSecurityMessage(
+ const nsAString& messageTag, const nsAString& messageCategory) {
+ DebugOnly<nsresult> rv = AddSecurityMessage(messageTag, messageCategory);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvRedirect1Begin(
+ const uint32_t& aRegistrarId, nsIURI* aNewUri,
+ const uint32_t& aNewLoadFlags, const uint32_t& aRedirectFlags,
+ const ParentLoadInfoForwarderArgs& aLoadInfoForwarder,
+ const nsHttpResponseHead& aResponseHead,
+ nsITransportSecurityInfo* aSecurityInfo, const uint64_t& aChannelId,
+ const NetAddr& aOldPeerAddr, const ResourceTimingStructArgs& aTiming) {
+ // TODO: handle security info
+ LOG(("HttpChannelChild::RecvRedirect1Begin [this=%p]\n", this));
+ // We set peer address of child to the old peer,
+ // Then it will be updated to new peer in OnStartRequest
+ mPeerAddr = aOldPeerAddr;
+
+ // Cookies headers should not be visible to the child process
+ MOZ_ASSERT(!nsHttpResponseHead(aResponseHead).HasHeader(nsHttp::Set_Cookie));
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this), aRegistrarId,
+ newUri = RefPtr{aNewUri}, aNewLoadFlags, aRedirectFlags,
+ aLoadInfoForwarder, aResponseHead,
+ aSecurityInfo = nsCOMPtr{aSecurityInfo}, aChannelId, aTiming]() {
+ self->Redirect1Begin(aRegistrarId, newUri, aNewLoadFlags,
+ aRedirectFlags, aLoadInfoForwarder, aResponseHead,
+ aSecurityInfo, aChannelId, aTiming);
+ }));
+ return IPC_OK();
+}
+
+nsresult HttpChannelChild::SetupRedirect(nsIURI* uri,
+ const nsHttpResponseHead* responseHead,
+ const uint32_t& redirectFlags,
+ nsIChannel** outChannel) {
+ LOG(("HttpChannelChild::SetupRedirect [this=%p]\n", this));
+
+ if (mCanceled) {
+ return NS_ERROR_ABORT;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIIOService> ioService;
+ rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> newChannel;
+ nsCOMPtr<nsILoadInfo> redirectLoadInfo =
+ CloneLoadInfoForRedirect(uri, redirectFlags);
+ rv = NS_NewChannelInternal(getter_AddRefs(newChannel), uri, redirectLoadInfo,
+ nullptr, // PerformanceStorage
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_NORMAL, ioService);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We won't get OnStartRequest, set cookies here.
+ mResponseHead = MakeUnique<nsHttpResponseHead>(*responseHead);
+
+ bool rewriteToGET = HttpBaseChannel::ShouldRewriteRedirectToGET(
+ mResponseHead->Status(), mRequestHead.ParsedMethod());
+
+ rv = SetupReplacementChannel(uri, newChannel, !rewriteToGET, redirectFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mRedirectChannelChild = do_QueryInterface(newChannel);
+ newChannel.forget(outChannel);
+
+ return NS_OK;
+}
+
+void HttpChannelChild::Redirect1Begin(
+ const uint32_t& registrarId, nsIURI* newOriginalURI,
+ const uint32_t& newLoadFlags, const uint32_t& redirectFlags,
+ const ParentLoadInfoForwarderArgs& loadInfoForwarder,
+ const nsHttpResponseHead& responseHead,
+ nsITransportSecurityInfo* securityInfo, const uint64_t& channelId,
+ const ResourceTimingStructArgs& timing) {
+ nsresult rv;
+
+ LOG(("HttpChannelChild::Redirect1Begin [this=%p]\n", this));
+
+ MOZ_ASSERT(newOriginalURI, "newOriginalURI should not be null");
+
+ ipc::MergeParentLoadInfoForwarder(loadInfoForwarder, mLoadInfo);
+ ResourceTimingStructArgsToTimingsStruct(timing, mTransactionTimings);
+
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+ nsAutoCString contentType;
+ responseHead.ContentType(contentType);
+
+ profiler_add_network_marker(
+ mURI, requestMethod, mPriority, mChannelId,
+ NetworkLoadType::LOAD_REDIRECT, mLastStatusReported, TimeStamp::Now(),
+ 0, kCacheUnknown, mLoadInfo->GetInnerWindowID(),
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0,
+ &mTransactionTimings, std::move(mSource),
+ Some(nsDependentCString(contentType.get())), newOriginalURI,
+ redirectFlags, channelId);
+ }
+
+ mSecurityInfo = securityInfo;
+
+ nsCOMPtr<nsIChannel> newChannel;
+ rv = SetupRedirect(newOriginalURI, &responseHead, redirectFlags,
+ getter_AddRefs(newChannel));
+
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_ALWAYS_SUCCEEDS(newChannel->SetLoadFlags(newLoadFlags));
+
+ if (mRedirectChannelChild) {
+ // Set the channelId allocated in parent to the child instance
+ nsCOMPtr<nsIHttpChannel> httpChannel =
+ do_QueryInterface(mRedirectChannelChild);
+ if (httpChannel) {
+ rv = httpChannel->SetChannelId(channelId);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ mRedirectChannelChild->ConnectParent(registrarId);
+ }
+
+ nsCOMPtr<nsISerialEventTarget> target = GetNeckoTarget();
+ MOZ_ASSERT(target);
+
+ rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, redirectFlags,
+ target);
+ }
+
+ if (NS_FAILED(rv)) OnRedirectVerifyCallback(rv);
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvRedirect3Complete() {
+ LOG(("HttpChannelChild::RecvRedirect3Complete [this=%p]\n", this));
+ nsCOMPtr<nsIChannel> redirectChannel =
+ do_QueryInterface(mRedirectChannelChild);
+ MOZ_ASSERT(redirectChannel);
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this), redirectChannel]() {
+ nsresult rv = NS_OK;
+ Unused << self->GetStatus(&rv);
+ if (NS_FAILED(rv)) {
+ // Pre-redirect channel was canceled. Call |HandleAsyncAbort|, so
+ // mListener's OnStart/StopRequest can be called. Nothing else will
+ // trigger these notification after this point.
+ // We do this before |CompleteRedirectSetup|, so post-redirect channel
+ // stays unopened and we also make sure that OnStart/StopRequest won't
+ // be called twice.
+ self->HandleAsyncAbort();
+
+ nsCOMPtr<nsIHttpChannelChild> chan =
+ do_QueryInterface(redirectChannel);
+ RefPtr<HttpChannelChild> httpChannelChild =
+ static_cast<HttpChannelChild*>(chan.get());
+ if (httpChannelChild) {
+ // For sending an IPC message to parent channel so that the loading
+ // can be cancelled.
+ Unused << httpChannelChild->CancelWithReason(
+ rv, "HttpChannelChild Redirect3 failed"_ns);
+
+ // The post-redirect channel could still get OnStart/StopRequest IPC
+ // messages from parent, but the mListener is still null. So, we
+ // call |DoNotifyListener| to pretend that OnStart/StopRequest are
+ // already called.
+ httpChannelChild->DoNotifyListener();
+ }
+ return;
+ }
+
+ self->Redirect3Complete();
+ }));
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvRedirectFailed(
+ const nsresult& status) {
+ LOG(("HttpChannelChild::RecvRedirectFailed this=%p status=%X\n", this,
+ static_cast<uint32_t>(status)));
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this), status]() {
+ nsCOMPtr<nsIRedirectResultListener> vetoHook;
+ self->GetCallback(vetoHook);
+ if (vetoHook) {
+ vetoHook->OnRedirectResult(status);
+ }
+
+ if (RefPtr<HttpChannelChild> httpChannelChild =
+ do_QueryObject(self->mRedirectChannelChild)) {
+ // For sending an IPC message to parent channel so that the loading
+ // can be cancelled.
+ Unused << httpChannelChild->CancelWithReason(
+ status, "HttpChannelChild RecvRedirectFailed"_ns);
+
+ // The post-redirect channel could still get OnStart/StopRequest IPC
+ // messages from parent, but the mListener is still null. So, we
+ // call |DoNotifyListener| to pretend that OnStart/StopRequest are
+ // already called.
+ httpChannelChild->DoNotifyListener();
+ }
+ }));
+
+ return IPC_OK();
+}
+
+void HttpChannelChild::ProcessNotifyClassificationFlags(
+ uint32_t aClassificationFlags, bool aIsThirdParty) {
+ LOG(
+ ("HttpChannelChild::ProcessNotifyClassificationFlags thirdparty=%d "
+ "flags=%" PRIu32 " [this=%p]\n",
+ static_cast<int>(aIsThirdParty), aClassificationFlags, this));
+ MOZ_ASSERT(OnSocketThread());
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this), aClassificationFlags,
+ aIsThirdParty]() {
+ self->AddClassificationFlags(aClassificationFlags, aIsThirdParty);
+ }));
+}
+
+void HttpChannelChild::ProcessSetClassifierMatchedInfo(
+ const nsACString& aList, const nsACString& aProvider,
+ const nsACString& aFullHash) {
+ LOG(("HttpChannelChild::ProcessSetClassifierMatchedInfo [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this,
+ [self = UnsafePtr<HttpChannelChild>(this), aList = nsCString(aList),
+ aProvider = nsCString(aProvider), aFullHash = nsCString(aFullHash)]() {
+ self->SetMatchedInfo(aList, aProvider, aFullHash);
+ }));
+}
+
+void HttpChannelChild::ProcessSetClassifierMatchedTrackingInfo(
+ const nsACString& aLists, const nsACString& aFullHashes) {
+ LOG(("HttpChannelChild::ProcessSetClassifierMatchedTrackingInfo [this=%p]\n",
+ this));
+ MOZ_ASSERT(OnSocketThread());
+
+ nsTArray<nsCString> lists, fullhashes;
+ for (const nsACString& token : aLists.Split(',')) {
+ lists.AppendElement(token);
+ }
+ for (const nsACString& token : aFullHashes.Split(',')) {
+ fullhashes.AppendElement(token);
+ }
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this),
+ lists = CopyableTArray{std::move(lists)},
+ fullhashes = CopyableTArray{std::move(fullhashes)}]() {
+ self->SetMatchedTrackingInfo(lists, fullhashes);
+ }));
+}
+
+// Completes the redirect and cleans up the old channel.
+void HttpChannelChild::Redirect3Complete() {
+ LOG(("HttpChannelChild::Redirect3Complete [this=%p]\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Using an error as the default so that when we fail to forward this redirect
+ // to the target channel, we make sure to notify the current listener from
+ // CleanupRedirectingChannel.
+ nsresult rv = NS_BINDING_ABORTED;
+
+ nsCOMPtr<nsIRedirectResultListener> vetoHook;
+ GetCallback(vetoHook);
+ if (vetoHook) {
+ vetoHook->OnRedirectResult(NS_OK);
+ }
+
+ // Chrome channel has been AsyncOpen'd. Reflect this in child.
+ if (mRedirectChannelChild) {
+ rv = mRedirectChannelChild->CompleteRedirectSetup(mListener);
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mSuccesfullyRedirected = NS_SUCCEEDED(rv);
+#endif
+ }
+
+ CleanupRedirectingChannel(rv);
+}
+
+void HttpChannelChild::CleanupRedirectingChannel(nsresult rv) {
+ // Redirecting to new channel: shut this down and init new channel
+ if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, NS_BINDING_ABORTED);
+
+ if (NS_SUCCEEDED(rv)) {
+ mLoadInfo->AppendRedirectHistoryEntry(this, false);
+ } else {
+ NS_WARNING("CompleteRedirectSetup failed, HttpChannelChild already open?");
+ }
+
+ // Release ref to new channel.
+ mRedirectChannelChild = nullptr;
+
+ NotifyOrReleaseListeners(rv);
+ CleanupBackgroundChannel();
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIChildChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::ConnectParent(uint32_t registrarId) {
+ LOG(("HttpChannelChild::ConnectParent [this=%p, id=%" PRIu32 "]\n", this,
+ registrarId));
+ MOZ_ASSERT(NS_IsMainThread());
+ mozilla::dom::BrowserChild* browserChild = nullptr;
+ nsCOMPtr<nsIBrowserChild> iBrowserChild;
+ GetCallback(iBrowserChild);
+ if (iBrowserChild) {
+ browserChild =
+ static_cast<mozilla::dom::BrowserChild*>(iBrowserChild.get());
+ }
+
+ if (browserChild && !browserChild->IPCOpen()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ContentChild* cc = static_cast<ContentChild*>(gNeckoChild->Manager());
+ if (cc->IsShuttingDown()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ HttpBaseChannel::SetDocshellUserAgentOverride();
+
+ // This must happen before the constructor message is sent. Otherwise messages
+ // from the parent could arrive quickly and be delivered to the wrong event
+ // target.
+ SetEventTarget();
+
+ if (browserChild) {
+ MOZ_ASSERT(browserChild->WebNavigation());
+ if (BrowsingContext* bc = browserChild->GetBrowsingContext()) {
+ mBrowserId = bc->BrowserId();
+ }
+ }
+
+ HttpChannelConnectArgs connectArgs(registrarId);
+ if (!gNeckoChild->SendPHttpChannelConstructor(
+ this, browserChild, IPC::SerializedLoadContext(this), connectArgs)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ {
+ MutexAutoLock lock(mBgChildMutex);
+
+ MOZ_ASSERT(!mBgChild);
+ MOZ_ASSERT(!mBgInitFailCallback);
+
+ mBgInitFailCallback = NewRunnableMethod<nsresult>(
+ "HttpChannelChild::OnRedirectVerifyCallback", this,
+ &HttpChannelChild::OnRedirectVerifyCallback, NS_ERROR_FAILURE);
+
+ RefPtr<HttpBackgroundChannelChild> bgChild =
+ new HttpBackgroundChannelChild();
+
+ MOZ_RELEASE_ASSERT(gSocketTransportService);
+
+ RefPtr<HttpChannelChild> self = this;
+ nsresult rv = gSocketTransportService->Dispatch(
+ NewRunnableMethod<RefPtr<HttpChannelChild>>(
+ "HttpBackgroundChannelChild::Init", bgChild,
+ &HttpBackgroundChannelChild::Init, std::move(self)),
+ NS_DISPATCH_NORMAL);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mBgChild = std::move(bgChild);
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mEverHadBgChildAtConnectParent = true;
+#endif
+ }
+
+ // Should wait for CompleteRedirectSetup to set the listener.
+ mEventQ->Suspend();
+ MOZ_ASSERT(!mSuspendForWaitCompleteRedirectSetup);
+ mSuspendForWaitCompleteRedirectSetup = true;
+
+ // Connect to socket process after mEventQ is suspended.
+ MaybeConnectToSocketProcess();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::CompleteRedirectSetup(nsIStreamListener* aListener) {
+ LOG(("HttpChannelChild::CompleteRedirectSetup [this=%p]\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ NS_ENSURE_TRUE(!LoadIsPending(), NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!LoadWasOpened(), NS_ERROR_ALREADY_OPENED);
+
+ // Resume the suspension in ConnectParent.
+ auto eventQueueResumeGuard = MakeScopeExit([&] {
+ MOZ_ASSERT(mSuspendForWaitCompleteRedirectSetup);
+ mEventQ->Resume();
+ mSuspendForWaitCompleteRedirectSetup = false;
+ });
+
+ /*
+ * No need to check for cancel: we don't get here if nsHttpChannel canceled
+ * before AsyncOpen(); if it's canceled after that, OnStart/Stop will just
+ * get called with error code as usual. So just setup mListener and make the
+ * channel reflect AsyncOpen'ed state.
+ */
+
+ mLastStatusReported = TimeStamp::Now();
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+
+ profiler_add_network_marker(
+ mURI, requestMethod, mPriority, mChannelId, NetworkLoadType::LOAD_START,
+ mChannelCreationTimestamp, mLastStatusReported, 0, kCacheUnknown,
+ mLoadInfo->GetInnerWindowID(),
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0);
+ }
+ StoreIsPending(true);
+ StoreWasOpened(true);
+ mListener = aListener;
+
+ // add ourselves to the load group.
+ if (mLoadGroup) mLoadGroup->AddRequest(this, nullptr);
+
+ // We already have an open IPDL connection to the parent. If on-modify-request
+ // listeners or load group observers canceled us, let the parent handle it
+ // and send it back to us naturally.
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIAsyncVerifyRedirectCallback
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::OnRedirectVerifyCallback(nsresult aResult) {
+ LOG(("HttpChannelChild::OnRedirectVerifyCallback [this=%p]\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIURI> redirectURI;
+
+ DebugOnly<nsresult> rv = NS_OK;
+
+ nsCOMPtr<nsIHttpChannel> newHttpChannel =
+ do_QueryInterface(mRedirectChannelChild);
+
+ if (NS_SUCCEEDED(aResult) && !mRedirectChannelChild) {
+ // mRedirectChannelChild doesn't exist means we're redirecting to a protocol
+ // that doesn't implement nsIChildChannel. The redirect result should be set
+ // as failed by veto listeners and shouldn't enter this condition. As the
+ // last resort, we synthesize the error result as NS_ERROR_DOM_BAD_URI here
+ // to let nsHttpChannel::ContinueProcessResponse2 know it's redirecting to
+ // another protocol and throw an error.
+ LOG((" redirecting to a protocol that doesn't implement nsIChildChannel"));
+ aResult = NS_ERROR_DOM_BAD_URI;
+ }
+
+ nsCOMPtr<nsIReferrerInfo> referrerInfo;
+ if (newHttpChannel) {
+ // Must not be called until after redirect observers called.
+ newHttpChannel->SetOriginalURI(mOriginalURI);
+ referrerInfo = newHttpChannel->GetReferrerInfo();
+ }
+
+ RequestHeaderTuples emptyHeaders;
+ RequestHeaderTuples* headerTuples = &emptyHeaders;
+ nsLoadFlags loadFlags = 0;
+ Maybe<CorsPreflightArgs> corsPreflightArgs;
+
+ nsCOMPtr<nsIHttpChannelChild> newHttpChannelChild =
+ do_QueryInterface(mRedirectChannelChild);
+ if (newHttpChannelChild && NS_SUCCEEDED(aResult)) {
+ rv = newHttpChannelChild->AddCookiesToRequest();
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = newHttpChannelChild->GetClientSetRequestHeaders(&headerTuples);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ newHttpChannelChild->GetClientSetCorsPreflightParameters(corsPreflightArgs);
+ }
+
+ if (NS_SUCCEEDED(aResult)) {
+ // Note: this is where we would notify "http-on-modify-response" observers.
+ // We have deliberately disabled this for child processes (see bug 806753)
+ //
+ // After we verify redirect, nsHttpChannel may hit the network: must give
+ // "http-on-modify-request" observers the chance to cancel before that.
+ // base->CallOnModifyRequestObservers();
+
+ nsCOMPtr<nsIHttpChannelInternal> newHttpChannelInternal =
+ do_QueryInterface(mRedirectChannelChild);
+ if (newHttpChannelInternal) {
+ Unused << newHttpChannelInternal->GetApiRedirectToURI(
+ getter_AddRefs(redirectURI));
+ }
+
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(mRedirectChannelChild);
+ if (request) {
+ request->GetLoadFlags(&loadFlags);
+ }
+ }
+
+ uint32_t sourceRequestBlockingReason = 0;
+ mLoadInfo->GetRequestBlockingReason(&sourceRequestBlockingReason);
+
+ Maybe<ChildLoadInfoForwarderArgs> targetLoadInfoForwarder;
+ nsCOMPtr<nsIChannel> newChannel = do_QueryInterface(mRedirectChannelChild);
+ if (newChannel) {
+ ChildLoadInfoForwarderArgs args;
+ nsCOMPtr<nsILoadInfo> loadInfo = newChannel->LoadInfo();
+ LoadInfoToChildLoadInfoForwarder(loadInfo, &args);
+ targetLoadInfoForwarder.emplace(args);
+ }
+
+ if (CanSend()) {
+ SendRedirect2Verify(aResult, *headerTuples, sourceRequestBlockingReason,
+ targetLoadInfoForwarder, loadFlags, referrerInfo,
+ redirectURI, corsPreflightArgs);
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP HttpChannelChild::SetCanceledReason(const nsACString& aReason) {
+ return SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP HttpChannelChild::GetCanceledReason(nsACString& aReason) {
+ return GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP
+HttpChannelChild::CancelWithReason(nsresult aStatus,
+ const nsACString& aReason) {
+ return CancelWithReasonImpl(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+HttpChannelChild::Cancel(nsresult aStatus) {
+ LOG(("HttpChannelChild::Cancel [this=%p, status=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(aStatus)));
+ // only logging on parent is necessary
+ Maybe<nsCString> logStack = CallingScriptLocationString();
+ Maybe<nsCString> logOnParent;
+ if (logStack.isSome()) {
+ logOnParent = Some(""_ns);
+ logOnParent->AppendPrintf(
+ "[this=%p] cancelled call in child process from script: %s", this,
+ logStack->get());
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mCanceled) {
+ // If this cancel occurs before nsHttpChannel has been set up, AsyncOpen
+ // is responsible for cleaning up.
+ mCanceled = true;
+ mStatus = aStatus;
+
+ bool remoteChannelExists = RemoteChannelExists();
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mCanSendAtCancel = CanSend();
+ mRemoteChannelExistedAtCancel = remoteChannelExists;
+#endif
+
+ if (remoteChannelExists) {
+ SendCancel(aStatus, mLoadInfo->GetRequestBlockingReason(),
+ mCanceledReason, logOnParent);
+ } else if (MOZ_UNLIKELY(!LoadOnStartRequestCalled() ||
+ !LoadOnStopRequestCalled())) {
+ Unused << AsyncAbort(mStatus);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::Suspend() {
+ LOG(("HttpChannelChild::Suspend [this=%p, mSuspendCount=%" PRIu32 "\n", this,
+ mSuspendCount + 1));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ LogCallingScriptLocation(this);
+
+ // SendSuspend only once, when suspend goes from 0 to 1.
+ // Don't SendSuspend at all if we're diverting callbacks to the parent;
+ // suspend will be called at the correct time in the parent itself.
+ if (!mSuspendCount++) {
+ if (RemoteChannelExists()) {
+ SendSuspend();
+ mSuspendSent = true;
+ }
+ }
+ mEventQ->Suspend();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::Resume() {
+ LOG(("HttpChannelChild::Resume [this=%p, mSuspendCount=%" PRIu32 "\n", this,
+ mSuspendCount - 1));
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED);
+
+ LogCallingScriptLocation(this);
+
+ nsresult rv = NS_OK;
+
+ // SendResume only once, when suspend count drops to 0.
+ // Don't SendResume at all if we're diverting callbacks to the parent (unless
+ // suspend was sent earlier); otherwise, resume will be called at the correct
+ // time in the parent itself.
+ if (!--mSuspendCount) {
+ if (RemoteChannelExists() && mSuspendSent) {
+ SendResume();
+ }
+ if (mCallOnResume) {
+ nsCOMPtr<nsISerialEventTarget> neckoTarget = GetNeckoTarget();
+ MOZ_ASSERT(neckoTarget);
+
+ RefPtr<HttpChannelChild> self = this;
+ std::function<nsresult(HttpChannelChild*)> callOnResume = nullptr;
+ std::swap(callOnResume, mCallOnResume);
+ rv = neckoTarget->Dispatch(
+ NS_NewRunnableFunction(
+ "net::HttpChannelChild::mCallOnResume",
+ [callOnResume, self{std::move(self)}]() { callOnResume(self); }),
+ NS_DISPATCH_NORMAL);
+ }
+ }
+ mEventQ->Resume();
+
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) {
+ NS_ENSURE_ARG_POINTER(aSecurityInfo);
+ *aSecurityInfo = do_AddRef(mSecurityInfo).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::AsyncOpen(nsIStreamListener* aListener) {
+ LOG(("HttpChannelChild::AsyncOpen [this=%p uri=%s]\n", this, mSpec.get()));
+
+ nsresult rv = AsyncOpenInternal(aListener);
+ if (NS_FAILED(rv)) {
+ uint32_t blockingReason = 0;
+ mLoadInfo->GetRequestBlockingReason(&blockingReason);
+ LOG(
+ ("HttpChannelChild::AsyncOpen failed [this=%p rv=0x%08x "
+ "blocking-reason=%u]\n",
+ this, static_cast<uint32_t>(rv), blockingReason));
+
+ gHttpHandler->OnFailedOpeningRequest(this);
+ }
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mAsyncOpenSucceeded = NS_SUCCEEDED(rv);
+#endif
+ return rv;
+}
+
+nsresult HttpChannelChild::AsyncOpenInternal(nsIStreamListener* aListener) {
+ nsresult rv;
+
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ ReleaseListeners();
+ return rv;
+ }
+
+ MOZ_ASSERT(
+ mLoadInfo->GetSecurityMode() == 0 ||
+ mLoadInfo->GetInitialSecurityCheckDone() ||
+ (mLoadInfo->GetSecurityMode() ==
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL &&
+ mLoadInfo->GetLoadingPrincipal() &&
+ mLoadInfo->GetLoadingPrincipal()->IsSystemPrincipal()),
+ "security flags in loadInfo but doContentSecurityCheck() not called");
+
+ LogCallingScriptLocation(this);
+
+ if (!mLoadGroup && !mCallbacks) {
+ // If no one called SetLoadGroup or SetNotificationCallbacks, the private
+ // state has not been updated on PrivateBrowsingChannel (which we derive
+ // from) Hence, we have to call UpdatePrivateBrowsing() here
+ UpdatePrivateBrowsing();
+ }
+
+#ifdef DEBUG
+ AssertPrivateBrowsingId();
+#endif
+
+ if (mCanceled) {
+ ReleaseListeners();
+ return mStatus;
+ }
+
+ NS_ENSURE_TRUE(gNeckoChild != nullptr, NS_ERROR_FAILURE);
+ NS_ENSURE_ARG_POINTER(listener);
+ NS_ENSURE_TRUE(!LoadIsPending(), NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!LoadWasOpened(), NS_ERROR_ALREADY_OPENED);
+
+ if (MaybeWaitForUploadStreamNormalization(listener, nullptr)) {
+ return NS_OK;
+ }
+
+ if (!LoadAsyncOpenTimeOverriden()) {
+ mAsyncOpenTime = TimeStamp::Now();
+ }
+
+ // Port checked in parent, but duplicate here so we can return with error
+ // immediately
+ rv = NS_CheckPortSafety(mURI);
+ if (NS_FAILED(rv)) {
+ ReleaseListeners();
+ return rv;
+ }
+
+ nsAutoCString cookie;
+ if (NS_SUCCEEDED(mRequestHead.GetHeader(nsHttp::Cookie, cookie))) {
+ mUserSetCookieHeader = cookie;
+ }
+
+ DebugOnly<nsresult> check = AddCookiesToRequest();
+ MOZ_ASSERT(NS_SUCCEEDED(check));
+
+ //
+ // NOTE: From now on we must return NS_OK; all errors must be handled via
+ // OnStart/OnStopRequest
+ //
+
+ // We notify "http-on-opening-request" observers in the child
+ // process so that devtools can capture a stack trace at the
+ // appropriate spot. See bug 806753 for some information about why
+ // other http-* notifications are disabled in child processes.
+ gHttpHandler->OnOpeningRequest(this);
+
+ mLastStatusReported = TimeStamp::Now();
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+
+ profiler_add_network_marker(
+ mURI, requestMethod, mPriority, mChannelId, NetworkLoadType::LOAD_START,
+ mChannelCreationTimestamp, mLastStatusReported, 0, kCacheUnknown,
+ mLoadInfo->GetInnerWindowID(),
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0);
+ }
+ StoreIsPending(true);
+ StoreWasOpened(true);
+ mListener = listener;
+
+ if (mCanceled) {
+ // We may have been canceled already, either by on-modify-request
+ // listeners or by load group observers; in that case, don't create IPDL
+ // connection. See nsHttpChannel::AsyncOpen().
+ ReleaseListeners();
+ return mStatus;
+ }
+
+ // Set user agent override from docshell
+ HttpBaseChannel::SetDocshellUserAgentOverride();
+
+ rv = ContinueAsyncOpen();
+ if (NS_FAILED(rv)) {
+ ReleaseListeners();
+ }
+ return rv;
+}
+
+// Assigns an nsISerialEventTarget to our IPDL actor so that IPC messages are
+// sent to the correct DocGroup/TabGroup.
+void HttpChannelChild::SetEventTarget() {
+ MutexAutoLock lock(mEventTargetMutex);
+ mNeckoTarget = GetMainThreadSerialEventTarget();
+}
+
+already_AddRefed<nsISerialEventTarget> HttpChannelChild::GetNeckoTarget() {
+ nsCOMPtr<nsISerialEventTarget> target;
+ {
+ MutexAutoLock lock(mEventTargetMutex);
+ target = mNeckoTarget;
+ }
+
+ if (!target) {
+ target = GetMainThreadSerialEventTarget();
+ }
+ return target.forget();
+}
+
+already_AddRefed<nsIEventTarget> HttpChannelChild::GetODATarget() {
+ nsCOMPtr<nsIEventTarget> target;
+ {
+ MutexAutoLock lock(mEventTargetMutex);
+ if (mODATarget) {
+ target = mODATarget;
+ } else {
+ target = mNeckoTarget;
+ }
+ }
+
+ if (!target) {
+ target = GetMainThreadSerialEventTarget();
+ }
+ return target.forget();
+}
+
+nsresult HttpChannelChild::ContinueAsyncOpen() {
+ nsresult rv;
+ //
+ // Send request to the chrome process...
+ //
+
+ mozilla::dom::BrowserChild* browserChild = nullptr;
+ nsCOMPtr<nsIBrowserChild> iBrowserChild;
+ GetCallback(iBrowserChild);
+ if (iBrowserChild) {
+ browserChild =
+ static_cast<mozilla::dom::BrowserChild*>(iBrowserChild.get());
+ }
+
+ // This id identifies the inner window's top-level document,
+ // which changes on every new load or navigation.
+ uint64_t contentWindowId = 0;
+ TimeStamp navigationStartTimeStamp;
+ if (browserChild) {
+ MOZ_ASSERT(browserChild->WebNavigation());
+ if (RefPtr<Document> document = browserChild->GetTopLevelDocument()) {
+ contentWindowId = document->InnerWindowID();
+ nsDOMNavigationTiming* navigationTiming = document->GetNavigationTiming();
+ if (navigationTiming) {
+ navigationStartTimeStamp =
+ navigationTiming->GetNavigationStartTimeStamp();
+ }
+ }
+ if (BrowsingContext* bc = browserChild->GetBrowsingContext()) {
+ mBrowserId = bc->BrowserId();
+ }
+ }
+ SetTopLevelContentWindowId(contentWindowId);
+
+ if (browserChild && !browserChild->IPCOpen()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ContentChild* cc = static_cast<ContentChild*>(gNeckoChild->Manager());
+ if (cc->IsShuttingDown()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // add ourselves to the load group.
+ if (mLoadGroup) {
+ mLoadGroup->AddRequest(this, nullptr);
+ }
+
+ HttpChannelOpenArgs openArgs;
+ // No access to HttpChannelOpenArgs members, but they each have a
+ // function with the struct name that returns a ref.
+ openArgs.uri() = mURI;
+ openArgs.original() = mOriginalURI;
+ openArgs.doc() = mDocumentURI;
+ openArgs.apiRedirectTo() = mAPIRedirectToURI;
+ openArgs.loadFlags() = mLoadFlags;
+ openArgs.requestHeaders() = mClientSetRequestHeaders;
+ mRequestHead.Method(openArgs.requestMethod());
+ openArgs.preferredAlternativeTypes() = mPreferredCachedAltDataTypes.Clone();
+ openArgs.referrerInfo() = mReferrerInfo;
+
+ if (mUploadStream) {
+ MOZ_ALWAYS_TRUE(SerializeIPCStream(do_AddRef(mUploadStream),
+ openArgs.uploadStream(),
+ /* aAllowLazy */ false));
+ }
+
+ Maybe<CorsPreflightArgs> optionalCorsPreflightArgs;
+ GetClientSetCorsPreflightParameters(optionalCorsPreflightArgs);
+
+ // NB: This call forces us to cache mTopWindowURI if we haven't already.
+ nsCOMPtr<nsIURI> uri;
+ GetTopWindowURI(mURI, getter_AddRefs(uri));
+
+ openArgs.topWindowURI() = mTopWindowURI;
+
+ openArgs.preflightArgs() = optionalCorsPreflightArgs;
+
+ openArgs.uploadStreamHasHeaders() = LoadUploadStreamHasHeaders();
+ openArgs.priority() = mPriority;
+ openArgs.classOfService() = mClassOfService;
+ openArgs.redirectionLimit() = mRedirectionLimit;
+ openArgs.allowSTS() = LoadAllowSTS();
+ openArgs.thirdPartyFlags() = LoadThirdPartyFlags();
+ openArgs.resumeAt() = mSendResumeAt;
+ openArgs.startPos() = mStartPos;
+ openArgs.entityID() = mEntityID;
+ openArgs.allowSpdy() = LoadAllowSpdy();
+ openArgs.allowHttp3() = LoadAllowHttp3();
+ openArgs.allowAltSvc() = LoadAllowAltSvc();
+ openArgs.beConservative() = LoadBeConservative();
+ openArgs.bypassProxy() = BypassProxy();
+ openArgs.tlsFlags() = mTlsFlags;
+ openArgs.initialRwin() = mInitialRwin;
+
+ openArgs.cacheKey() = mCacheKey;
+
+ openArgs.blockAuthPrompt() = LoadBlockAuthPrompt();
+
+ openArgs.allowStaleCacheContent() = LoadAllowStaleCacheContent();
+ openArgs.preferCacheLoadOverBypass() = LoadPreferCacheLoadOverBypass();
+
+ openArgs.contentTypeHint() = mContentTypeHint;
+
+ rv = mozilla::ipc::LoadInfoToLoadInfoArgs(mLoadInfo, &openArgs.loadInfo());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ EnsureRequestContextID();
+ openArgs.requestContextID() = mRequestContextID;
+
+ openArgs.requestMode() = mRequestMode;
+ openArgs.redirectMode() = mRedirectMode;
+
+ openArgs.channelId() = mChannelId;
+
+ openArgs.integrityMetadata() = mIntegrityMetadata;
+
+ openArgs.contentWindowId() = contentWindowId;
+ openArgs.browserId() = mBrowserId;
+
+ LOG(("HttpChannelChild::ContinueAsyncOpen this=%p gid=%" PRIu64
+ " browser id=%" PRIx64,
+ this, mChannelId, mBrowserId));
+
+ openArgs.launchServiceWorkerStart() = mLaunchServiceWorkerStart;
+ openArgs.launchServiceWorkerEnd() = mLaunchServiceWorkerEnd;
+ openArgs.dispatchFetchEventStart() = mDispatchFetchEventStart;
+ openArgs.dispatchFetchEventEnd() = mDispatchFetchEventEnd;
+ openArgs.handleFetchEventStart() = mHandleFetchEventStart;
+ openArgs.handleFetchEventEnd() = mHandleFetchEventEnd;
+
+ openArgs.forceMainDocumentChannel() = LoadForceMainDocumentChannel();
+
+ openArgs.navigationStartTimeStamp() = navigationStartTimeStamp;
+ openArgs.earlyHintPreloaderId() = mEarlyHintPreloaderId;
+
+ openArgs.classicScriptHintCharset() = mClassicScriptHintCharset;
+
+ openArgs.isUserAgentHeaderModified() = LoadIsUserAgentHeaderModified();
+
+ RefPtr<Document> doc;
+ mLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
+
+ if (doc) {
+ nsAutoString documentCharacterSet;
+ doc->GetCharacterSet(documentCharacterSet);
+ openArgs.documentCharacterSet() = documentCharacterSet;
+ }
+
+ // This must happen before the constructor message is sent. Otherwise messages
+ // from the parent could arrive quickly and be delivered to the wrong event
+ // target.
+ SetEventTarget();
+
+ if (!gNeckoChild->SendPHttpChannelConstructor(
+ this, browserChild, IPC::SerializedLoadContext(this), openArgs)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ {
+ MutexAutoLock lock(mBgChildMutex);
+
+ MOZ_RELEASE_ASSERT(gSocketTransportService);
+
+ // Service worker might use the same HttpChannelChild to do async open
+ // twice. Need to disconnect with previous background channel before
+ // creating the new one, to prevent receiving further notification
+ // from it.
+ if (mBgChild) {
+ RefPtr<HttpBackgroundChannelChild> prevBgChild = std::move(mBgChild);
+ gSocketTransportService->Dispatch(
+ NewRunnableMethod("HttpBackgroundChannelChild::OnChannelClosed",
+ prevBgChild,
+ &HttpBackgroundChannelChild::OnChannelClosed),
+ NS_DISPATCH_NORMAL);
+ }
+
+ MOZ_ASSERT(!mBgInitFailCallback);
+
+ mBgInitFailCallback = NewRunnableMethod<nsresult>(
+ "HttpChannelChild::FailedAsyncOpen", this,
+ &HttpChannelChild::FailedAsyncOpen, NS_ERROR_FAILURE);
+
+ RefPtr<HttpBackgroundChannelChild> bgChild =
+ new HttpBackgroundChannelChild();
+
+ RefPtr<HttpChannelChild> self = this;
+ nsresult rv = gSocketTransportService->Dispatch(
+ NewRunnableMethod<RefPtr<HttpChannelChild>>(
+ "HttpBackgroundChannelChild::Init", bgChild,
+ &HttpBackgroundChannelChild::Init, self),
+ NS_DISPATCH_NORMAL);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mBgChild = std::move(bgChild);
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mEverHadBgChildAtAsyncOpen = true;
+#endif
+ }
+
+ MaybeConnectToSocketProcess();
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIHttpChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::SetRequestHeader(const nsACString& aHeader,
+ const nsACString& aValue, bool aMerge) {
+ LOG(("HttpChannelChild::SetRequestHeader [this=%p]\n", this));
+ nsresult rv = HttpBaseChannel::SetRequestHeader(aHeader, aValue, aMerge);
+ if (NS_FAILED(rv)) return rv;
+
+ RequestHeaderTuple* tuple = mClientSetRequestHeaders.AppendElement();
+ if (!tuple) return NS_ERROR_OUT_OF_MEMORY;
+
+ // Mark that the User-Agent header has been modified.
+ if (nsHttp::ResolveAtom(aHeader) == nsHttp::User_Agent) {
+ StoreIsUserAgentHeaderModified(true);
+ }
+
+ tuple->mHeader = aHeader;
+ tuple->mValue = aValue;
+ tuple->mMerge = aMerge;
+ tuple->mEmpty = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::SetEmptyRequestHeader(const nsACString& aHeader) {
+ LOG(("HttpChannelChild::SetEmptyRequestHeader [this=%p]\n", this));
+ nsresult rv = HttpBaseChannel::SetEmptyRequestHeader(aHeader);
+ if (NS_FAILED(rv)) return rv;
+
+ RequestHeaderTuple* tuple = mClientSetRequestHeaders.AppendElement();
+ if (!tuple) return NS_ERROR_OUT_OF_MEMORY;
+
+ // Mark that the User-Agent header has been modified.
+ if (nsHttp::ResolveAtom(aHeader) == nsHttp::User_Agent) {
+ StoreIsUserAgentHeaderModified(true);
+ }
+
+ tuple->mHeader = aHeader;
+ tuple->mMerge = false;
+ tuple->mEmpty = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::RedirectTo(nsIURI* newURI) {
+ // disabled until/unless addons run in child or something else needs this
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::UpgradeToSecure() {
+ // disabled until/unless addons run in child or something else needs this
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetProtocolVersion(nsACString& aProtocolVersion) {
+ aProtocolVersion = mProtocolVersion;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIHttpChannelInternal
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::GetIsAuthChannel(bool* aIsAuthChannel) { DROP_DEAD(); }
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsICacheInfoChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::GetCacheTokenFetchCount(uint32_t* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mCacheEntryAvailable && !mAltDataCacheEntryAvailable) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *_retval = mCacheFetchCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetCacheTokenExpirationTime(uint32_t* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mCacheEntryAvailable) return NS_ERROR_NOT_AVAILABLE;
+
+ *_retval = mCacheExpirationTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::IsFromCache(bool* value) {
+ if (!LoadIsPending()) return NS_ERROR_NOT_AVAILABLE;
+
+ *value = mIsFromCache;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetCacheEntryId(uint64_t* aCacheEntryId) {
+ bool fromCache = false;
+ if (NS_FAILED(IsFromCache(&fromCache)) || !fromCache ||
+ !mCacheEntryAvailable) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aCacheEntryId = mCacheEntryId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::IsRacing(bool* aIsRacing) {
+ if (!LoadAfterOnStartRequestBegun()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aIsRacing = mIsRacing;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetCacheKey(uint32_t* cacheKey) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ *cacheKey = mCacheKey;
+ return NS_OK;
+}
+NS_IMETHODIMP
+HttpChannelChild::SetCacheKey(uint32_t cacheKey) {
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+
+ mCacheKey = cacheKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::SetAllowStaleCacheContent(bool aAllowStaleCacheContent) {
+ StoreAllowStaleCacheContent(aAllowStaleCacheContent);
+ return NS_OK;
+}
+NS_IMETHODIMP
+HttpChannelChild::GetAllowStaleCacheContent(bool* aAllowStaleCacheContent) {
+ NS_ENSURE_ARG(aAllowStaleCacheContent);
+ *aAllowStaleCacheContent = LoadAllowStaleCacheContent();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::SetForceValidateCacheContent(
+ bool aForceValidateCacheContent) {
+ StoreForceValidateCacheContent(aForceValidateCacheContent);
+ return NS_OK;
+}
+NS_IMETHODIMP
+HttpChannelChild::GetForceValidateCacheContent(
+ bool* aForceValidateCacheContent) {
+ NS_ENSURE_ARG(aForceValidateCacheContent);
+ *aForceValidateCacheContent = LoadForceValidateCacheContent();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::SetPreferCacheLoadOverBypass(
+ bool aPreferCacheLoadOverBypass) {
+ StorePreferCacheLoadOverBypass(aPreferCacheLoadOverBypass);
+ return NS_OK;
+}
+NS_IMETHODIMP
+HttpChannelChild::GetPreferCacheLoadOverBypass(
+ bool* aPreferCacheLoadOverBypass) {
+ NS_ENSURE_ARG(aPreferCacheLoadOverBypass);
+ *aPreferCacheLoadOverBypass = LoadPreferCacheLoadOverBypass();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::PreferAlternativeDataType(
+ const nsACString& aType, const nsACString& aContentType,
+ PreferredAlternativeDataDeliveryType aDeliverAltData) {
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+
+ mPreferredCachedAltDataTypes.AppendElement(PreferredAlternativeDataTypeParams(
+ nsCString(aType), nsCString(aContentType), aDeliverAltData));
+ return NS_OK;
+}
+
+const nsTArray<PreferredAlternativeDataTypeParams>&
+HttpChannelChild::PreferredAlternativeDataTypes() {
+ return mPreferredCachedAltDataTypes;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetAlternativeDataType(nsACString& aType) {
+ // Must be called during or after OnStartRequest
+ if (!LoadAfterOnStartRequestBegun()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ aType = mAvailableCachedAltDataType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::OpenAlternativeOutputStream(const nsACString& aType,
+ int64_t aPredictedSize,
+ nsIAsyncOutputStream** _retval) {
+ MOZ_ASSERT(NS_IsMainThread(), "Main thread only");
+
+ if (!CanSend()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ if (static_cast<ContentChild*>(gNeckoChild->Manager())->IsShuttingDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsISerialEventTarget> neckoTarget = GetNeckoTarget();
+ MOZ_ASSERT(neckoTarget);
+
+ RefPtr<AltDataOutputStreamChild> stream = new AltDataOutputStreamChild();
+ stream->AddIPDLReference();
+
+ if (!gNeckoChild->SendPAltDataOutputStreamConstructor(
+ stream, nsCString(aType), aPredictedSize, WrapNotNull(this))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ stream.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetOriginalInputStream(nsIInputStreamReceiver* aReceiver) {
+ if (aReceiver == nullptr) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (!CanSend()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mOriginalInputStreamReceiver = aReceiver;
+ Unused << SendOpenOriginalCacheInputStream();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetAlternativeDataInputStream(nsIInputStream** aInputStream) {
+ NS_ENSURE_ARG_POINTER(aInputStream);
+
+ nsCOMPtr<nsIInputStream> is = mAltDataInputStream;
+ is.forget(aInputStream);
+
+ return NS_OK;
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvOriginalCacheInputStreamAvailable(
+ const Maybe<IPCStream>& aStream) {
+ nsCOMPtr<nsIInputStream> stream = DeserializeIPCStream(aStream);
+ nsCOMPtr<nsIInputStreamReceiver> receiver;
+ receiver.swap(mOriginalInputStreamReceiver);
+ if (receiver) {
+ receiver->OnInputStreamReady(stream);
+ }
+
+ return IPC_OK();
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIResumableChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::ResumeAt(uint64_t startPos, const nsACString& entityID) {
+ LOG(("HttpChannelChild::ResumeAt [this=%p]\n", this));
+ ENSURE_CALLED_BEFORE_CONNECT();
+ mStartPos = startPos;
+ mEntityID = entityID;
+ mSendResumeAt = true;
+ return NS_OK;
+}
+
+// GetEntityID is shared in HttpBaseChannel
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsISupportsPriority
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::SetPriority(int32_t aPriority) {
+ LOG(("HttpChannelChild::SetPriority %p p=%d", this, aPriority));
+ int16_t newValue = clamped<int32_t>(aPriority, INT16_MIN, INT16_MAX);
+ if (mPriority == newValue) return NS_OK;
+ mPriority = newValue;
+ if (RemoteChannelExists()) SendSetPriority(mPriority);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIClassOfService
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+HttpChannelChild::SetClassFlags(uint32_t inFlags) {
+ if (mClassOfService.Flags() == inFlags) {
+ return NS_OK;
+ }
+
+ mClassOfService.SetFlags(inFlags);
+
+ LOG(("HttpChannelChild %p ClassOfService flags=%lu inc=%d", this,
+ mClassOfService.Flags(), mClassOfService.Incremental()));
+
+ if (RemoteChannelExists()) {
+ SendSetClassOfService(mClassOfService);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::AddClassFlags(uint32_t inFlags) {
+ mClassOfService.SetFlags(inFlags | mClassOfService.Flags());
+
+ LOG(("HttpChannelChild %p ClassOfService flags=%lu inc=%d", this,
+ mClassOfService.Flags(), mClassOfService.Incremental()));
+
+ if (RemoteChannelExists()) {
+ SendSetClassOfService(mClassOfService);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::ClearClassFlags(uint32_t inFlags) {
+ mClassOfService.SetFlags(~inFlags & mClassOfService.Flags());
+
+ LOG(("HttpChannelChild %p ClassOfService=%lu", this,
+ mClassOfService.Flags()));
+
+ if (RemoteChannelExists()) {
+ SendSetClassOfService(mClassOfService);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::SetClassOfService(ClassOfService inCos) {
+ mClassOfService = inCos;
+ LOG(("HttpChannelChild %p ClassOfService flags=%lu inc=%d", this,
+ mClassOfService.Flags(), mClassOfService.Incremental()));
+ if (RemoteChannelExists()) {
+ SendSetClassOfService(mClassOfService);
+ }
+ return NS_OK;
+}
+NS_IMETHODIMP
+HttpChannelChild::SetIncremental(bool inIncremental) {
+ mClassOfService.SetIncremental(inIncremental);
+ LOG(("HttpChannelChild %p ClassOfService flags=%lu inc=%d", this,
+ mClassOfService.Flags(), mClassOfService.Incremental()));
+ if (RemoteChannelExists()) {
+ SendSetClassOfService(mClassOfService);
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIProxiedChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::GetProxyInfo(nsIProxyInfo** aProxyInfo) { DROP_DEAD(); }
+
+NS_IMETHODIMP HttpChannelChild::GetHttpProxyConnectResponseCode(
+ int32_t* aResponseCode) {
+ DROP_DEAD();
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIHttpChannelChild
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP HttpChannelChild::AddCookiesToRequest() {
+ HttpBaseChannel::AddCookiesToRequest();
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpChannelChild::GetClientSetRequestHeaders(
+ RequestHeaderTuples** aRequestHeaders) {
+ *aRequestHeaders = &mClientSetRequestHeaders;
+ return NS_OK;
+}
+
+void HttpChannelChild::GetClientSetCorsPreflightParameters(
+ Maybe<CorsPreflightArgs>& aArgs) {
+ if (LoadRequireCORSPreflight()) {
+ CorsPreflightArgs args;
+ args.unsafeHeaders() = mUnsafeHeaders.Clone();
+ aArgs.emplace(args);
+ } else {
+ aArgs = Nothing();
+ }
+}
+
+NS_IMETHODIMP
+HttpChannelChild::RemoveCorsPreflightCacheEntry(
+ nsIURI* aURI, nsIPrincipal* aPrincipal,
+ const OriginAttributes& aOriginAttributes) {
+ PrincipalInfo principalInfo;
+ MOZ_ASSERT(aURI, "aURI should not be null");
+ nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ bool result = false;
+ // Be careful to not attempt to send a message to the parent after the
+ // actor has been destroyed.
+ if (CanSend()) {
+ result = SendRemoveCorsPreflightCacheEntry(aURI, principalInfo,
+ aOriginAttributes);
+ }
+ return result ? NS_OK : NS_ERROR_FAILURE;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIMuliPartChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::GetBaseChannel(nsIChannel** aBaseChannel) {
+ if (!mMultiPartID) {
+ MOZ_ASSERT(false, "Not a multipart channel");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ nsCOMPtr<nsIChannel> channel = this;
+ channel.forget(aBaseChannel);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetPartID(uint32_t* aPartID) {
+ if (!mMultiPartID) {
+ MOZ_ASSERT(false, "Not a multipart channel");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aPartID = *mMultiPartID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetIsFirstPart(bool* aIsFirstPart) {
+ if (!mMultiPartID) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aIsFirstPart = mIsFirstPartOfMultiPart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetIsLastPart(bool* aIsLastPart) {
+ if (!mMultiPartID) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aIsLastPart = mIsLastPartOfMultiPart;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIThreadRetargetableRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::RetargetDeliveryTo(nsISerialEventTarget* aNewTarget) {
+ LOG(("HttpChannelChild::RetargetDeliveryTo [this=%p, aNewTarget=%p]", this,
+ aNewTarget));
+ MOZ_ASSERT(NS_IsMainThread(), "Should be called on main thread only");
+ MOZ_ASSERT(aNewTarget);
+
+ NS_ENSURE_ARG(aNewTarget);
+ if (aNewTarget->IsOnCurrentThread()) {
+ NS_WARNING("Retargeting delivery to same thread");
+ mOMTResult = LABELS_HTTP_CHILD_OMT_STATS::successMainThread;
+ return NS_OK;
+ }
+
+ if (mMultiPartID) {
+ // TODO: Maybe add a new label for this? Maybe it doesn't
+ // matter though, since we also blocked QI, so we shouldn't
+ // ever get here.
+ mOMTResult = LABELS_HTTP_CHILD_OMT_STATS::failListener;
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ // Ensure that |mListener| and any subsequent listeners can be retargeted
+ // to another thread.
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(mListener, &rv);
+ if (!retargetableListener || NS_FAILED(rv)) {
+ NS_WARNING("Listener is not retargetable");
+ mOMTResult = LABELS_HTTP_CHILD_OMT_STATS::failListener;
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ rv = retargetableListener->CheckListenerChain();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Subsequent listeners are not retargetable");
+ mOMTResult = LABELS_HTTP_CHILD_OMT_STATS::failListenerChain;
+ return rv;
+ }
+
+ {
+ MutexAutoLock lock(mEventTargetMutex);
+ MOZ_ASSERT(!mODATarget);
+ mODATarget = aNewTarget;
+ }
+
+ mOMTResult = LABELS_HTTP_CHILD_OMT_STATS::success;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetDeliveryTarget(nsISerialEventTarget** aEventTarget) {
+ MutexAutoLock lock(mEventTargetMutex);
+
+ nsCOMPtr<nsISerialEventTarget> target = mODATarget;
+ if (!mODATarget) {
+ target = GetCurrentSerialEventTarget();
+ }
+ target.forget(aEventTarget);
+ return NS_OK;
+}
+
+void HttpChannelChild::TrySendDeletingChannel() {
+ AUTO_PROFILER_LABEL("HttpChannelChild::TrySendDeletingChannel", NETWORK);
+ if (!mDeletingChannelSent.compareExchange(false, true)) {
+ // SendDeletingChannel is already sent.
+ return;
+ }
+
+ if (NS_IsMainThread()) {
+ if (NS_WARN_IF(!CanSend())) {
+ // IPC actor is destroyed already, do not send more messages.
+ return;
+ }
+
+ Unused << PHttpChannelChild::SendDeletingChannel();
+ return;
+ }
+
+ nsCOMPtr<nsISerialEventTarget> neckoTarget = GetNeckoTarget();
+ MOZ_ASSERT(neckoTarget);
+
+ DebugOnly<nsresult> rv = neckoTarget->Dispatch(
+ NewNonOwningRunnableMethod(
+ "net::HttpChannelChild::TrySendDeletingChannel", this,
+ &HttpChannelChild::TrySendDeletingChannel),
+ NS_DISPATCH_NORMAL);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+nsresult HttpChannelChild::AsyncCallImpl(
+ void (HttpChannelChild::*funcPtr)(),
+ nsRunnableMethod<HttpChannelChild>** retval) {
+ nsresult rv;
+
+ RefPtr<nsRunnableMethod<HttpChannelChild>> event =
+ NewRunnableMethod("net::HttpChannelChild::AsyncCall", this, funcPtr);
+ nsCOMPtr<nsISerialEventTarget> neckoTarget = GetNeckoTarget();
+ MOZ_ASSERT(neckoTarget);
+
+ rv = neckoTarget->Dispatch(event, NS_DISPATCH_NORMAL);
+
+ if (NS_SUCCEEDED(rv) && retval) {
+ *retval = event;
+ }
+
+ return rv;
+}
+
+nsresult HttpChannelChild::SetReferrerHeader(const nsACString& aReferrer,
+ bool aRespectBeforeConnect) {
+ // Normally this would be ENSURE_CALLED_BEFORE_CONNECT, but since the
+ // "connect" is done in the main process, and LoadRequestObserversCalled() is
+ // never set in the ChannelChild, before connect basically means before
+ // asyncOpen.
+ if (aRespectBeforeConnect) {
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+ }
+
+ // remove old referrer if any
+ mClientSetRequestHeaders.RemoveElementsBy(
+ [](const auto& header) { return "Referer"_ns.Equals(header.mHeader); });
+
+ return HttpBaseChannel::SetReferrerHeader(aReferrer, aRespectBeforeConnect);
+}
+
+void HttpChannelChild::CancelOnMainThread(nsresult aRv,
+ const nsACString& aReason) {
+ LOG(("HttpChannelChild::CancelOnMainThread [this=%p]", this));
+
+ if (NS_IsMainThread()) {
+ CancelWithReason(aRv, aReason);
+ return;
+ }
+
+ mEventQ->Suspend();
+ // Cancel is expected to preempt any other channel events, thus we put this
+ // event in the front of mEventQ to make sure nsIStreamListener not receiving
+ // any ODA/OnStopRequest callbacks.
+ nsCString reason(aReason);
+ mEventQ->PrependEvent(MakeUnique<NeckoTargetChannelFunctionEvent>(
+ this, [self = UnsafePtr<HttpChannelChild>(this), aRv, reason]() {
+ self->CancelWithReason(aRv, reason);
+ }));
+ mEventQ->Resume();
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvSetPriority(
+ const int16_t& aPriority) {
+ mPriority = aPriority;
+ return IPC_OK();
+}
+
+// We don't have a copyable Endpoint and NeckoTargetChannelFunctionEvent takes
+// std::function<void()>. It's not possible to avoid the copy from the type of
+// lambda to std::function, so does the capture list. Hence, we're forced to
+// use the old-fashioned channel event inheritance.
+class AttachStreamFilterEvent : public ChannelEvent {
+ public:
+ AttachStreamFilterEvent(HttpChannelChild* aChild,
+ already_AddRefed<nsIEventTarget> aTarget,
+ Endpoint<extensions::PStreamFilterParent>&& aEndpoint)
+ : mChild(aChild), mTarget(aTarget), mEndpoint(std::move(aEndpoint)) {}
+
+ already_AddRefed<nsIEventTarget> GetEventTarget() override {
+ nsCOMPtr<nsIEventTarget> target = mTarget;
+ return target.forget();
+ }
+
+ void Run() override {
+ extensions::StreamFilterParent::Attach(mChild, std::move(mEndpoint));
+ }
+
+ private:
+ HttpChannelChild* mChild;
+ nsCOMPtr<nsIEventTarget> mTarget;
+ Endpoint<extensions::PStreamFilterParent> mEndpoint;
+};
+
+void HttpChannelChild::RegisterStreamFilter(
+ RefPtr<extensions::StreamFilterParent>& aStreamFilter) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mStreamFilters.AppendElement(aStreamFilter);
+}
+
+void HttpChannelChild::ProcessAttachStreamFilter(
+ Endpoint<extensions::PStreamFilterParent>&& aEndpoint) {
+ LOG(("HttpChannelChild::ProcessAttachStreamFilter [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+
+ mEventQ->RunOrEnqueue(new AttachStreamFilterEvent(this, GetNeckoTarget(),
+ std::move(aEndpoint)));
+}
+
+void HttpChannelChild::OnDetachStreamFilters() {
+ LOG(("HttpChannelChild::OnDetachStreamFilters [this=%p]\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+ for (auto& StreamFilter : mStreamFilters) {
+ StreamFilter->Disconnect("ServiceWorker fallback redirection"_ns);
+ }
+ mStreamFilters.Clear();
+}
+
+void HttpChannelChild::ProcessDetachStreamFilters() {
+ LOG(("HttpChannelChild::ProcessDetachStreamFilter [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this)]() {
+ self->OnDetachStreamFilters();
+ }));
+}
+
+void HttpChannelChild::ActorDestroy(ActorDestroyReason aWhy) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mActorDestroyReason.emplace(aWhy);
+#endif
+
+ // OnStartRequest might be dropped if IPDL is destroyed abnormally
+ // and BackgroundChild might have pending IPC messages.
+ // Clean up BackgroundChild at this time to prevent memleak.
+ if (aWhy != Deletion) {
+ // Make sure all the messages are processed.
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ mStatus = NS_ERROR_DOCSHELL_DYING;
+ HandleAsyncAbort();
+
+ // Cleanup the background channel before we resume the eventQ so we don't
+ // get any other events.
+ CleanupBackgroundChannel();
+
+ mIPCActorDeleted = true;
+ mCanceled = true;
+ }
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvLogBlockedCORSRequest(
+ const nsAString& aMessage, const nsACString& aCategory,
+ const bool& aIsWarning) {
+ Unused << LogBlockedCORSRequest(aMessage, aCategory, aIsWarning);
+ return IPC_OK();
+}
+
+NS_IMETHODIMP
+HttpChannelChild::LogBlockedCORSRequest(const nsAString& aMessage,
+ const nsACString& aCategory,
+ bool aIsWarning) {
+ uint64_t innerWindowID = mLoadInfo->GetInnerWindowID();
+ bool privateBrowsing = !!mLoadInfo->GetOriginAttributes().mPrivateBrowsingId;
+ bool fromChromeContext =
+ mLoadInfo->TriggeringPrincipal()->IsSystemPrincipal();
+ nsCORSListenerProxy::LogBlockedCORSRequest(innerWindowID, privateBrowsing,
+ fromChromeContext, aMessage,
+ aCategory, aIsWarning);
+ return NS_OK;
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvLogMimeTypeMismatch(
+ const nsACString& aMessageName, const bool& aWarning, const nsAString& aURL,
+ const nsAString& aContentType) {
+ Unused << LogMimeTypeMismatch(aMessageName, aWarning, aURL, aContentType);
+ return IPC_OK();
+}
+
+NS_IMETHODIMP
+HttpChannelChild::LogMimeTypeMismatch(const nsACString& aMessageName,
+ bool aWarning, const nsAString& aURL,
+ const nsAString& aContentType) {
+ RefPtr<Document> doc;
+ mLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
+
+ AutoTArray<nsString, 2> params;
+ params.AppendElement(aURL);
+ params.AppendElement(aContentType);
+ nsContentUtils::ReportToConsole(
+ aWarning ? nsIScriptError::warningFlag : nsIScriptError::errorFlag,
+ "MIMEMISMATCH"_ns, doc, nsContentUtils::eSECURITY_PROPERTIES,
+ nsCString(aMessageName).get(), params);
+ return NS_OK;
+}
+
+nsresult HttpChannelChild::MaybeLogCOEPError(nsresult aStatus) {
+ if (aStatus == NS_ERROR_DOM_CORP_FAILED) {
+ RefPtr<Document> doc;
+ mLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
+
+ nsAutoCString url;
+ mURI->GetSpec(url);
+
+ AutoTArray<nsString, 2> params;
+ params.AppendElement(NS_ConvertUTF8toUTF16(url));
+ // The MDN URL intentionally ends with a # so the webconsole linkification
+ // doesn't ignore the final ) of the URL
+ params.AppendElement(
+ u"https://developer.mozilla.org/docs/Web/HTTP/Cross-Origin_Resource_Policy_(CORP)#"_ns);
+ nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, "COEP"_ns, doc,
+ nsContentUtils::eNECKO_PROPERTIES,
+ "CORPBlocked", params);
+ }
+
+ return NS_OK;
+}
+
+nsresult HttpChannelChild::CrossProcessRedirectFinished(nsresult aStatus) {
+ if (!CanSend()) {
+ return NS_BINDING_FAILED;
+ }
+
+ if (!mCanceled && NS_SUCCEEDED(mStatus)) {
+ mStatus = aStatus;
+ }
+
+ return mStatus;
+}
+
+void HttpChannelChild::DoDiagnosticAssertWhenOnStopNotCalledOnDestroy() {
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mDoDiagnosticAssertWhenOnStopNotCalledOnDestroy = true;
+#endif
+}
+
+void HttpChannelChild::MaybeConnectToSocketProcess() {
+ if (!nsIOService::UseSocketProcess()) {
+ return;
+ }
+
+ if (!StaticPrefs::network_send_ODA_to_content_directly()) {
+ return;
+ }
+
+ RefPtr<HttpBackgroundChannelChild> bgChild;
+ {
+ MutexAutoLock lock(mBgChildMutex);
+ bgChild = mBgChild;
+ }
+ SocketProcessBridgeChild::GetSocketProcessBridge()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [bgChild, channelId = ChannelId()](
+ const RefPtr<SocketProcessBridgeChild>& aBridge) {
+ Endpoint<PBackgroundDataBridgeParent> parentEndpoint;
+ Endpoint<PBackgroundDataBridgeChild> childEndpoint;
+ PBackgroundDataBridge::CreateEndpoints(&parentEndpoint, &childEndpoint);
+ aBridge->SendInitBackgroundDataBridge(std::move(parentEndpoint),
+ channelId);
+
+ gSocketTransportService->Dispatch(
+ NS_NewRunnableFunction(
+ "HttpBackgroundChannelChild::CreateDataBridge",
+ [bgChild, endpoint = std::move(childEndpoint)]() mutable {
+ bgChild->CreateDataBridge(std::move(endpoint));
+ }),
+ NS_DISPATCH_NORMAL);
+ },
+ []() { NS_WARNING("Failed to create SocketProcessBridgeChild"); });
+}
+
+NS_IMETHODIMP
+HttpChannelChild::SetEarlyHintObserver(nsIEarlyHintObserver* aObserver) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpChannelChild::SetWebTransportSessionEventListener(
+ WebTransportSessionEventListener* aListener) {
+ return NS_OK;
+}
+
+} // namespace mozilla::net