Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/netwerk/protocol/http/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 114 kB image not shown  

Quelle  HttpChannelChild.cpp

  Sprache: C
 

/* -*- 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 "nsError.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/PBackgroundDataBridge.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 "nsIStreamTransportService.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/glean/NetwerkMetrics.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(), falsetrue,
                                      false);
  }

  if (!aArgs.cookieHeaders().IsEmpty()) {
    SetCookieHeaders(aArgs.cookieHeaders());
  }

  // Note: this is where we would notify "http-on-after-examine-response"
  // observers.  We have deliberately disabled this for child processes (see bug
  // 806753)
  //
  // gHttpHandler->OnAfterExamineResponse(this);

  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, request=%p]\n"this,
       aRequest));

  // 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;

    // We call MaybeRetarget here to allow the stream converter
    // the option to request data on another thread, even if the
    // final listener might not support it
    if (nsCOMPtr<nsIStreamConverter> conv =
            do_QueryInterface((mCompressListener))) {
      conv->MaybeRetarget(this);
    }
  }
}

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, request=%p]\n"this,
       aRequest));
  if (mCanceled) return;

  mGotDataAvailable = true;
  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);
    MOZ_ASSERT((delta.ToMilliseconds() >= 0),
               "OnDataFinished after OnStopRequest");
    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);
          // We can be on main thread or background thread at this point
          // http_content_ondatafinished_delay_2 is used to track
          // delay observed between dispatch the OnDataFinished on the socket
          // thread and running OnDataFinished on the background thread
          if (!NS_IsMainThread()) {
            glean::networking::http_content_ondatafinished_delay_2
                .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::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;
    mozilla::Maybe<mozilla::net::HttpVersion> httpVersion = Nothing();
    mozilla::Maybe<uint32_t> responseStatus = Nothing();
    if (mResponseHead) {
      mResponseHead->ContentType(contentType);
      httpVersion = Some(mResponseHead->Version());
      responseStatus = Some(mResponseHead->Status());
    }
    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().IsPrivateBrowsing(),
        mClassOfService.Flags(), &mTransactionTimings, std::move(mSource),
        httpVersion, responseStatus,
        Some(nsDependentCString(contentType.get())));
  }

  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",
       thisstatic_cast<uint32_t>(aStatus)));
  StoreIsPending(false);

  MaybeReportTimingData();

  if (!mCanceled && NS_SUCCEEDED(mStatus)) {
    mStatus = aStatus;
  }
}

// We want to inspect all upgradable mixed content loads
// (i.e., loads point to HTTP from an HTTPS page), for
// resources that stem from audio, video and img elements.
// Of those, we want to measure which succceed and which fail.
// Some double negatives, but we check the following:exempt loads that
// 1) Request was upgraded as mixed passive content
// 2) Request _could_ have been upgraded as mixed passive content if the pref
// had been set and Request wasn't upgraded by any other means (URL isn't https)
void HttpChannelChild::CollectMixedContentTelemetry() {
  MOZ_ASSERT(NS_IsMainThread());

  bool wasUpgraded = mLoadInfo->GetBrowserDidUpgradeInsecureRequests();
  if (!wasUpgraded) {
    // If this wasn't upgraded, let's check if it _could_ have been upgraded as
    // passive mixed content and that it wasn't upgraded with any other method
    if (!mURI->SchemeIs("https") &&
        !mLoadInfo->GetBrowserWouldUpgradeInsecureRequests()) {
      return;
    }
  }

  // UseCounters require a document.
  RefPtr<Document> doc;
  mLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
  if (!doc) {
    return;
  }

  nsContentPolicyType internalLoadType;
  mLoadInfo->GetInternalContentPolicyType(&internalLoadType);
  bool statusIsSuccess = NS_SUCCEEDED(mStatus);

  if (internalLoadType == nsIContentPolicy::TYPE_INTERNAL_IMAGE) {
    if (wasUpgraded) {
      doc->SetUseCounter(
          statusIsSuccess
              ? eUseCounter_custom_MixedContentUpgradedImageSuccess
              : eUseCounter_custom_MixedContentUpgradedImageFailure);
    } else {
      doc->SetUseCounter(
          statusIsSuccess
              ? eUseCounter_custom_MixedContentNotUpgradedImageSuccess
              : eUseCounter_custom_MixedContentNotUpgradedImageFailure);
    }
    return;
  }
  if (internalLoadType == nsIContentPolicy::TYPE_INTERNAL_VIDEO) {
    if (wasUpgraded) {
      doc->SetUseCounter(
          statusIsSuccess
              ? eUseCounter_custom_MixedContentUpgradedVideoSuccess
              : eUseCounter_custom_MixedContentUpgradedVideoFailure);
    } else {
      doc->SetUseCounter(
          statusIsSuccess
              ? eUseCounter_custom_MixedContentNotUpgradedVideoSuccess
              : eUseCounter_custom_MixedContentNotUpgradedVideoFailure);
    }
    return;
  }
  if (internalLoadType == nsIContentPolicy::TYPE_INTERNAL_AUDIO) {
    if (wasUpgraded) {
      doc->SetUseCounter(
          statusIsSuccess
              ? eUseCounter_custom_MixedContentUpgradedAudioSuccess
              : eUseCounter_custom_MixedContentUpgradedAudioFailure);
    } else {
      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, request=%p]\n"this,
       aRequest));
  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");

  // notify "http-on-before-stop-request" observers
  gHttpHandler->OnBeforeStopRequest(this);

  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-request" 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);

  // notify "http-on-before-stop-request" observers
  gHttpHandler->OnBeforeStopRequest(this);

  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,
    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 = std::move(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().IsPrivateBrowsing(),
        mClassOfService.Flags(), &mTransactionTimings, std::move(mSource),
        Some(responseHead.Version()), Some(responseHead.Status()),
        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(thisfalse);
  } 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().IsPrivateBrowsing(),
        mClassOfService.Flags());
  }
  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) {
  AUTO_PROFILER_LABEL("HttpChannelChild::AsyncOpen", NETWORK);
  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",
         thisstatic_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().IsPrivateBrowsing(),
        mClassOfService.Flags());
  }
  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;
  if (mAPIRedirectTo) {
    openArgs.apiRedirectTo() = mAPIRedirectTo->first();
  }
  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();
  openArgs.initiatorType() = mInitiatorType;

  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::TransparentRedirectTo(nsIURI* newURI) {
  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 = std::clamp<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");
    return NS_OK;
  }

  if (mMultiPartID) {
    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");
    return NS_ERROR_NO_INTERFACE;
  }

  rv = retargetableListener->CheckListenerChain();
  if (NS_FAILED(rv)) {
    NS_WARNING("Subsequent listeners are not retargetable");
    return rv;
  }

  MutexAutoLock lock(mEventTargetMutex);
  // Don't assert if the target hasn't changed, or if we haven't gotten
  // OnDataAvailable (backed off on this last bit, see bug 1917901)
  if (mODATarget == aNewTarget) {
    // Same target
    return NS_OK;
  } else if (mODATarget) {
    // We already retargetted (valentin: unclear if this should be allowed)
    NS_WARNING("Retargeting delivery when already retargeted");
    return NS_ERROR_ALREADY_INITIALIZED;
  } else if (mGotDataAvailable) {
    // Too late to retarget now.
    return NS_ERROR_FAILURE;
  }

  RetargetDeliveryToImpl(aNewTarget, lock);
  return NS_OK;
}

void HttpChannelChild::RetargetDeliveryToImpl(nsISerialEventTarget* aNewTarget,
                                              MutexAutoLock& aLockRef) {
  aLockRef.AssertOwns(mEventTargetMutex);

  mODATarget = aNewTarget;
}

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);
  MOZ_ASSERT(NS_IsMainThread());

  if (!mDeletingChannelSent.compareExchange(falsetrue)) {
    // SendDeletingChannel is already sent.
    return;
  }

  if (NS_WARN_IF(!CanSend())) {
    // IPC actor is destroyed already, do not send more messages.
    return;
  }

  Unused << PHttpChannelChild::SendDeletingChannel();
}

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().IsPrivateBrowsing();
  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;
}

void HttpChannelChild::ExplicitSetUploadStreamLength(
    uint64_t aContentLength, bool aSetContentLengthHeader) {
  // SetRequestHeader propagates headers to chrome if HttpChannelChild
  MOZ_ASSERT(!LoadWasOpened());
  HttpBaseChannel::ExplicitSetUploadStreamLength(aContentLength,
                                                 aSetContentLengthHeader);
}

}  // namespace mozilla::net

Messung V0.5 in Prozent
C=89 H=100 G=94

¤ Dauer der Verarbeitung: 0.52 Sekunden  (vorverarbeitet am  2026-04-26) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.