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