Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
// 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/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.
//
// gHttpHandler->OnExamineResponse(this);
ResourceTimingStructArgsToTimingsStruct(aArgs.timing(), mTransactionTimings);
nsAutoCString cosString;
ClassOfService::ToString(mClassOfService, cosString);
if (!mAsyncOpenTime.IsNull() &&
!aArgs.timing().transactionPending().IsNull()) {
Telemetry::AccumulateTimeDelta(
Telemetry::NETWORK_ASYNC_OPEN_CHILD_TO_TRANSACTION_PENDING_EXP_MS,
cosString, mAsyncOpenTime, aArgs.timing().transactionPending());
PerfStats::RecordMeasurement(
PerfStats::Metric::HttpChannelAsyncOpenToTransactionPending,
aArgs.timing().transactionPending() - mAsyncOpenTime);
}
const TimeStamp now = TimeStamp::Now();
if (!aArgs.timing().responseStart().IsNull()) {
Telemetry::AccumulateTimeDelta(
Telemetry::NETWORK_RESPONSE_START_PARENT_TO_CONTENT_EXP_MS, cosString,
aArgs.timing().responseStart(), now);
PerfStats::RecordMeasurement(
PerfStats::Metric::HttpChannelResponseStartParentToContent,
now - aArgs.timing().responseStart());
}
if (!mOnStartRequestStartTime.IsNull()) {
PerfStats::RecordMeasurement(PerfStats::Metric::OnStartRequestToContent,
now - mOnStartRequestStartTime);
}
StoreAllRedirectsSameOrigin(aArgs.allRedirectsSameOrigin());
mMultiPartID = aArgs.multiPartID();
mIsFirstPartOfMultiPart = aArgs.isFirstPartOfMultiPart();
mIsLastPartOfMultiPart = aArgs.isLastPartOfMultiPart();
if (aArgs.overrideReferrerInfo()) {
// The arguments passed to SetReferrerInfoInternal here should mirror the
// arguments passed in
// nsHttpChannel::ReEvaluateReferrerAfterTrackingStatusIsKnown(), except for
// aRespectBeforeConnect which we pass false here since we're intentionally
// overriding the referrer after BeginConnect().
Unused << SetReferrerInfoInternal(aArgs.overrideReferrerInfo(), false, true,
false);
}
if (!aArgs.cookie().IsEmpty()) {
SetCookie(aArgs.cookie());
}
// 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();
// 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) {