/* -*- 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/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 using namespace mozilla::dom; using namespace mozilla::ipc; namespace mozilla::net { //----------------------------------------------------------------------------- // HttpChannelChild //----------------------------------------------------------------------------- HttpChannelChild::HttpChannelChild() : HttpAsyncAborter(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(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 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(nsresult(mStatus)), static_cast(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 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 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 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(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(aResponseHead); } mSecurityInfo = aArgs.securityInfo(); ipc::MergeParentLoadInfoForwarder(aArgs.loadInfoForwarder(), mLoadInfo); mIsFromCache = aArgs.isFromCache(); mIsRacing = aArgs.isRacing(); mCacheEntryAvailable = aArgs.cacheEntryAvailable(); mCacheEntryId = aArgs.cacheEntryId(); mCacheFetchCount = aArgs.cacheFetchCount(); mProtocolVersion = aArgs.protocolVersion(); mCacheExpirationTime = aArgs.cacheExpirationTime(); mSelfAddr = aArgs.selfAddr(); mPeerAddr = aArgs.peerAddr(); mRedirectCount = aArgs.redirectCount(); mAvailableCachedAltDataType = aArgs.altDataType(); StoreDeliveringAltData(aArgs.deliveringAltData()); mAltDataLength = aArgs.altDataLength(); StoreResolvedByTRR(aArgs.isResolvedByTRR()); mEffectiveTRRMode = aArgs.effectiveTRRMode(); mTRRSkipReason = aArgs.trrSkipReason(); SetApplyConversion(aArgs.applyConversion()); StoreAfterOnStartRequestBegun(true); StoreHasHTTPSRR(aArgs.hasHTTPSRR()); AutoEventEnqueuer ensureSerialDispatch(mEventQ); mCacheKey = aArgs.cacheKey(); StoreIsProxyUsed(aArgs.isProxyUsed()); // replace our request headers with what actually got sent in the parent mRequestHead.SetHeaders(aRequestHeaders); // Note: this is where we would notify "http-on-examine-response" observers. // We have deliberately disabled this for child processes (see bug 806753) // // gHttpHandler->OnExamineResponse(this); ResourceTimingStructArgsToTimingsStruct(aArgs.timing(), mTransactionTimings); nsAutoCString cosString; ClassOfService::ToString(mClassOfService, cosString); if (!mAsyncOpenTime.IsNull() && !aArgs.timing().transactionPending().IsNull()) { Telemetry::AccumulateTimeDelta( Telemetry::NETWORK_ASYNC_OPEN_CHILD_TO_TRANSACTION_PENDING_EXP_MS, cosString, mAsyncOpenTime, aArgs.timing().transactionPending()); PerfStats::RecordMeasurement( PerfStats::Metric::HttpChannelAsyncOpenToTransactionPending, aArgs.timing().transactionPending() - mAsyncOpenTime); } const TimeStamp now = TimeStamp::Now(); if (!aArgs.timing().responseStart().IsNull()) { Telemetry::AccumulateTimeDelta( Telemetry::NETWORK_RESPONSE_START_PARENT_TO_CONTENT_EXP_MS, cosString, aArgs.timing().responseStart(), now); PerfStats::RecordMeasurement( PerfStats::Metric::HttpChannelResponseStartParentToContent, now - aArgs.timing().responseStart()); } if (!mOnStartRequestStartTime.IsNull()) { PerfStats::RecordMeasurement(PerfStats::Metric::OnStartRequestToContent, now - mOnStartRequestStartTime); } StoreAllRedirectsSameOrigin(aArgs.allRedirectsSameOrigin()); mMultiPartID = aArgs.multiPartID(); mIsFirstPartOfMultiPart = aArgs.isFirstPartOfMultiPart(); mIsLastPartOfMultiPart = aArgs.isLastPartOfMultiPart(); if (aArgs.overrideReferrerInfo()) { // The arguments passed to SetReferrerInfoInternal here should mirror the // arguments passed in // nsHttpChannel::ReEvaluateReferrerAfterTrackingStatusIsKnown(), except for // aRespectBeforeConnect which we pass false here since we're intentionally // overriding the referrer after BeginConnect(). Unused << SetReferrerInfoInternal(aArgs.overrideReferrerInfo(), false, true, false); } if (!aArgs.cookie().IsEmpty()) { SetCookie(aArgs.cookie()); } if (aArgs.shouldWaitForOnStartRequestSent() && !mRecvOnStartRequestSentCalled) { LOG((" > pending DoOnStartRequest until RecvOnStartRequestSent\n")); MOZ_ASSERT(NS_IsMainThread()); mEventQ->Suspend(); mSuspendedByWaitingForPermissionCookie = true; mEventQ->PrependEvent(MakeUnique( this, [self = UnsafePtr(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(this), aStatus]() { self->OnAfterLastPart(aStatus); })); } void HttpChannelChild::OnAfterLastPart(const nsresult& aStatus) { if (LoadOnStopRequestCalled()) { return; } StoreOnStopRequestCalled(true); // notify "http-on-stop-connect" observers gHttpHandler->OnStopRequest(this); ReleaseListeners(); // If a preferred alt-data type was set, the parent would hold a reference to // the cache entry in case the child calls openAlternativeOutputStream(). // (see nsHttpChannel::OnStopRequest) if (!mPreferredCachedAltDataTypes.IsEmpty()) { mAltDataCacheEntryAvailable = mCacheEntryAvailable; } mCacheEntryAvailable = false; if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus); CleanupBackgroundChannel(); if (mLoadFlags & LOAD_DOCUMENT_URI) { // Keep IPDL channel open, but only for updating security info. // If IPDL is already closed, then do nothing. if (CanSend()) { mKeptAlive = true; SendDocumentChannelCleanup(true); } } else { // The parent process will respond by sending a DeleteSelf message and // making sure not to send any more messages after that. TrySendDeletingChannel(); } } void HttpChannelChild::DoOnStartRequest(nsIRequest* aRequest) { nsresult rv; LOG(("HttpChannelChild::DoOnStartRequest [this=%p]\n", this)); // We handle all the listener chaining before OnStartRequest at this moment. // Prevent additional listeners being added to the chain after the request // as started. StoreTracingEnabled(false); // mListener could be null if the redirect setup is not completed. MOZ_ASSERT(mListener || LoadOnStartRequestCalled()); if (!mListener) { Cancel(NS_ERROR_FAILURE); return; } if (mListener) { nsCOMPtr 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 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 conv = do_QueryInterface((mCompressListener))) { rv = conv->MaybeRetarget(this); if (NS_SUCCEEDED(rv)) { mOMTResult = LABELS_HTTP_CHILD_OMT_STATS_2::successOnlyDecomp; } } } } 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(this)]() { return self->GetODATarget(); }, [self = UnsafePtr(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 self = this; nsCOMPtr neckoTarget = GetNeckoTarget(); MOZ_ASSERT(neckoTarget); DebugOnly 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 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 self = this; int32_t bytesRead = mUnreportBytesRead; nsCOMPtr neckoTarget = GetNeckoTarget(); MOZ_ASSERT(neckoTarget); DebugOnly rv = neckoTarget->Dispatch( NS_NewRunnableFunction("net::HttpChannelChild::SendBytesRead", [self, bytesRead]() { Unused << self->SendBytesRead(bytesRead); }), NS_DISPATCH_NORMAL); MOZ_ASSERT(NS_SUCCEEDED(rv)); } mUnreportBytesRead = 0; } } } bool HttpChannelChild::NeedToReportBytesRead() { if (mCacheNeedToReportBytesReadInitialized) { return mNeedToReportBytesRead; } // Might notify parent for partial cache, and the IPC message is ignored by // parent. int64_t contentLength = -1; if (gHttpHandler->SendWindowSize() == 0 || mIsFromCache || NS_FAILED(GetContentLength(&contentLength)) || contentLength < gHttpHandler->SendWindowSize()) { mNeedToReportBytesRead = false; } mCacheNeedToReportBytesReadInitialized = true; return mNeedToReportBytesRead; } void HttpChannelChild::DoOnStatus(nsIRequest* aRequest, nsresult status) { LOG(("HttpChannelChild::DoOnStatus [this=%p]\n", this)); MOZ_ASSERT(NS_IsMainThread()); if (mCanceled) return; // cache the progress sink so we don't have to query for it each time. if (!mProgressSink) GetCallback(mProgressSink); // block status/progress after Cancel or OnStopRequest has been called, // or if channel has LOAD_BACKGROUND set. if (mProgressSink && NS_SUCCEEDED(mStatus) && LoadIsPending() && !(mLoadFlags & LOAD_BACKGROUND)) { nsAutoCString host; mURI->GetHost(host); mProgressSink->OnStatus(aRequest, status, NS_ConvertUTF8toUTF16(host).get()); } } void HttpChannelChild::DoOnProgress(nsIRequest* aRequest, int64_t progress, int64_t progressMax) { LOG(("HttpChannelChild::DoOnProgress [this=%p]\n", this)); MOZ_ASSERT(NS_IsMainThread()); if (mCanceled) return; // cache the progress sink so we don't have to query for it each time. if (!mProgressSink) GetCallback(mProgressSink); // block status/progress after Cancel or OnStopRequest has been called, // or if channel has LOAD_BACKGROUND set. if (mProgressSink && NS_SUCCEEDED(mStatus) && LoadIsPending()) { // OnProgress // if (progress > 0) { mProgressSink->OnProgress(aRequest, progress, progressMax); } } // mOnProgressEventSent indicates we have flushed all the // progress events on the main thread. It is needed if // we do not want to dispatch OnDataFinished before sending // all of the progress updates. if (progress == progressMax) { mOnProgressEventSent = true; } } void HttpChannelChild::DoOnDataAvailable(nsIRequest* aRequest, nsIInputStream* aStream, uint64_t aOffset, uint32_t aCount) { AUTO_PROFILER_LABEL("HttpChannelChild::DoOnDataAvailable", NETWORK); LOG(("HttpChannelChild::DoOnDataAvailable [this=%p]\n", this)); if (mCanceled) return; if (mListener) { nsCOMPtr 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 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&& 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 timing; TimeStamp start = TimeStamp::Now(); if (StaticPrefs::network_send_OnDataFinished()) { timing = new RecordStopRequestDelta; mEventQ->RunOrEnqueue(new ChannelFunctionEvent( [self = UnsafePtr(this)]() { return self->GetODATarget(); }, [self = UnsafePtr(this), status = aChannelStatus, start, timing]() { TimeStamp now = TimeStamp::Now(); TimeDuration delay = now - start; glean::networking::http_content_ondatafinished_delay .AccumulateRawDuration(delay); timing->mOnDataFinishedTime = now; self->SendOnDataFinished(status); })); } mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent( this, [self = UnsafePtr(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&& aConsoleReports) { LOG(("HttpChannelChild::ProcessOnConsoleReport [this=%p]\n", this)); MOZ_ASSERT(OnSocketThread()); mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent( this, [self = UnsafePtr(this), consoleReports = CopyableTArray{aConsoleReports.Clone()}]() mutable { self->DoOnConsoleReport(std::move(consoleReports)); self->ContinueOnStopRequest(); })); } void HttpChannelChild::DoOnConsoleReport( nsTArray&& aConsoleReports) { if (aConsoleReports.IsEmpty()) { return; } for (ConsoleReportCollected& report : aConsoleReports) { if (report.propertiesFile() < nsContentUtils::PropertiesFile::PropertiesFile_COUNT) { AddConsoleReport(report.errorFlags(), report.category(), nsContentUtils::PropertiesFile(report.propertiesFile()), report.sourceFileURI(), report.lineNumber(), report.columnNumber(), report.messageName(), report.stringParams()); } } MaybeFlushConsoleReports(); } void HttpChannelChild::RecordChannelCompletionDurationForEarlyHint() { if (!mLoadGroup) { return; } uint32_t earlyHintType = 0; nsCOMPtr req; Unused << mLoadGroup->GetDefaultLoadRequest(getter_AddRefs(req)); if (nsCOMPtr httpChannel = do_QueryInterface(req)) { Unused << httpChannel->GetEarlyHintLinkType(&earlyHintType); } if (!earlyHintType) { return; } nsAutoCString earlyHintKey; if (mIsFromCache) { earlyHintKey.Append("cache_"_ns); } else { earlyHintKey.Append("net_"_ns); } if (earlyHintType & LinkStyle::ePRECONNECT) { earlyHintKey.Append("preconnect_"_ns); } if (earlyHintType & LinkStyle::ePRELOAD) { earlyHintKey.Append("preload_"_ns); earlyHintKey.Append(mEarlyHintPreloaderId ? "1"_ns : "0"_ns); } Telemetry::AccumulateTimeDelta(Telemetry::EH_PERF_CHANNEL_COMPLETION_TIME, earlyHintKey, mAsyncOpenTime, TimeStamp::Now()); } void HttpChannelChild::OnStopRequest( const nsresult& aChannelStatus, const ResourceTimingStructArgs& aTiming, const nsHttpHeaderArray& aResponseTrailers) { LOG(("HttpChannelChild::OnStopRequest [this=%p status=%" PRIx32 "]\n", this, static_cast(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 conv = do_QueryInterface(mCompressListener); if (conv) { conv->GetDecodedDataLength(&mDecodedBodySize); } ResourceTimingStructArgsToTimingsStruct(aTiming, mTransactionTimings); // Do not overwrite or adjust the original mAsyncOpenTime by timing.fetchStart // We must use the original child process time in order to account for child // side work and IPC transit overhead. // XXX: This depends on TimeStamp being equivalent across processes. // This is true for modern hardware but for older platforms it is not always // true. mRedirectStartTimeStamp = aTiming.redirectStart(); mRedirectEndTimeStamp = aTiming.redirectEnd(); // mTransferSize and mEncodedBodySize are set in ProcessOnStopRequest // TODO: check if we need to move assignments of other members to // ProcessOnStopRequest mCacheReadStart = aTiming.cacheReadStart(); mCacheReadEnd = aTiming.cacheReadEnd(); const TimeStamp now = TimeStamp::Now(); if (profiler_thread_is_being_profiled_for_markers()) { nsAutoCString requestMethod; GetRequestMethod(requestMethod); nsAutoCString contentType; if (mResponseHead) { mResponseHead->ContentType(contentType); } int32_t priority = PRIORITY_NORMAL; GetPriority(&priority); profiler_add_network_marker( mURI, requestMethod, priority, mChannelId, NetworkLoadType::LOAD_STOP, mLastStatusReported, now, mTransferSize, kCacheUnknown, mLoadInfo->GetInnerWindowID(), mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0, &mTransactionTimings, std::move(mSource), Some(nsDependentCString(contentType.get()))); } RecordChannelCompletionDurationForEarlyHint(); TimeDuration channelCompletionDuration = now - mAsyncOpenTime; if (mIsFromCache) { PerfStats::RecordMeasurement(PerfStats::Metric::HttpChannelCompletion_Cache, channelCompletionDuration); } else { PerfStats::RecordMeasurement( PerfStats::Metric::HttpChannelCompletion_Network, channelCompletionDuration); } PerfStats::RecordMeasurement(PerfStats::Metric::HttpChannelCompletion, channelCompletionDuration); if (!aTiming.responseEnd().IsNull()) { nsAutoCString cosString; ClassOfService::ToString(mClassOfService, cosString); Telemetry::AccumulateTimeDelta( Telemetry::NETWORK_RESPONSE_END_PARENT_TO_CONTENT_MS, cosString, aTiming.responseEnd(), now); PerfStats::RecordMeasurement( PerfStats::Metric::HttpChannelResponseEndParentToContent, now - aTiming.responseEnd()); } if (!mOnStopRequestStartTime.IsNull()) { PerfStats::RecordMeasurement(PerfStats::Metric::OnStopRequestToContent, now - mOnStopRequestStartTime); } mResponseTrailers = MakeUnique(aResponseTrailers); DoPreOnStopRequest(aChannelStatus); { // We must flush the queue before we Send__delete__ // (although we really shouldn't receive any msgs after OnStop), // so make sure this goes out of scope before then. AutoEventEnqueuer ensureSerialDispatch(mEventQ); DoOnStopRequest(this, aChannelStatus); // DoOnStopRequest() calls ReleaseListeners() } } void HttpChannelChild::ContinueOnStopRequest() { // If we're a multi-part stream, then don't cleanup yet, and we'll do so // in OnAfterLastPart. if (mMultiPartID) { LOG( ("HttpChannelChild::OnStopRequest - Expecting future parts on a " "multipart channel postpone cleaning up.")); return; } CollectMixedContentTelemetry(); CleanupBackgroundChannel(); // If there is a possibility we might want to write alt data to the cache // entry, we keep the channel alive. We still send the DocumentChannelCleanup // message but request the cache entry to be kept by the parent. // If the channel has failed, the cache entry is in a non-writtable state and // we want to release it to not block following consumers. if (NS_SUCCEEDED(mStatus) && !mPreferredCachedAltDataTypes.IsEmpty()) { mKeptAlive = true; SendDocumentChannelCleanup(false); // don't clear cache entry return; } if (mLoadFlags & LOAD_DOCUMENT_URI) { // Keep IPDL channel open, but only for updating security info. // If IPDL is already closed, then do nothing. if (CanSend()) { mKeptAlive = true; SendDocumentChannelCleanup(true); } } else { // The parent process will respond by sending a DeleteSelf message and // making sure not to send any more messages after that. TrySendDeletingChannel(); } } void HttpChannelChild::DoPreOnStopRequest(nsresult aStatus) { AUTO_PROFILER_LABEL("HttpChannelChild::DoPreOnStopRequest", NETWORK); LOG(("HttpChannelChild::DoPreOnStopRequest [this=%p status=%" PRIx32 "]\n", this, static_cast(aStatus))); StoreIsPending(false); MaybeReportTimingData(); if (!mCanceled && NS_SUCCEEDED(mStatus)) { mStatus = aStatus; } CollectOMTTelemetry(); } void HttpChannelChild::CollectOMTTelemetry() { MOZ_ASSERT(NS_IsMainThread()); // Only collect telemetry for HTTP channel that is loaded successfully and // completely. if (mCanceled || NS_FAILED(mStatus)) { return; } // Use content policy type to accumulate data by usage. nsAutoCString key( NS_CP_ContentTypeName(mLoadInfo->InternalContentPolicyType())); Telemetry::AccumulateCategoricalKeyed( key, static_cast(mOMTResult)); } // 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 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]\n", this)); MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!LoadIsPending()); auto checkForBlockedContent = [&]() { // NB: We use aChannelStatus here instead of mStatus because if there was an // nsCORSListenerProxy on this request, it will override the tracking // protection's return value. if (UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode( aChannelStatus) || aChannelStatus == NS_ERROR_MALWARE_URI || aChannelStatus == NS_ERROR_UNWANTED_URI || aChannelStatus == NS_ERROR_BLOCKED_URI || aChannelStatus == NS_ERROR_HARMFUL_URI || aChannelStatus == NS_ERROR_PHISHING_URI) { nsCString list, provider, fullhash; nsresult rv = GetMatchedList(list); NS_ENSURE_SUCCESS_VOID(rv); rv = GetMatchedProvider(provider); NS_ENSURE_SUCCESS_VOID(rv); rv = GetMatchedFullHash(fullhash); NS_ENSURE_SUCCESS_VOID(rv); UrlClassifierCommon::SetBlockedContent(this, aChannelStatus, list, provider, fullhash); } }; checkForBlockedContent(); MaybeLogCOEPError(aChannelStatus); // See bug 1587686. If the redirect setup is not completed, the post-redirect // channel will be not opened and mListener will be null. MOZ_ASSERT(mListener || !LoadWasOpened()); if (!mListener) { return; } MOZ_ASSERT(!LoadOnStopRequestCalled(), "We should not call OnStopRequest twice"); // notify "http-on-before-stop-request" observers gHttpHandler->OnBeforeStopRequest(this); if (mListener) { nsCOMPtr 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(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(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(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::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(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 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(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 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(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 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 doc; mLoadInfo->GetLoadingDocument(getter_AddRefs(doc)); FlushConsoleReports(doc); } } } mozilla::ipc::IPCResult HttpChannelChild::RecvReportSecurityMessage( const nsAString& messageTag, const nsAString& messageCategory) { DebugOnly rv = AddSecurityMessage(messageTag, messageCategory); MOZ_ASSERT(NS_SUCCEEDED(rv)); return IPC_OK(); } mozilla::ipc::IPCResult HttpChannelChild::RecvRedirect1Begin( const uint32_t& aRegistrarId, nsIURI* aNewUri, const uint32_t& aNewLoadFlags, const uint32_t& aRedirectFlags, const ParentLoadInfoForwarderArgs& aLoadInfoForwarder, const nsHttpResponseHead& aResponseHead, nsITransportSecurityInfo* aSecurityInfo, const uint64_t& aChannelId, const NetAddr& aOldPeerAddr, const ResourceTimingStructArgs& aTiming) { // TODO: handle security info LOG(("HttpChannelChild::RecvRedirect1Begin [this=%p]\n", this)); // We set peer address of child to the old peer, // Then it will be updated to new peer in OnStartRequest mPeerAddr = aOldPeerAddr; // Cookies headers should not be visible to the child process MOZ_ASSERT(!nsHttpResponseHead(aResponseHead).HasHeader(nsHttp::Set_Cookie)); mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent( this, [self = UnsafePtr(this), aRegistrarId, newUri = RefPtr{aNewUri}, aNewLoadFlags, aRedirectFlags, aLoadInfoForwarder, aResponseHead, aSecurityInfo = nsCOMPtr{aSecurityInfo}, aChannelId, aTiming]() { self->Redirect1Begin(aRegistrarId, newUri, aNewLoadFlags, aRedirectFlags, aLoadInfoForwarder, aResponseHead, aSecurityInfo, aChannelId, aTiming); })); return IPC_OK(); } nsresult HttpChannelChild::SetupRedirect(nsIURI* uri, const nsHttpResponseHead* responseHead, const uint32_t& redirectFlags, nsIChannel** outChannel) { LOG(("HttpChannelChild::SetupRedirect [this=%p]\n", this)); if (mCanceled) { return NS_ERROR_ABORT; } nsresult rv; nsCOMPtr ioService; rv = gHttpHandler->GetIOService(getter_AddRefs(ioService)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr newChannel; nsCOMPtr 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(*responseHead); bool rewriteToGET = HttpBaseChannel::ShouldRewriteRedirectToGET( mResponseHead->Status(), mRequestHead.ParsedMethod()); rv = SetupReplacementChannel(uri, newChannel, !rewriteToGET, redirectFlags); NS_ENSURE_SUCCESS(rv, rv); mRedirectChannelChild = do_QueryInterface(newChannel); newChannel.forget(outChannel); return NS_OK; } void HttpChannelChild::Redirect1Begin( const uint32_t& registrarId, nsIURI* newOriginalURI, const uint32_t& newLoadFlags, const uint32_t& redirectFlags, const ParentLoadInfoForwarderArgs& loadInfoForwarder, const nsHttpResponseHead& responseHead, nsITransportSecurityInfo* securityInfo, const uint64_t& channelId, const ResourceTimingStructArgs& timing) { nsresult rv; LOG(("HttpChannelChild::Redirect1Begin [this=%p]\n", this)); MOZ_ASSERT(newOriginalURI, "newOriginalURI should not be null"); ipc::MergeParentLoadInfoForwarder(loadInfoForwarder, mLoadInfo); ResourceTimingStructArgsToTimingsStruct(timing, mTransactionTimings); if (profiler_thread_is_being_profiled_for_markers()) { nsAutoCString requestMethod; GetRequestMethod(requestMethod); nsAutoCString contentType; responseHead.ContentType(contentType); profiler_add_network_marker( mURI, requestMethod, mPriority, mChannelId, NetworkLoadType::LOAD_REDIRECT, mLastStatusReported, TimeStamp::Now(), 0, kCacheUnknown, mLoadInfo->GetInnerWindowID(), mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0, &mTransactionTimings, std::move(mSource), Some(nsDependentCString(contentType.get())), newOriginalURI, redirectFlags, channelId); } mSecurityInfo = securityInfo; nsCOMPtr 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 httpChannel = do_QueryInterface(mRedirectChannelChild); if (httpChannel) { rv = httpChannel->SetChannelId(channelId); MOZ_ASSERT(NS_SUCCEEDED(rv)); } mRedirectChannelChild->ConnectParent(registrarId); } nsCOMPtr 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 redirectChannel = do_QueryInterface(mRedirectChannelChild); MOZ_ASSERT(redirectChannel); mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent( this, [self = UnsafePtr(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 chan = do_QueryInterface(redirectChannel); RefPtr httpChannelChild = static_cast(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(status))); mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent( this, [self = UnsafePtr(this), status]() { nsCOMPtr vetoHook; self->GetCallback(vetoHook); if (vetoHook) { vetoHook->OnRedirectResult(status); } if (RefPtr 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(aIsThirdParty), aClassificationFlags, this)); MOZ_ASSERT(OnSocketThread()); mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent( this, [self = UnsafePtr(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(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 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(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 vetoHook; GetCallback(vetoHook); if (vetoHook) { vetoHook->OnRedirectResult(NS_OK); } // Chrome channel has been AsyncOpen'd. Reflect this in child. if (mRedirectChannelChild) { rv = mRedirectChannelChild->CompleteRedirectSetup(mListener); #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED mSuccesfullyRedirected = NS_SUCCEEDED(rv); #endif } CleanupRedirectingChannel(rv); } void HttpChannelChild::CleanupRedirectingChannel(nsresult rv) { // Redirecting to new channel: shut this down and init new channel if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, NS_BINDING_ABORTED); if (NS_SUCCEEDED(rv)) { mLoadInfo->AppendRedirectHistoryEntry(this, false); } else { NS_WARNING("CompleteRedirectSetup failed, HttpChannelChild already open?"); } // Release ref to new channel. mRedirectChannelChild = nullptr; NotifyOrReleaseListeners(rv); CleanupBackgroundChannel(); } //----------------------------------------------------------------------------- // HttpChannelChild::nsIChildChannel //----------------------------------------------------------------------------- NS_IMETHODIMP HttpChannelChild::ConnectParent(uint32_t registrarId) { LOG(("HttpChannelChild::ConnectParent [this=%p, id=%" PRIu32 "]\n", this, registrarId)); MOZ_ASSERT(NS_IsMainThread()); mozilla::dom::BrowserChild* browserChild = nullptr; nsCOMPtr iBrowserChild; GetCallback(iBrowserChild); if (iBrowserChild) { browserChild = static_cast(iBrowserChild.get()); } if (browserChild && !browserChild->IPCOpen()) { return NS_ERROR_FAILURE; } ContentChild* cc = static_cast(gNeckoChild->Manager()); if (cc->IsShuttingDown()) { return NS_ERROR_FAILURE; } HttpBaseChannel::SetDocshellUserAgentOverride(); // This must happen before the constructor message is sent. Otherwise messages // from the parent could arrive quickly and be delivered to the wrong event // target. SetEventTarget(); if (browserChild) { MOZ_ASSERT(browserChild->WebNavigation()); if (BrowsingContext* bc = browserChild->GetBrowsingContext()) { mBrowserId = bc->BrowserId(); } } HttpChannelConnectArgs connectArgs(registrarId); if (!gNeckoChild->SendPHttpChannelConstructor( this, browserChild, IPC::SerializedLoadContext(this), connectArgs)) { return NS_ERROR_FAILURE; } { MutexAutoLock lock(mBgChildMutex); MOZ_ASSERT(!mBgChild); MOZ_ASSERT(!mBgInitFailCallback); mBgInitFailCallback = NewRunnableMethod( "HttpChannelChild::OnRedirectVerifyCallback", this, &HttpChannelChild::OnRedirectVerifyCallback, NS_ERROR_FAILURE); RefPtr bgChild = new HttpBackgroundChannelChild(); MOZ_RELEASE_ASSERT(gSocketTransportService); RefPtr self = this; nsresult rv = gSocketTransportService->Dispatch( NewRunnableMethod>( "HttpBackgroundChannelChild::Init", bgChild, &HttpBackgroundChannelChild::Init, std::move(self)), NS_DISPATCH_NORMAL); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } mBgChild = std::move(bgChild); #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED mEverHadBgChildAtConnectParent = true; #endif } // Should wait for CompleteRedirectSetup to set the listener. mEventQ->Suspend(); MOZ_ASSERT(!mSuspendForWaitCompleteRedirectSetup); mSuspendForWaitCompleteRedirectSetup = true; // Connect to socket process after mEventQ is suspended. MaybeConnectToSocketProcess(); return NS_OK; } NS_IMETHODIMP HttpChannelChild::CompleteRedirectSetup(nsIStreamListener* aListener) { LOG(("HttpChannelChild::CompleteRedirectSetup [this=%p]\n", this)); MOZ_ASSERT(NS_IsMainThread()); NS_ENSURE_TRUE(!LoadIsPending(), NS_ERROR_IN_PROGRESS); NS_ENSURE_TRUE(!LoadWasOpened(), NS_ERROR_ALREADY_OPENED); // Resume the suspension in ConnectParent. auto eventQueueResumeGuard = MakeScopeExit([&] { MOZ_ASSERT(mSuspendForWaitCompleteRedirectSetup); mEventQ->Resume(); mSuspendForWaitCompleteRedirectSetup = false; }); /* * No need to check for cancel: we don't get here if nsHttpChannel canceled * before AsyncOpen(); if it's canceled after that, OnStart/Stop will just * get called with error code as usual. So just setup mListener and make the * channel reflect AsyncOpen'ed state. */ mLastStatusReported = TimeStamp::Now(); if (profiler_thread_is_being_profiled_for_markers()) { nsAutoCString requestMethod; GetRequestMethod(requestMethod); profiler_add_network_marker( mURI, requestMethod, mPriority, mChannelId, NetworkLoadType::LOAD_START, mChannelCreationTimestamp, mLastStatusReported, 0, kCacheUnknown, mLoadInfo->GetInnerWindowID(), mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0); } StoreIsPending(true); StoreWasOpened(true); mListener = aListener; // add ourselves to the load group. if (mLoadGroup) mLoadGroup->AddRequest(this, nullptr); // We already have an open IPDL connection to the parent. If on-modify-request // listeners or load group observers canceled us, let the parent handle it // and send it back to us naturally. return NS_OK; } //----------------------------------------------------------------------------- // HttpChannelChild::nsIAsyncVerifyRedirectCallback //----------------------------------------------------------------------------- NS_IMETHODIMP HttpChannelChild::OnRedirectVerifyCallback(nsresult aResult) { LOG(("HttpChannelChild::OnRedirectVerifyCallback [this=%p]\n", this)); MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr redirectURI; DebugOnly rv = NS_OK; nsCOMPtr newHttpChannel = do_QueryInterface(mRedirectChannelChild); if (NS_SUCCEEDED(aResult) && !mRedirectChannelChild) { // mRedirectChannelChild doesn't exist means we're redirecting to a protocol // that doesn't implement nsIChildChannel. The redirect result should be set // as failed by veto listeners and shouldn't enter this condition. As the // last resort, we synthesize the error result as NS_ERROR_DOM_BAD_URI here // to let nsHttpChannel::ContinueProcessResponse2 know it's redirecting to // another protocol and throw an error. LOG((" redirecting to a protocol that doesn't implement nsIChildChannel")); aResult = NS_ERROR_DOM_BAD_URI; } nsCOMPtr referrerInfo; if (newHttpChannel) { // Must not be called until after redirect observers called. newHttpChannel->SetOriginalURI(mOriginalURI); referrerInfo = newHttpChannel->GetReferrerInfo(); } RequestHeaderTuples emptyHeaders; RequestHeaderTuples* headerTuples = &emptyHeaders; nsLoadFlags loadFlags = 0; Maybe corsPreflightArgs; nsCOMPtr newHttpChannelChild = do_QueryInterface(mRedirectChannelChild); if (newHttpChannelChild && NS_SUCCEEDED(aResult)) { rv = newHttpChannelChild->AddCookiesToRequest(); MOZ_ASSERT(NS_SUCCEEDED(rv)); rv = newHttpChannelChild->GetClientSetRequestHeaders(&headerTuples); MOZ_ASSERT(NS_SUCCEEDED(rv)); newHttpChannelChild->GetClientSetCorsPreflightParameters(corsPreflightArgs); } if (NS_SUCCEEDED(aResult)) { // Note: this is where we would notify "http-on-modify-response" observers. // We have deliberately disabled this for child processes (see bug 806753) // // After we verify redirect, nsHttpChannel may hit the network: must give // "http-on-modify-request" observers the chance to cancel before that. // base->CallOnModifyRequestObservers(); nsCOMPtr newHttpChannelInternal = do_QueryInterface(mRedirectChannelChild); if (newHttpChannelInternal) { Unused << newHttpChannelInternal->GetApiRedirectToURI( getter_AddRefs(redirectURI)); } nsCOMPtr request = do_QueryInterface(mRedirectChannelChild); if (request) { request->GetLoadFlags(&loadFlags); } } uint32_t sourceRequestBlockingReason = 0; mLoadInfo->GetRequestBlockingReason(&sourceRequestBlockingReason); Maybe targetLoadInfoForwarder; nsCOMPtr newChannel = do_QueryInterface(mRedirectChannelChild); if (newChannel) { ChildLoadInfoForwarderArgs args; nsCOMPtr loadInfo = newChannel->LoadInfo(); LoadInfoToChildLoadInfoForwarder(loadInfo, &args); targetLoadInfoForwarder.emplace(args); } if (CanSend()) { SendRedirect2Verify(aResult, *headerTuples, sourceRequestBlockingReason, targetLoadInfoForwarder, loadFlags, referrerInfo, redirectURI, corsPreflightArgs); } return NS_OK; } //----------------------------------------------------------------------------- // HttpChannelChild::nsIRequest //----------------------------------------------------------------------------- NS_IMETHODIMP HttpChannelChild::SetCanceledReason(const nsACString& aReason) { return SetCanceledReasonImpl(aReason); } NS_IMETHODIMP HttpChannelChild::GetCanceledReason(nsACString& aReason) { return GetCanceledReasonImpl(aReason); } NS_IMETHODIMP HttpChannelChild::CancelWithReason(nsresult aStatus, const nsACString& aReason) { return CancelWithReasonImpl(aStatus, aReason); } NS_IMETHODIMP HttpChannelChild::Cancel(nsresult aStatus) { LOG(("HttpChannelChild::Cancel [this=%p, status=%" PRIx32 "]\n", this, static_cast(aStatus))); // only logging on parent is necessary Maybe logStack = CallingScriptLocationString(); Maybe logOnParent; if (logStack.isSome()) { logOnParent = Some(""_ns); logOnParent->AppendPrintf( "[this=%p] cancelled call in child process from script: %s", this, logStack->get()); } MOZ_ASSERT(NS_IsMainThread()); if (!mCanceled) { // If this cancel occurs before nsHttpChannel has been set up, AsyncOpen // is responsible for cleaning up. mCanceled = true; mStatus = aStatus; bool remoteChannelExists = RemoteChannelExists(); #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED mCanSendAtCancel = CanSend(); mRemoteChannelExistedAtCancel = remoteChannelExists; #endif if (remoteChannelExists) { SendCancel(aStatus, mLoadInfo->GetRequestBlockingReason(), mCanceledReason, logOnParent); } else if (MOZ_UNLIKELY(!LoadOnStartRequestCalled() || !LoadOnStopRequestCalled())) { Unused << AsyncAbort(mStatus); } } return NS_OK; } NS_IMETHODIMP HttpChannelChild::Suspend() { LOG(("HttpChannelChild::Suspend [this=%p, mSuspendCount=%" PRIu32 "\n", this, mSuspendCount + 1)); MOZ_ASSERT(NS_IsMainThread()); LogCallingScriptLocation(this); // SendSuspend only once, when suspend goes from 0 to 1. // Don't SendSuspend at all if we're diverting callbacks to the parent; // suspend will be called at the correct time in the parent itself. if (!mSuspendCount++) { if (RemoteChannelExists()) { SendSuspend(); mSuspendSent = true; } } mEventQ->Suspend(); return NS_OK; } NS_IMETHODIMP HttpChannelChild::Resume() { LOG(("HttpChannelChild::Resume [this=%p, mSuspendCount=%" PRIu32 "\n", this, mSuspendCount - 1)); MOZ_ASSERT(NS_IsMainThread()); NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED); LogCallingScriptLocation(this); nsresult rv = NS_OK; // SendResume only once, when suspend count drops to 0. // Don't SendResume at all if we're diverting callbacks to the parent (unless // suspend was sent earlier); otherwise, resume will be called at the correct // time in the parent itself. if (!--mSuspendCount) { if (RemoteChannelExists() && mSuspendSent) { SendResume(); } if (mCallOnResume) { nsCOMPtr neckoTarget = GetNeckoTarget(); MOZ_ASSERT(neckoTarget); RefPtr self = this; std::function callOnResume = nullptr; std::swap(callOnResume, mCallOnResume); rv = neckoTarget->Dispatch( NS_NewRunnableFunction( "net::HttpChannelChild::mCallOnResume", [callOnResume, self{std::move(self)}]() { callOnResume(self); }), NS_DISPATCH_NORMAL); } } mEventQ->Resume(); return rv; } //----------------------------------------------------------------------------- // HttpChannelChild::nsIChannel //----------------------------------------------------------------------------- NS_IMETHODIMP HttpChannelChild::GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) { NS_ENSURE_ARG_POINTER(aSecurityInfo); *aSecurityInfo = do_AddRef(mSecurityInfo).take(); return NS_OK; } NS_IMETHODIMP HttpChannelChild::AsyncOpen(nsIStreamListener* aListener) { LOG(("HttpChannelChild::AsyncOpen [this=%p uri=%s]\n", this, mSpec.get())); nsresult rv = AsyncOpenInternal(aListener); if (NS_FAILED(rv)) { uint32_t blockingReason = 0; mLoadInfo->GetRequestBlockingReason(&blockingReason); LOG( ("HttpChannelChild::AsyncOpen failed [this=%p rv=0x%08x " "blocking-reason=%u]\n", this, static_cast(rv), blockingReason)); gHttpHandler->OnFailedOpeningRequest(this); } #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED mAsyncOpenSucceeded = NS_SUCCEEDED(rv); #endif return rv; } nsresult HttpChannelChild::AsyncOpenInternal(nsIStreamListener* aListener) { nsresult rv; nsCOMPtr listener = aListener; rv = nsContentSecurityManager::doContentSecurityCheck(this, listener); if (NS_WARN_IF(NS_FAILED(rv))) { ReleaseListeners(); return rv; } MOZ_ASSERT( mLoadInfo->GetSecurityMode() == 0 || mLoadInfo->GetInitialSecurityCheckDone() || (mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL && mLoadInfo->GetLoadingPrincipal() && mLoadInfo->GetLoadingPrincipal()->IsSystemPrincipal()), "security flags in loadInfo but doContentSecurityCheck() not called"); LogCallingScriptLocation(this); if (!mLoadGroup && !mCallbacks) { // If no one called SetLoadGroup or SetNotificationCallbacks, the private // state has not been updated on PrivateBrowsingChannel (which we derive // from) Hence, we have to call UpdatePrivateBrowsing() here UpdatePrivateBrowsing(); } #ifdef DEBUG AssertPrivateBrowsingId(); #endif if (mCanceled) { ReleaseListeners(); return mStatus; } NS_ENSURE_TRUE(gNeckoChild != nullptr, NS_ERROR_FAILURE); NS_ENSURE_ARG_POINTER(listener); NS_ENSURE_TRUE(!LoadIsPending(), NS_ERROR_IN_PROGRESS); NS_ENSURE_TRUE(!LoadWasOpened(), NS_ERROR_ALREADY_OPENED); if (MaybeWaitForUploadStreamNormalization(listener, nullptr)) { return NS_OK; } if (!LoadAsyncOpenTimeOverriden()) { mAsyncOpenTime = TimeStamp::Now(); } // Port checked in parent, but duplicate here so we can return with error // immediately rv = NS_CheckPortSafety(mURI); if (NS_FAILED(rv)) { ReleaseListeners(); return rv; } nsAutoCString cookie; if (NS_SUCCEEDED(mRequestHead.GetHeader(nsHttp::Cookie, cookie))) { mUserSetCookieHeader = cookie; } DebugOnly check = AddCookiesToRequest(); MOZ_ASSERT(NS_SUCCEEDED(check)); // // NOTE: From now on we must return NS_OK; all errors must be handled via // OnStart/OnStopRequest // // We notify "http-on-opening-request" observers in the child // process so that devtools can capture a stack trace at the // appropriate spot. See bug 806753 for some information about why // other http-* notifications are disabled in child processes. gHttpHandler->OnOpeningRequest(this); mLastStatusReported = TimeStamp::Now(); if (profiler_thread_is_being_profiled_for_markers()) { nsAutoCString requestMethod; GetRequestMethod(requestMethod); profiler_add_network_marker( mURI, requestMethod, mPriority, mChannelId, NetworkLoadType::LOAD_START, mChannelCreationTimestamp, mLastStatusReported, 0, kCacheUnknown, mLoadInfo->GetInnerWindowID(), mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0); } StoreIsPending(true); StoreWasOpened(true); mListener = listener; if (mCanceled) { // We may have been canceled already, either by on-modify-request // listeners or by load group observers; in that case, don't create IPDL // connection. See nsHttpChannel::AsyncOpen(). ReleaseListeners(); return mStatus; } // Set user agent override from docshell HttpBaseChannel::SetDocshellUserAgentOverride(); rv = ContinueAsyncOpen(); if (NS_FAILED(rv)) { ReleaseListeners(); } return rv; } // Assigns an nsISerialEventTarget to our IPDL actor so that IPC messages are // sent to the correct DocGroup/TabGroup. void HttpChannelChild::SetEventTarget() { MutexAutoLock lock(mEventTargetMutex); mNeckoTarget = GetMainThreadSerialEventTarget(); } already_AddRefed HttpChannelChild::GetNeckoTarget() { nsCOMPtr target; { MutexAutoLock lock(mEventTargetMutex); target = mNeckoTarget; } if (!target) { target = GetMainThreadSerialEventTarget(); } return target.forget(); } already_AddRefed HttpChannelChild::GetODATarget() { nsCOMPtr target; { MutexAutoLock lock(mEventTargetMutex); if (mODATarget) { target = mODATarget; } else { target = mNeckoTarget; } } if (!target) { target = GetMainThreadSerialEventTarget(); } return target.forget(); } nsresult HttpChannelChild::ContinueAsyncOpen() { nsresult rv; // // Send request to the chrome process... // mozilla::dom::BrowserChild* browserChild = nullptr; nsCOMPtr iBrowserChild; GetCallback(iBrowserChild); if (iBrowserChild) { browserChild = static_cast(iBrowserChild.get()); } // This id identifies the inner window's top-level document, // which changes on every new load or navigation. uint64_t contentWindowId = 0; TimeStamp navigationStartTimeStamp; if (browserChild) { MOZ_ASSERT(browserChild->WebNavigation()); if (RefPtr document = browserChild->GetTopLevelDocument()) { contentWindowId = document->InnerWindowID(); nsDOMNavigationTiming* navigationTiming = document->GetNavigationTiming(); if (navigationTiming) { navigationStartTimeStamp = navigationTiming->GetNavigationStartTimeStamp(); } } if (BrowsingContext* bc = browserChild->GetBrowsingContext()) { mBrowserId = bc->BrowserId(); } } SetTopLevelContentWindowId(contentWindowId); if (browserChild && !browserChild->IPCOpen()) { return NS_ERROR_FAILURE; } ContentChild* cc = static_cast(gNeckoChild->Manager()); if (cc->IsShuttingDown()) { return NS_ERROR_FAILURE; } // add ourselves to the load group. if (mLoadGroup) { mLoadGroup->AddRequest(this, nullptr); } HttpChannelOpenArgs openArgs; // No access to HttpChannelOpenArgs members, but they each have a // function with the struct name that returns a ref. openArgs.uri() = mURI; openArgs.original() = mOriginalURI; openArgs.doc() = mDocumentURI; openArgs.apiRedirectTo() = mAPIRedirectToURI; openArgs.loadFlags() = mLoadFlags; openArgs.requestHeaders() = mClientSetRequestHeaders; mRequestHead.Method(openArgs.requestMethod()); openArgs.preferredAlternativeTypes() = mPreferredCachedAltDataTypes.Clone(); openArgs.referrerInfo() = mReferrerInfo; if (mUploadStream) { MOZ_ALWAYS_TRUE(SerializeIPCStream(do_AddRef(mUploadStream), openArgs.uploadStream(), /* aAllowLazy */ false)); } Maybe optionalCorsPreflightArgs; GetClientSetCorsPreflightParameters(optionalCorsPreflightArgs); // NB: This call forces us to cache mTopWindowURI if we haven't already. nsCOMPtr uri; GetTopWindowURI(mURI, getter_AddRefs(uri)); openArgs.topWindowURI() = mTopWindowURI; openArgs.preflightArgs() = optionalCorsPreflightArgs; openArgs.uploadStreamHasHeaders() = LoadUploadStreamHasHeaders(); openArgs.priority() = mPriority; openArgs.classOfService() = mClassOfService; openArgs.redirectionLimit() = mRedirectionLimit; openArgs.allowSTS() = LoadAllowSTS(); openArgs.thirdPartyFlags() = LoadThirdPartyFlags(); openArgs.resumeAt() = mSendResumeAt; openArgs.startPos() = mStartPos; openArgs.entityID() = mEntityID; openArgs.allowSpdy() = LoadAllowSpdy(); openArgs.allowHttp3() = LoadAllowHttp3(); openArgs.allowAltSvc() = LoadAllowAltSvc(); openArgs.beConservative() = LoadBeConservative(); openArgs.bypassProxy() = BypassProxy(); openArgs.tlsFlags() = mTlsFlags; openArgs.initialRwin() = mInitialRwin; openArgs.cacheKey() = mCacheKey; openArgs.blockAuthPrompt() = LoadBlockAuthPrompt(); openArgs.allowStaleCacheContent() = LoadAllowStaleCacheContent(); openArgs.preferCacheLoadOverBypass() = LoadPreferCacheLoadOverBypass(); openArgs.contentTypeHint() = mContentTypeHint; rv = mozilla::ipc::LoadInfoToLoadInfoArgs(mLoadInfo, &openArgs.loadInfo()); NS_ENSURE_SUCCESS(rv, rv); EnsureRequestContextID(); openArgs.requestContextID() = mRequestContextID; openArgs.requestMode() = mRequestMode; openArgs.redirectMode() = mRedirectMode; openArgs.channelId() = mChannelId; openArgs.integrityMetadata() = mIntegrityMetadata; openArgs.contentWindowId() = contentWindowId; openArgs.browserId() = mBrowserId; LOG(("HttpChannelChild::ContinueAsyncOpen this=%p gid=%" PRIu64 " browser id=%" PRIx64, this, mChannelId, mBrowserId)); openArgs.launchServiceWorkerStart() = mLaunchServiceWorkerStart; openArgs.launchServiceWorkerEnd() = mLaunchServiceWorkerEnd; openArgs.dispatchFetchEventStart() = mDispatchFetchEventStart; openArgs.dispatchFetchEventEnd() = mDispatchFetchEventEnd; openArgs.handleFetchEventStart() = mHandleFetchEventStart; openArgs.handleFetchEventEnd() = mHandleFetchEventEnd; openArgs.forceMainDocumentChannel() = LoadForceMainDocumentChannel(); openArgs.navigationStartTimeStamp() = navigationStartTimeStamp; openArgs.earlyHintPreloaderId() = mEarlyHintPreloaderId; openArgs.classicScriptHintCharset() = mClassicScriptHintCharset; openArgs.isUserAgentHeaderModified() = LoadIsUserAgentHeaderModified(); RefPtr doc; mLoadInfo->GetLoadingDocument(getter_AddRefs(doc)); if (doc) { nsAutoString documentCharacterSet; doc->GetCharacterSet(documentCharacterSet); openArgs.documentCharacterSet() = documentCharacterSet; } // This must happen before the constructor message is sent. Otherwise messages // from the parent could arrive quickly and be delivered to the wrong event // target. SetEventTarget(); if (!gNeckoChild->SendPHttpChannelConstructor( this, browserChild, IPC::SerializedLoadContext(this), openArgs)) { return NS_ERROR_FAILURE; } { MutexAutoLock lock(mBgChildMutex); MOZ_RELEASE_ASSERT(gSocketTransportService); // Service worker might use the same HttpChannelChild to do async open // twice. Need to disconnect with previous background channel before // creating the new one, to prevent receiving further notification // from it. if (mBgChild) { RefPtr prevBgChild = std::move(mBgChild); gSocketTransportService->Dispatch( NewRunnableMethod("HttpBackgroundChannelChild::OnChannelClosed", prevBgChild, &HttpBackgroundChannelChild::OnChannelClosed), NS_DISPATCH_NORMAL); } MOZ_ASSERT(!mBgInitFailCallback); mBgInitFailCallback = NewRunnableMethod( "HttpChannelChild::FailedAsyncOpen", this, &HttpChannelChild::FailedAsyncOpen, NS_ERROR_FAILURE); RefPtr bgChild = new HttpBackgroundChannelChild(); RefPtr self = this; nsresult rv = gSocketTransportService->Dispatch( NewRunnableMethod>( "HttpBackgroundChannelChild::Init", bgChild, &HttpBackgroundChannelChild::Init, self), NS_DISPATCH_NORMAL); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } mBgChild = std::move(bgChild); #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED mEverHadBgChildAtAsyncOpen = true; #endif } MaybeConnectToSocketProcess(); return NS_OK; } //----------------------------------------------------------------------------- // HttpChannelChild::nsIHttpChannel //----------------------------------------------------------------------------- NS_IMETHODIMP HttpChannelChild::SetRequestHeader(const nsACString& aHeader, const nsACString& aValue, bool aMerge) { LOG(("HttpChannelChild::SetRequestHeader [this=%p]\n", this)); nsresult rv = HttpBaseChannel::SetRequestHeader(aHeader, aValue, aMerge); if (NS_FAILED(rv)) return rv; RequestHeaderTuple* tuple = mClientSetRequestHeaders.AppendElement(); if (!tuple) return NS_ERROR_OUT_OF_MEMORY; // Mark that the User-Agent header has been modified. if (nsHttp::ResolveAtom(aHeader) == nsHttp::User_Agent) { StoreIsUserAgentHeaderModified(true); } tuple->mHeader = aHeader; tuple->mValue = aValue; tuple->mMerge = aMerge; tuple->mEmpty = false; return NS_OK; } NS_IMETHODIMP HttpChannelChild::SetEmptyRequestHeader(const nsACString& aHeader) { LOG(("HttpChannelChild::SetEmptyRequestHeader [this=%p]\n", this)); nsresult rv = HttpBaseChannel::SetEmptyRequestHeader(aHeader); if (NS_FAILED(rv)) return rv; RequestHeaderTuple* tuple = mClientSetRequestHeaders.AppendElement(); if (!tuple) return NS_ERROR_OUT_OF_MEMORY; // Mark that the User-Agent header has been modified. if (nsHttp::ResolveAtom(aHeader) == nsHttp::User_Agent) { StoreIsUserAgentHeaderModified(true); } tuple->mHeader = aHeader; tuple->mMerge = false; tuple->mEmpty = true; return NS_OK; } NS_IMETHODIMP HttpChannelChild::RedirectTo(nsIURI* newURI) { // disabled until/unless addons run in child or something else needs this return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP HttpChannelChild::UpgradeToSecure() { // disabled until/unless addons run in child or something else needs this return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP HttpChannelChild::GetProtocolVersion(nsACString& aProtocolVersion) { aProtocolVersion = mProtocolVersion; return NS_OK; } //----------------------------------------------------------------------------- // HttpChannelChild::nsIHttpChannelInternal //----------------------------------------------------------------------------- NS_IMETHODIMP HttpChannelChild::GetIsAuthChannel(bool* aIsAuthChannel) { DROP_DEAD(); } //----------------------------------------------------------------------------- // HttpChannelChild::nsICacheInfoChannel //----------------------------------------------------------------------------- NS_IMETHODIMP HttpChannelChild::GetCacheTokenFetchCount(uint32_t* _retval) { NS_ENSURE_ARG_POINTER(_retval); MOZ_ASSERT(NS_IsMainThread()); if (!mCacheEntryAvailable && !mAltDataCacheEntryAvailable) { return NS_ERROR_NOT_AVAILABLE; } *_retval = mCacheFetchCount; return NS_OK; } NS_IMETHODIMP HttpChannelChild::GetCacheTokenExpirationTime(uint32_t* _retval) { NS_ENSURE_ARG_POINTER(_retval); MOZ_ASSERT(NS_IsMainThread()); if (!mCacheEntryAvailable) return NS_ERROR_NOT_AVAILABLE; *_retval = mCacheExpirationTime; return NS_OK; } NS_IMETHODIMP HttpChannelChild::IsFromCache(bool* value) { if (!LoadIsPending()) return NS_ERROR_NOT_AVAILABLE; *value = mIsFromCache; return NS_OK; } NS_IMETHODIMP HttpChannelChild::GetCacheEntryId(uint64_t* aCacheEntryId) { bool fromCache = false; if (NS_FAILED(IsFromCache(&fromCache)) || !fromCache || !mCacheEntryAvailable) { return NS_ERROR_NOT_AVAILABLE; } *aCacheEntryId = mCacheEntryId; return NS_OK; } NS_IMETHODIMP HttpChannelChild::IsRacing(bool* aIsRacing) { if (!LoadAfterOnStartRequestBegun()) { return NS_ERROR_NOT_AVAILABLE; } *aIsRacing = mIsRacing; return NS_OK; } NS_IMETHODIMP HttpChannelChild::GetCacheKey(uint32_t* cacheKey) { MOZ_ASSERT(NS_IsMainThread()); *cacheKey = mCacheKey; return NS_OK; } NS_IMETHODIMP HttpChannelChild::SetCacheKey(uint32_t cacheKey) { ENSURE_CALLED_BEFORE_ASYNC_OPEN(); mCacheKey = cacheKey; return NS_OK; } NS_IMETHODIMP HttpChannelChild::SetAllowStaleCacheContent(bool aAllowStaleCacheContent) { StoreAllowStaleCacheContent(aAllowStaleCacheContent); return NS_OK; } NS_IMETHODIMP HttpChannelChild::GetAllowStaleCacheContent(bool* aAllowStaleCacheContent) { NS_ENSURE_ARG(aAllowStaleCacheContent); *aAllowStaleCacheContent = LoadAllowStaleCacheContent(); return NS_OK; } NS_IMETHODIMP HttpChannelChild::SetForceValidateCacheContent( bool aForceValidateCacheContent) { StoreForceValidateCacheContent(aForceValidateCacheContent); return NS_OK; } NS_IMETHODIMP HttpChannelChild::GetForceValidateCacheContent( bool* aForceValidateCacheContent) { NS_ENSURE_ARG(aForceValidateCacheContent); *aForceValidateCacheContent = LoadForceValidateCacheContent(); return NS_OK; } NS_IMETHODIMP HttpChannelChild::SetPreferCacheLoadOverBypass( bool aPreferCacheLoadOverBypass) { StorePreferCacheLoadOverBypass(aPreferCacheLoadOverBypass); return NS_OK; } NS_IMETHODIMP HttpChannelChild::GetPreferCacheLoadOverBypass( bool* aPreferCacheLoadOverBypass) { NS_ENSURE_ARG(aPreferCacheLoadOverBypass); *aPreferCacheLoadOverBypass = LoadPreferCacheLoadOverBypass(); return NS_OK; } NS_IMETHODIMP HttpChannelChild::PreferAlternativeDataType( const nsACString& aType, const nsACString& aContentType, PreferredAlternativeDataDeliveryType aDeliverAltData) { ENSURE_CALLED_BEFORE_ASYNC_OPEN(); mPreferredCachedAltDataTypes.AppendElement(PreferredAlternativeDataTypeParams( nsCString(aType), nsCString(aContentType), aDeliverAltData)); return NS_OK; } const nsTArray& HttpChannelChild::PreferredAlternativeDataTypes() { return mPreferredCachedAltDataTypes; } NS_IMETHODIMP HttpChannelChild::GetAlternativeDataType(nsACString& aType) { // Must be called during or after OnStartRequest if (!LoadAfterOnStartRequestBegun()) { return NS_ERROR_NOT_AVAILABLE; } aType = mAvailableCachedAltDataType; return NS_OK; } NS_IMETHODIMP HttpChannelChild::OpenAlternativeOutputStream(const nsACString& aType, int64_t aPredictedSize, nsIAsyncOutputStream** _retval) { MOZ_ASSERT(NS_IsMainThread(), "Main thread only"); if (!CanSend()) { return NS_ERROR_NOT_AVAILABLE; } if (static_cast(gNeckoChild->Manager())->IsShuttingDown()) { return NS_ERROR_NOT_AVAILABLE; } nsCOMPtr neckoTarget = GetNeckoTarget(); MOZ_ASSERT(neckoTarget); RefPtr stream = new AltDataOutputStreamChild(); stream->AddIPDLReference(); if (!gNeckoChild->SendPAltDataOutputStreamConstructor( stream, nsCString(aType), aPredictedSize, WrapNotNull(this))) { return NS_ERROR_FAILURE; } stream.forget(_retval); return NS_OK; } NS_IMETHODIMP HttpChannelChild::GetOriginalInputStream(nsIInputStreamReceiver* aReceiver) { if (aReceiver == nullptr) { return NS_ERROR_INVALID_ARG; } if (!CanSend()) { return NS_ERROR_NOT_AVAILABLE; } mOriginalInputStreamReceiver = aReceiver; Unused << SendOpenOriginalCacheInputStream(); return NS_OK; } NS_IMETHODIMP HttpChannelChild::GetAlternativeDataInputStream(nsIInputStream** aInputStream) { NS_ENSURE_ARG_POINTER(aInputStream); nsCOMPtr is = mAltDataInputStream; is.forget(aInputStream); return NS_OK; } mozilla::ipc::IPCResult HttpChannelChild::RecvOriginalCacheInputStreamAvailable( const Maybe& aStream) { nsCOMPtr stream = DeserializeIPCStream(aStream); nsCOMPtr receiver; receiver.swap(mOriginalInputStreamReceiver); if (receiver) { receiver->OnInputStreamReady(stream); } return IPC_OK(); } //----------------------------------------------------------------------------- // HttpChannelChild::nsIResumableChannel //----------------------------------------------------------------------------- NS_IMETHODIMP HttpChannelChild::ResumeAt(uint64_t startPos, const nsACString& entityID) { LOG(("HttpChannelChild::ResumeAt [this=%p]\n", this)); ENSURE_CALLED_BEFORE_CONNECT(); mStartPos = startPos; mEntityID = entityID; mSendResumeAt = true; return NS_OK; } // GetEntityID is shared in HttpBaseChannel //----------------------------------------------------------------------------- // HttpChannelChild::nsISupportsPriority //----------------------------------------------------------------------------- NS_IMETHODIMP HttpChannelChild::SetPriority(int32_t aPriority) { LOG(("HttpChannelChild::SetPriority %p p=%d", this, aPriority)); int16_t newValue = clamped(aPriority, INT16_MIN, INT16_MAX); if (mPriority == newValue) return NS_OK; mPriority = newValue; if (RemoteChannelExists()) SendSetPriority(mPriority); return NS_OK; } //----------------------------------------------------------------------------- // HttpChannelChild::nsIClassOfService //----------------------------------------------------------------------------- NS_IMETHODIMP HttpChannelChild::SetClassFlags(uint32_t inFlags) { if (mClassOfService.Flags() == inFlags) { return NS_OK; } mClassOfService.SetFlags(inFlags); LOG(("HttpChannelChild %p ClassOfService flags=%lu inc=%d", this, mClassOfService.Flags(), mClassOfService.Incremental())); if (RemoteChannelExists()) { SendSetClassOfService(mClassOfService); } return NS_OK; } NS_IMETHODIMP HttpChannelChild::AddClassFlags(uint32_t inFlags) { mClassOfService.SetFlags(inFlags | mClassOfService.Flags()); LOG(("HttpChannelChild %p ClassOfService flags=%lu inc=%d", this, mClassOfService.Flags(), mClassOfService.Incremental())); if (RemoteChannelExists()) { SendSetClassOfService(mClassOfService); } return NS_OK; } NS_IMETHODIMP HttpChannelChild::ClearClassFlags(uint32_t inFlags) { mClassOfService.SetFlags(~inFlags & mClassOfService.Flags()); LOG(("HttpChannelChild %p ClassOfService=%lu", this, mClassOfService.Flags())); if (RemoteChannelExists()) { SendSetClassOfService(mClassOfService); } return NS_OK; } NS_IMETHODIMP HttpChannelChild::SetClassOfService(ClassOfService inCos) { mClassOfService = inCos; LOG(("HttpChannelChild %p ClassOfService flags=%lu inc=%d", this, mClassOfService.Flags(), mClassOfService.Incremental())); if (RemoteChannelExists()) { SendSetClassOfService(mClassOfService); } return NS_OK; } NS_IMETHODIMP HttpChannelChild::SetIncremental(bool inIncremental) { mClassOfService.SetIncremental(inIncremental); LOG(("HttpChannelChild %p ClassOfService flags=%lu inc=%d", this, mClassOfService.Flags(), mClassOfService.Incremental())); if (RemoteChannelExists()) { SendSetClassOfService(mClassOfService); } return NS_OK; } //----------------------------------------------------------------------------- // HttpChannelChild::nsIProxiedChannel //----------------------------------------------------------------------------- NS_IMETHODIMP HttpChannelChild::GetProxyInfo(nsIProxyInfo** aProxyInfo) { DROP_DEAD(); } NS_IMETHODIMP HttpChannelChild::GetHttpProxyConnectResponseCode( int32_t* aResponseCode) { DROP_DEAD(); } //----------------------------------------------------------------------------- // HttpChannelChild::nsIHttpChannelChild //----------------------------------------------------------------------------- NS_IMETHODIMP HttpChannelChild::AddCookiesToRequest() { HttpBaseChannel::AddCookiesToRequest(); return NS_OK; } NS_IMETHODIMP HttpChannelChild::GetClientSetRequestHeaders( RequestHeaderTuples** aRequestHeaders) { *aRequestHeaders = &mClientSetRequestHeaders; return NS_OK; } void HttpChannelChild::GetClientSetCorsPreflightParameters( Maybe& aArgs) { if (LoadRequireCORSPreflight()) { CorsPreflightArgs args; args.unsafeHeaders() = mUnsafeHeaders.Clone(); aArgs.emplace(args); } else { aArgs = Nothing(); } } NS_IMETHODIMP HttpChannelChild::RemoveCorsPreflightCacheEntry( nsIURI* aURI, nsIPrincipal* aPrincipal, const OriginAttributes& aOriginAttributes) { PrincipalInfo principalInfo; MOZ_ASSERT(aURI, "aURI should not be null"); nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool result = false; // Be careful to not attempt to send a message to the parent after the // actor has been destroyed. if (CanSend()) { result = SendRemoveCorsPreflightCacheEntry(aURI, principalInfo, aOriginAttributes); } return result ? NS_OK : NS_ERROR_FAILURE; } //----------------------------------------------------------------------------- // HttpChannelChild::nsIMuliPartChannel //----------------------------------------------------------------------------- NS_IMETHODIMP HttpChannelChild::GetBaseChannel(nsIChannel** aBaseChannel) { if (!mMultiPartID) { MOZ_ASSERT(false, "Not a multipart channel"); return NS_ERROR_NOT_AVAILABLE; } nsCOMPtr channel = this; channel.forget(aBaseChannel); return NS_OK; } NS_IMETHODIMP HttpChannelChild::GetPartID(uint32_t* aPartID) { if (!mMultiPartID) { MOZ_ASSERT(false, "Not a multipart channel"); return NS_ERROR_NOT_AVAILABLE; } *aPartID = *mMultiPartID; return NS_OK; } NS_IMETHODIMP HttpChannelChild::GetIsFirstPart(bool* aIsFirstPart) { if (!mMultiPartID) { return NS_ERROR_NOT_AVAILABLE; } *aIsFirstPart = mIsFirstPartOfMultiPart; return NS_OK; } NS_IMETHODIMP HttpChannelChild::GetIsLastPart(bool* aIsLastPart) { if (!mMultiPartID) { return NS_ERROR_NOT_AVAILABLE; } *aIsLastPart = mIsLastPartOfMultiPart; return NS_OK; } //----------------------------------------------------------------------------- // HttpChannelChild::nsIThreadRetargetableRequest //----------------------------------------------------------------------------- NS_IMETHODIMP HttpChannelChild::RetargetDeliveryTo(nsISerialEventTarget* aNewTarget) { LOG(("HttpChannelChild::RetargetDeliveryTo [this=%p, aNewTarget=%p]", this, aNewTarget)); MOZ_ASSERT(NS_IsMainThread(), "Should be called on main thread only"); MOZ_ASSERT(aNewTarget); NS_ENSURE_ARG(aNewTarget); if (aNewTarget->IsOnCurrentThread()) { NS_WARNING("Retargeting delivery to same thread"); mOMTResult = LABELS_HTTP_CHILD_OMT_STATS_2::successMainThread; return NS_OK; } if (mMultiPartID) { // TODO: Maybe add a new label for this? Maybe it doesn't // matter though, since we also blocked QI, so we shouldn't // ever get here. mOMTResult = LABELS_HTTP_CHILD_OMT_STATS_2::failListener; return NS_ERROR_NO_INTERFACE; } // Ensure that |mListener| and any subsequent listeners can be retargeted // to another thread. nsresult rv = NS_OK; nsCOMPtr retargetableListener = do_QueryInterface(mListener, &rv); if (!retargetableListener || NS_FAILED(rv)) { NS_WARNING("Listener is not retargetable"); mOMTResult = LABELS_HTTP_CHILD_OMT_STATS_2::failListener; return NS_ERROR_NO_INTERFACE; } rv = retargetableListener->CheckListenerChain(); if (NS_FAILED(rv)) { NS_WARNING("Subsequent listeners are not retargetable"); mOMTResult = LABELS_HTTP_CHILD_OMT_STATS_2::failListenerChain; return rv; } { MutexAutoLock lock(mEventTargetMutex); MOZ_ASSERT(!mODATarget); RetargetDeliveryToImpl(aNewTarget, lock); } mOMTResult = LABELS_HTTP_CHILD_OMT_STATS_2::success; return NS_OK; } void HttpChannelChild::RetargetDeliveryToImpl(nsISerialEventTarget* aNewTarget, MutexAutoLock& aLockRef) { aLockRef.AssertOwns(mEventTargetMutex); mODATarget = aNewTarget; } NS_IMETHODIMP HttpChannelChild::GetDeliveryTarget(nsISerialEventTarget** aEventTarget) { MutexAutoLock lock(mEventTargetMutex); nsCOMPtr target = mODATarget; if (!mODATarget) { target = GetCurrentSerialEventTarget(); } target.forget(aEventTarget); return NS_OK; } void HttpChannelChild::TrySendDeletingChannel() { AUTO_PROFILER_LABEL("HttpChannelChild::TrySendDeletingChannel", NETWORK); MOZ_ASSERT(NS_IsMainThread()); if (!mDeletingChannelSent.compareExchange(false, true)) { // SendDeletingChannel is already sent. return; } if (NS_WARN_IF(!CanSend())) { // IPC actor is destroyed already, do not send more messages. return; } Unused << PHttpChannelChild::SendDeletingChannel(); } nsresult HttpChannelChild::AsyncCallImpl( void (HttpChannelChild::*funcPtr)(), nsRunnableMethod** retval) { nsresult rv; RefPtr> event = NewRunnableMethod("net::HttpChannelChild::AsyncCall", this, funcPtr); nsCOMPtr neckoTarget = GetNeckoTarget(); MOZ_ASSERT(neckoTarget); rv = neckoTarget->Dispatch(event, NS_DISPATCH_NORMAL); if (NS_SUCCEEDED(rv) && retval) { *retval = event; } return rv; } nsresult HttpChannelChild::SetReferrerHeader(const nsACString& aReferrer, bool aRespectBeforeConnect) { // Normally this would be ENSURE_CALLED_BEFORE_CONNECT, but since the // "connect" is done in the main process, and LoadRequestObserversCalled() is // never set in the ChannelChild, before connect basically means before // asyncOpen. if (aRespectBeforeConnect) { ENSURE_CALLED_BEFORE_ASYNC_OPEN(); } // remove old referrer if any mClientSetRequestHeaders.RemoveElementsBy( [](const auto& header) { return "Referer"_ns.Equals(header.mHeader); }); return HttpBaseChannel::SetReferrerHeader(aReferrer, aRespectBeforeConnect); } void HttpChannelChild::CancelOnMainThread(nsresult aRv, const nsACString& aReason) { LOG(("HttpChannelChild::CancelOnMainThread [this=%p]", this)); if (NS_IsMainThread()) { CancelWithReason(aRv, aReason); return; } mEventQ->Suspend(); // Cancel is expected to preempt any other channel events, thus we put this // event in the front of mEventQ to make sure nsIStreamListener not receiving // any ODA/OnStopRequest callbacks. nsCString reason(aReason); mEventQ->PrependEvent(MakeUnique( this, [self = UnsafePtr(this), aRv, reason]() { self->CancelWithReason(aRv, reason); })); mEventQ->Resume(); } mozilla::ipc::IPCResult HttpChannelChild::RecvSetPriority( const int16_t& aPriority) { mPriority = aPriority; return IPC_OK(); } // We don't have a copyable Endpoint and NeckoTargetChannelFunctionEvent takes // std::function. It's not possible to avoid the copy from the type of // lambda to std::function, so does the capture list. Hence, we're forced to // use the old-fashioned channel event inheritance. class AttachStreamFilterEvent : public ChannelEvent { public: AttachStreamFilterEvent(HttpChannelChild* aChild, already_AddRefed aTarget, Endpoint&& aEndpoint) : mChild(aChild), mTarget(aTarget), mEndpoint(std::move(aEndpoint)) {} already_AddRefed GetEventTarget() override { nsCOMPtr target = mTarget; return target.forget(); } void Run() override { extensions::StreamFilterParent::Attach(mChild, std::move(mEndpoint)); } private: HttpChannelChild* mChild; nsCOMPtr mTarget; Endpoint mEndpoint; }; void HttpChannelChild::RegisterStreamFilter( RefPtr& aStreamFilter) { MOZ_ASSERT(NS_IsMainThread()); mStreamFilters.AppendElement(aStreamFilter); } void HttpChannelChild::ProcessAttachStreamFilter( Endpoint&& aEndpoint) { LOG(("HttpChannelChild::ProcessAttachStreamFilter [this=%p]\n", this)); MOZ_ASSERT(OnSocketThread()); mEventQ->RunOrEnqueue(new AttachStreamFilterEvent(this, GetNeckoTarget(), std::move(aEndpoint))); } void HttpChannelChild::OnDetachStreamFilters() { LOG(("HttpChannelChild::OnDetachStreamFilters [this=%p]\n", this)); MOZ_ASSERT(NS_IsMainThread()); for (auto& StreamFilter : mStreamFilters) { StreamFilter->Disconnect("ServiceWorker fallback redirection"_ns); } mStreamFilters.Clear(); } void HttpChannelChild::ProcessDetachStreamFilters() { LOG(("HttpChannelChild::ProcessDetachStreamFilter [this=%p]\n", this)); MOZ_ASSERT(OnSocketThread()); mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent( this, [self = UnsafePtr(this)]() { self->OnDetachStreamFilters(); })); } void HttpChannelChild::ActorDestroy(ActorDestroyReason aWhy) { MOZ_ASSERT(NS_IsMainThread()); #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED mActorDestroyReason.emplace(aWhy); #endif // OnStartRequest might be dropped if IPDL is destroyed abnormally // and BackgroundChild might have pending IPC messages. // Clean up BackgroundChild at this time to prevent memleak. if (aWhy != Deletion) { // Make sure all the messages are processed. AutoEventEnqueuer ensureSerialDispatch(mEventQ); mStatus = NS_ERROR_DOCSHELL_DYING; HandleAsyncAbort(); // Cleanup the background channel before we resume the eventQ so we don't // get any other events. CleanupBackgroundChannel(); mIPCActorDeleted = true; mCanceled = true; } } mozilla::ipc::IPCResult HttpChannelChild::RecvLogBlockedCORSRequest( const nsAString& aMessage, const nsACString& aCategory, const bool& aIsWarning) { Unused << LogBlockedCORSRequest(aMessage, aCategory, aIsWarning); return IPC_OK(); } NS_IMETHODIMP HttpChannelChild::LogBlockedCORSRequest(const nsAString& aMessage, const nsACString& aCategory, bool aIsWarning) { uint64_t innerWindowID = mLoadInfo->GetInnerWindowID(); bool privateBrowsing = !!mLoadInfo->GetOriginAttributes().mPrivateBrowsingId; bool fromChromeContext = mLoadInfo->TriggeringPrincipal()->IsSystemPrincipal(); nsCORSListenerProxy::LogBlockedCORSRequest(innerWindowID, privateBrowsing, fromChromeContext, aMessage, aCategory, aIsWarning); return NS_OK; } mozilla::ipc::IPCResult HttpChannelChild::RecvLogMimeTypeMismatch( const nsACString& aMessageName, const bool& aWarning, const nsAString& aURL, const nsAString& aContentType) { Unused << LogMimeTypeMismatch(aMessageName, aWarning, aURL, aContentType); return IPC_OK(); } NS_IMETHODIMP HttpChannelChild::LogMimeTypeMismatch(const nsACString& aMessageName, bool aWarning, const nsAString& aURL, const nsAString& aContentType) { RefPtr doc; mLoadInfo->GetLoadingDocument(getter_AddRefs(doc)); AutoTArray params; params.AppendElement(aURL); params.AppendElement(aContentType); nsContentUtils::ReportToConsole( aWarning ? nsIScriptError::warningFlag : nsIScriptError::errorFlag, "MIMEMISMATCH"_ns, doc, nsContentUtils::eSECURITY_PROPERTIES, nsCString(aMessageName).get(), params); return NS_OK; } nsresult HttpChannelChild::MaybeLogCOEPError(nsresult aStatus) { if (aStatus == NS_ERROR_DOM_CORP_FAILED) { RefPtr doc; mLoadInfo->GetLoadingDocument(getter_AddRefs(doc)); nsAutoCString url; mURI->GetSpec(url); AutoTArray params; params.AppendElement(NS_ConvertUTF8toUTF16(url)); // The MDN URL intentionally ends with a # so the webconsole linkification // doesn't ignore the final ) of the URL params.AppendElement( u"https://developer.mozilla.org/docs/Web/HTTP/Cross-Origin_Resource_Policy_(CORP)#"_ns); nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, "COEP"_ns, doc, nsContentUtils::eNECKO_PROPERTIES, "CORPBlocked", params); } return NS_OK; } nsresult HttpChannelChild::CrossProcessRedirectFinished(nsresult aStatus) { if (!CanSend()) { return NS_BINDING_FAILED; } if (!mCanceled && NS_SUCCEEDED(mStatus)) { mStatus = aStatus; } return mStatus; } void HttpChannelChild::DoDiagnosticAssertWhenOnStopNotCalledOnDestroy() { #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED mDoDiagnosticAssertWhenOnStopNotCalledOnDestroy = true; #endif } void HttpChannelChild::MaybeConnectToSocketProcess() { if (!nsIOService::UseSocketProcess()) { return; } if (!StaticPrefs::network_send_ODA_to_content_directly()) { return; } RefPtr bgChild; { MutexAutoLock lock(mBgChildMutex); bgChild = mBgChild; } SocketProcessBridgeChild::GetSocketProcessBridge()->Then( GetCurrentSerialEventTarget(), __func__, [bgChild, channelId = ChannelId()]( const RefPtr& aBridge) { Endpoint parentEndpoint; Endpoint childEndpoint; PBackgroundDataBridge::CreateEndpoints(&parentEndpoint, &childEndpoint); aBridge->SendInitBackgroundDataBridge(std::move(parentEndpoint), channelId); gSocketTransportService->Dispatch( NS_NewRunnableFunction( "HttpBackgroundChannelChild::CreateDataBridge", [bgChild, endpoint = std::move(childEndpoint)]() mutable { bgChild->CreateDataBridge(std::move(endpoint)); }), NS_DISPATCH_NORMAL); }, []() { NS_WARNING("Failed to create SocketProcessBridgeChild"); }); } NS_IMETHODIMP HttpChannelChild::SetEarlyHintObserver(nsIEarlyHintObserver* aObserver) { return NS_OK; } NS_IMETHODIMP HttpChannelChild::SetWebTransportSessionEventListener( WebTransportSessionEventListener* aListener) { return NS_OK; } } // namespace mozilla::net