Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


SSL HttpChannelChild.cpp   Interaktion und
PortierbarkeitC

 
/* -*- 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);
  }

--> --------------------

--> maximum size reached

--> --------------------

97%


¤ 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.0.24Bemerkung:  (vorverarbeitet)  ¤

*Bot Zugriff






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 ist noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge