Source code
Revision control
Copy as Markdown
Other Tools
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
// HttpLog.h should generally be included first
#include "ErrorList.h"
#include "HttpLog.h"
#include "mozilla/ConsoleReportCollector.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/ipc/IPCStreamUtils.h"
#include "mozilla/net/EarlyHintRegistrar.h"
#include "mozilla/net/HttpChannelParent.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/ContentProcessManager.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/ServiceWorkerUtils.h"
#include "mozilla/dom/BrowserParent.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/net/NeckoParent.h"
#include "mozilla/net/CookieServiceParent.h"
#include "mozilla/Components.h"
#include "mozilla/InputStreamLengthHelper.h"
#include "mozilla/IntegerPrintfMacros.h"
#include "mozilla/Preferences.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/ProfilerMarkers.h"
#include "mozilla/StoragePrincipalHelper.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
#include "HttpBackgroundChannelParent.h"
#include "ParentChannelListener.h"
#include "nsDebug.h"
#include "nsICacheInfoChannel.h"
#include "nsHttpHandler.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "nsISupportsPriority.h"
#include "mozilla/net/BackgroundChannelRegistrar.h"
#include "nsSerializationHelper.h"
#include "nsISerializable.h"
#include "mozilla/ipc/InputStreamUtils.h"
#include "mozilla/ipc/URIUtils.h"
#include "SerializedLoadContext.h"
#include "nsIAuthPrompt.h"
#include "nsIAuthPrompt2.h"
#include "mozilla/ipc/BackgroundUtils.h"
#include "mozilla/LoadInfo.h"
#include "nsQueryObject.h"
#include "mozilla/BasePrincipal.h"
#include "nsCORSListenerProxy.h"
#include "nsIIPCSerializableInputStream.h"
#include "nsIPrompt.h"
#include "nsIPromptFactory.h"
#include "mozilla/net/ChannelEventQueue.h"
#include "mozilla/net/RedirectChannelRegistrar.h"
#include "nsIWindowWatcher.h"
#include "mozilla/dom/Document.h"
#include "nsISecureBrowserUI.h"
#include "nsStreamUtils.h"
#include "nsStringStream.h"
#include "nsThreadUtils.h"
#include "nsQueryObject.h"
#include "nsIMultiPartChannel.h"
#include "nsIViewSourceChannel.h"
using namespace mozilla;
namespace geckoprofiler::markers {
struct ChannelMarker {
static constexpr Span<const char> MarkerTypeName() {
return MakeStringSpan("ChannelMarker");
}
static void StreamJSONMarkerData(
mozilla::baseprofiler::SpliceableJSONWriter& aWriter,
const mozilla::ProfilerString8View& aURL, uint64_t aChannelId) {
if (aURL.Length() != 0) {
aWriter.StringProperty("url", aURL);
}
aWriter.IntProperty("channelId", static_cast<int64_t>(aChannelId));
}
static MarkerSchema MarkerTypeDisplay() {
using MS = MarkerSchema;
MS schema(MS::Location::MarkerChart, MS::Location::MarkerTable);
schema.SetTableLabel("{marker.name} - {marker.data.url}");
schema.AddKeyFormatSearchable("url", MS::Format::Url,
MS::Searchable::Searchable);
schema.AddStaticLabelValue(
"Description",
"Timestamp capturing various phases of a network channel's lifespan.");
return schema;
}
};
} // namespace geckoprofiler::markers
using mozilla::BasePrincipal;
using namespace mozilla::dom;
using namespace mozilla::ipc;
namespace mozilla::net {
HttpChannelParent::HttpChannelParent(dom::BrowserParent* iframeEmbedding,
nsILoadContext* aLoadContext,
PBOverrideStatus aOverrideStatus)
: mLoadContext(aLoadContext),
mIPCClosed(false),
mPBOverride(aOverrideStatus),
mStatus(NS_OK),
mIgnoreProgress(false),
mHasSuspendedByBackPressure(false),
mCacheNeedFlowControlInitialized(false),
mNeedFlowControl(true),
mSuspendedForFlowControl(false),
mAfterOnStartRequestBegun(false),
mDataSentToChildProcess(false) {
LOG(("Creating HttpChannelParent [this=%p]\n", this));
// Ensure gHttpHandler is initialized: we need the atom table up and running.
nsCOMPtr<nsIHttpProtocolHandler> dummyInitializer =
do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http");
MOZ_ASSERT(gHttpHandler);
mHttpHandler = gHttpHandler;
mBrowserParent = iframeEmbedding;
mSendWindowSize = gHttpHandler->SendWindowSize();
mEventQ =
new ChannelEventQueue(static_cast<nsIParentRedirectingChannel*>(this));
}
HttpChannelParent::~HttpChannelParent() {
LOG(("Destroying HttpChannelParent [this=%p]\n", this));
CleanupBackgroundChannel();
MOZ_ASSERT(!mRedirectCallback);
if (NS_WARN_IF(mRedirectCallback)) {
mRedirectCallback->OnRedirectVerifyCallback(NS_ERROR_UNEXPECTED);
mRedirectCallback = nullptr;
}
mEventQ->NotifyReleasingOwner();
}
void HttpChannelParent::ActorDestroy(ActorDestroyReason why) {
// We may still have refcount>0 if nsHttpChannel hasn't called OnStopRequest
// yet, but child process has crashed. We must not try to send any more msgs
// to child, or IPDL will kill chrome process, too.
mIPCClosed = true;
CleanupBackgroundChannel();
}
bool HttpChannelParent::Init(const HttpChannelCreationArgs& aArgs) {
LOG(("HttpChannelParent::Init [this=%p]\n", this));
AUTO_PROFILER_LABEL("HttpChannelParent::Init", NETWORK);
switch (aArgs.type()) {
case HttpChannelCreationArgs::THttpChannelOpenArgs: {
const HttpChannelOpenArgs& a = aArgs.get_HttpChannelOpenArgs();
return DoAsyncOpen(
a.uri(), a.original(), a.doc(), a.referrerInfo(), a.apiRedirectTo(),
a.topWindowURI(), a.loadFlags(), a.requestHeaders(),
a.requestMethod(), a.uploadStream(), a.uploadStreamHasHeaders(),
a.priority(), a.classOfService(), a.redirectionLimit(), a.allowSTS(),
a.thirdPartyFlags(), a.resumeAt(), a.startPos(), a.entityID(),
a.allowSpdy(), a.allowHttp3(), a.allowAltSvc(), a.beConservative(),
a.bypassProxy(), a.tlsFlags(), a.loadInfo(), a.cacheKey(),
a.requestContextID(), a.preflightArgs(), a.initialRwin(),
a.blockAuthPrompt(), a.allowStaleCacheContent(),
a.preferCacheLoadOverBypass(), a.contentTypeHint(), a.requestMode(),
a.redirectMode(), a.channelId(), a.integrityMetadata(),
a.contentWindowId(), a.preferredAlternativeTypes(), a.browserId(),
a.launchServiceWorkerStart(), a.launchServiceWorkerEnd(),
a.dispatchFetchEventStart(), a.dispatchFetchEventEnd(),
a.handleFetchEventStart(), a.handleFetchEventEnd(),
a.forceMainDocumentChannel(), a.navigationStartTimeStamp(),
a.earlyHintPreloaderId(), a.classicScriptHintCharset(),
a.documentCharacterSet(), a.isUserAgentHeaderModified());
}
case HttpChannelCreationArgs::THttpChannelConnectArgs: {
const HttpChannelConnectArgs& cArgs = aArgs.get_HttpChannelConnectArgs();
return ConnectChannel(cArgs.registrarId());
}
default:
MOZ_ASSERT_UNREACHABLE("unknown open type");
return false;
}
}
void HttpChannelParent::TryInvokeAsyncOpen(nsresult aRv) {
LOG(("HttpChannelParent::TryInvokeAsyncOpen [this=%p barrier=%u rv=%" PRIx32
"]\n",
this, mAsyncOpenBarrier, static_cast<uint32_t>(aRv)));
MOZ_ASSERT(NS_IsMainThread());
AUTO_PROFILER_LABEL("HttpChannelParent::TryInvokeAsyncOpen", NETWORK);
// TryInvokeAsyncOpen is called more than we expected.
// Assert in nightly build but ignore it in release channel.
MOZ_DIAGNOSTIC_ASSERT(mAsyncOpenBarrier > 0);
if (NS_WARN_IF(!mAsyncOpenBarrier)) {
return;
}
if (--mAsyncOpenBarrier > 0 && NS_SUCCEEDED(aRv)) {
// Need to wait for more events.
return;
}
InvokeAsyncOpen(aRv);
}
void HttpChannelParent::OnBackgroundParentReady(
HttpBackgroundChannelParent* aBgParent) {
LOG(("HttpChannelParent::OnBackgroundParentReady [this=%p bgParent=%p]\n",
this, aBgParent));
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!mBgParent);
mBgParent = aBgParent;
mPromise.ResolveIfExists(true, __func__);
}
void HttpChannelParent::OnBackgroundParentDestroyed() {
LOG(("HttpChannelParent::OnBackgroundParentDestroyed [this=%p]\n", this));
MOZ_ASSERT(NS_IsMainThread());
if (!mPromise.IsEmpty()) {
MOZ_ASSERT(!mBgParent);
mPromise.Reject(NS_ERROR_FAILURE, __func__);
return;
}
if (!mBgParent) {
return;
}
// Background channel is closed unexpectly, abort PHttpChannel operation.
mBgParent = nullptr;
Delete();
}
void HttpChannelParent::CleanupBackgroundChannel() {
LOG(("HttpChannelParent::CleanupBackgroundChannel [this=%p bgParent=%p]\n",
this, mBgParent.get()));
MOZ_ASSERT(NS_IsMainThread());
if (mBgParent) {
RefPtr<HttpBackgroundChannelParent> bgParent = std::move(mBgParent);
bgParent->OnChannelClosed();
return;
}
// The nsHttpChannel may have a reference to this parent, release it
// to avoid circular references.
RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
if (httpChannelImpl) {
httpChannelImpl->SetWarningReporter(nullptr);
}
if (!mPromise.IsEmpty()) {
mRequest.DisconnectIfExists();
mPromise.Reject(NS_ERROR_FAILURE, __func__);
if (!mChannel) {
return;
}
// This HttpChannelParent might still have a reference from
// BackgroundChannelRegistrar.
nsCOMPtr<nsIBackgroundChannelRegistrar> registrar =
BackgroundChannelRegistrar::GetOrCreate();
MOZ_ASSERT(registrar);
registrar->DeleteChannel(mChannel->ChannelId());
// If mAsyncOpenBarrier is greater than zero, it means AsyncOpen procedure
// is still on going. we need to abort AsyncOpen with failure to destroy
// PHttpChannel actor.
if (mAsyncOpenBarrier) {
TryInvokeAsyncOpen(NS_ERROR_FAILURE);
}
}
}
base::ProcessId HttpChannelParent::OtherPid() const {
if (mIPCClosed) {
return 0;
}
return PHttpChannelParent::OtherPid();
}
//-----------------------------------------------------------------------------
// HttpChannelParent::nsISupports
//-----------------------------------------------------------------------------
NS_IMPL_ADDREF(HttpChannelParent)
NS_IMPL_RELEASE(HttpChannelParent)
NS_INTERFACE_MAP_BEGIN(HttpChannelParent)
NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
NS_INTERFACE_MAP_ENTRY(nsIParentChannel)
NS_INTERFACE_MAP_ENTRY(nsIParentRedirectingChannel)
NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectReadyCallback)
NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
NS_INTERFACE_MAP_ENTRY(nsIRedirectResultListener)
NS_INTERFACE_MAP_ENTRY(nsIMultiPartChannelListener)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIParentRedirectingChannel)
NS_INTERFACE_MAP_ENTRY_CONCRETE(HttpChannelParent)
NS_INTERFACE_MAP_END
//-----------------------------------------------------------------------------
// HttpChannelParent::nsIInterfaceRequestor
//-----------------------------------------------------------------------------
NS_IMETHODIMP
HttpChannelParent::GetInterface(const nsIID& aIID, void** result) {
// A system XHR can be created without reference to a window, hence mTabParent
// may be null. In that case we want to let the window watcher pick a prompt
// directly.
if (!mBrowserParent && (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
aIID.Equals(NS_GET_IID(nsIAuthPrompt2)))) {
nsresult rv;
nsCOMPtr<nsIWindowWatcher> wwatch;
wwatch = mozilla::components::WindowWatcher::Service(&rv);
NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE);
bool hasWindowCreator = false;
Unused << wwatch->HasWindowCreator(&hasWindowCreator);
if (!hasWindowCreator) {
return NS_ERROR_NO_INTERFACE;
}
nsCOMPtr<nsIPromptFactory> factory = do_QueryInterface(wwatch);
if (!factory) {
return NS_ERROR_NO_INTERFACE;
}
rv = factory->GetPrompt(nullptr, aIID, reinterpret_cast<void**>(result));
if (NS_FAILED(rv)) {
return NS_ERROR_NO_INTERFACE;
}
return NS_OK;
}
// Only support nsILoadContext if child channel's callbacks did too
if (aIID.Equals(NS_GET_IID(nsILoadContext)) && mLoadContext) {
nsCOMPtr<nsILoadContext> copy = mLoadContext;
copy.forget(result);
return NS_OK;
}
return QueryInterface(aIID, result);
}
//-----------------------------------------------------------------------------
// HttpChannelParent::PHttpChannelParent
//-----------------------------------------------------------------------------
void HttpChannelParent::AsyncOpenFailed(nsresult aRv) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(NS_FAILED(aRv));
// Break the reference cycle among HttpChannelParent,
// ParentChannelListener, and nsHttpChannel to avoid memory leakage.
mChannel = nullptr;
mParentListener = nullptr;
if (!mIPCClosed) {
Unused << SendFailedAsyncOpen(aRv);
}
}
void HttpChannelParent::InvokeAsyncOpen(nsresult rv) {
LOG(("HttpChannelParent::InvokeAsyncOpen [this=%p rv=%" PRIx32 "]\n", this,
static_cast<uint32_t>(rv)));
MOZ_ASSERT(NS_IsMainThread());
if (NS_FAILED(rv)) {
AsyncOpenFailed(rv);
return;
}
rv = mChannel->AsyncOpen(mParentListener);
if (NS_FAILED(rv)) {
AsyncOpenFailed(rv);
}
}
void HttpChannelParent::InvokeEarlyHintPreloader(
nsresult rv, uint64_t aEarlyHintPreloaderId) {
LOG(("HttpChannelParent::InvokeEarlyHintPreloader [this=%p rv=%" PRIx32 "]\n",
this, static_cast<uint32_t>(rv)));
MOZ_ASSERT(NS_IsMainThread());
ContentParentId cpId =
static_cast<ContentParent*>(Manager()->Manager())->ChildID();
RefPtr<EarlyHintRegistrar> ehr = EarlyHintRegistrar::GetOrCreate();
if (NS_SUCCEEDED(rv)) {
rv = ehr->LinkParentChannel(cpId, aEarlyHintPreloaderId, this)
? NS_OK
: NS_ERROR_FAILURE;
}
if (NS_FAILED(rv)) {
ehr->DeleteEntry(cpId, aEarlyHintPreloaderId);
AsyncOpenFailed(NS_ERROR_FAILURE);
}
}
bool HttpChannelParent::DoAsyncOpen(
nsIURI* aURI, nsIURI* aOriginalURI, nsIURI* aDocURI,
nsIReferrerInfo* aReferrerInfo, nsIURI* aAPIRedirectToURI,
nsIURI* aTopWindowURI, const uint32_t& aLoadFlags,
const RequestHeaderTuples& requestHeaders, const nsCString& requestMethod,
const Maybe<IPCStream>& uploadStream, const bool& uploadStreamHasHeaders,
const int16_t& priority, const ClassOfService& classOfService,
const uint8_t& redirectionLimit, const bool& allowSTS,
const uint32_t& thirdPartyFlags, const bool& doResumeAt,
const uint64_t& startPos, const nsCString& entityID, const bool& allowSpdy,
const bool& allowHttp3, const bool& allowAltSvc, const bool& beConservative,
const bool& bypassProxy, const uint32_t& tlsFlags,
const LoadInfoArgs& aLoadInfoArgs, const uint32_t& aCacheKey,
const uint64_t& aRequestContextID,
const Maybe<CorsPreflightArgs>& aCorsPreflightArgs,
const uint32_t& aInitialRwin, const bool& aBlockAuthPrompt,
const bool& aAllowStaleCacheContent, const bool& aPreferCacheLoadOverBypass,
const nsCString& aContentTypeHint, const dom::RequestMode& aRequestMode,
const uint32_t& aRedirectMode, const uint64_t& aChannelId,
const nsString& aIntegrityMetadata, const uint64_t& aContentWindowId,
const nsTArray<PreferredAlternativeDataTypeParams>&
aPreferredAlternativeTypes,
const uint64_t& aBrowserId, const TimeStamp& aLaunchServiceWorkerStart,
const TimeStamp& aLaunchServiceWorkerEnd,
const TimeStamp& aDispatchFetchEventStart,
const TimeStamp& aDispatchFetchEventEnd,
const TimeStamp& aHandleFetchEventStart,
const TimeStamp& aHandleFetchEventEnd,
const bool& aForceMainDocumentChannel,
const TimeStamp& aNavigationStartTimeStamp,
const uint64_t& aEarlyHintPreloaderId,
const nsAString& aClassicScriptHintCharset,
const nsAString& aDocumentCharacterSet,
const bool& aIsUserAgentHeaderModified) {
MOZ_ASSERT(aURI, "aURI should not be NULL");
if (aEarlyHintPreloaderId) {
// Wait for HttpBackgrounChannel to continue the async open procedure.
mEarlyHintPreloaderId = aEarlyHintPreloaderId;
RefPtr<HttpChannelParent> self = this;
WaitForBgParent(aChannelId)
->Then(
GetMainThreadSerialEventTarget(), __func__,
[self, aEarlyHintPreloaderId]() {
self->mRequest.Complete();
self->InvokeEarlyHintPreloader(NS_OK, aEarlyHintPreloaderId);
},
[self, aEarlyHintPreloaderId](nsresult aStatus) {
self->mRequest.Complete();
self->InvokeEarlyHintPreloader(aStatus, aEarlyHintPreloaderId);
})
->Track(mRequest);
return true;
}
if (!aURI) {
// this check is neccessary to prevent null deref
// in opt builds
return false;
}
LOG(("HttpChannelParent RecvAsyncOpen [this=%p uri=%s, gid=%" PRIu64
" browserid=%" PRIx64 "]\n",
this, aURI->GetSpecOrDefault().get(), aChannelId, aBrowserId));
PROFILER_MARKER("Receive AsyncOpen in Parent", NETWORK,
MarkerThreadId::MainThread(), ChannelMarker,
aURI->GetSpecOrDefault(), aChannelId);
nsresult rv;
nsCOMPtr<nsIIOService> ios(do_GetIOService(&rv));
if (NS_FAILED(rv)) {
return SendFailedAsyncOpen(rv);
}
nsAutoCString remoteType;
rv = GetRemoteType(remoteType);
if (NS_FAILED(rv)) {
return SendFailedAsyncOpen(rv);
}
nsCOMPtr<nsILoadInfo> loadInfo;
rv = mozilla::ipc::LoadInfoArgsToLoadInfo(aLoadInfoArgs, remoteType,
getter_AddRefs(loadInfo));
if (NS_FAILED(rv)) {
return SendFailedAsyncOpen(rv);
}
nsCOMPtr<nsIChannel> channel;
rv = NS_NewChannelInternal(getter_AddRefs(channel), aURI, loadInfo, nullptr,
nullptr, nullptr, aLoadFlags, ios);
if (NS_FAILED(rv)) {
return SendFailedAsyncOpen(rv);
}
RefPtr<HttpBaseChannel> httpChannel = do_QueryObject(channel, &rv);
if (NS_FAILED(rv)) {
return SendFailedAsyncOpen(rv);
}
// Set attributes needed to create a FetchEvent from this channel.
httpChannel->SetRequestMode(aRequestMode);
httpChannel->SetRedirectMode(aRedirectMode);
// Set the channelId allocated in child to the parent instance
httpChannel->SetChannelId(aChannelId);
httpChannel->SetTopLevelContentWindowId(aContentWindowId);
httpChannel->SetBrowserId(aBrowserId);
httpChannel->SetIntegrityMetadata(aIntegrityMetadata);
RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(httpChannel);
if (httpChannelImpl) {
httpChannelImpl->SetWarningReporter(this);
}
httpChannel->SetTimingEnabled(true);
if (mPBOverride != kPBOverride_Unset) {
httpChannel->SetPrivate(mPBOverride == kPBOverride_Private);
}
if (doResumeAt) httpChannel->ResumeAt(startPos, entityID);
if (aOriginalURI) {
httpChannel->SetOriginalURI(aOriginalURI);
}
if (aDocURI) {
httpChannel->SetDocumentURI(aDocURI);
}
if (aReferrerInfo) {
// Referrer header is computed in child no need to recompute here
rv =
httpChannel->SetReferrerInfoInternal(aReferrerInfo, false, false, true);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
httpChannel->SetClassicScriptHintCharset(aClassicScriptHintCharset);
httpChannel->SetDocumentCharacterSet(aDocumentCharacterSet);
if (aAPIRedirectToURI) {
httpChannel->RedirectTo(aAPIRedirectToURI);
}
if (aTopWindowURI) {
httpChannel->SetTopWindowURI(aTopWindowURI);
}
if (aLoadFlags != nsIRequest::LOAD_NORMAL) {
httpChannel->SetLoadFlags(aLoadFlags);
}
if (aForceMainDocumentChannel) {
httpChannel->SetIsMainDocumentChannel(true);
}
for (uint32_t i = 0; i < requestHeaders.Length(); i++) {
if (requestHeaders[i].mEmpty) {
httpChannel->SetEmptyRequestHeader(requestHeaders[i].mHeader);
} else {
httpChannel->SetRequestHeader(requestHeaders[i].mHeader,
requestHeaders[i].mValue,
requestHeaders[i].mMerge);
}
}
httpChannel->SetIsUserAgentHeaderModified(aIsUserAgentHeaderModified);
RefPtr<ParentChannelListener> parentListener = new ParentChannelListener(
this, mBrowserParent ? mBrowserParent->GetBrowsingContext() : nullptr);
httpChannel->SetRequestMethod(nsDependentCString(requestMethod.get()));
if (aCorsPreflightArgs.isSome()) {
const CorsPreflightArgs& args = aCorsPreflightArgs.ref();
httpChannel->SetCorsPreflightParameters(args.unsafeHeaders(), false, false);
}
nsCOMPtr<nsIInputStream> stream = DeserializeIPCStream(uploadStream);
if (stream) {
rv = httpChannel->InternalSetUploadStream(stream);
if (NS_FAILED(rv)) {
return SendFailedAsyncOpen(rv);
}
httpChannel->SetUploadStreamHasHeaders(uploadStreamHasHeaders);
}
nsCOMPtr<nsICacheInfoChannel> cacheChannel =
do_QueryInterface(static_cast<nsIChannel*>(httpChannel.get()));
if (cacheChannel) {
cacheChannel->SetCacheKey(aCacheKey);
for (const auto& data : aPreferredAlternativeTypes) {
cacheChannel->PreferAlternativeDataType(data.type(), data.contentType(),
data.deliverAltData());
}
cacheChannel->SetAllowStaleCacheContent(aAllowStaleCacheContent);
cacheChannel->SetPreferCacheLoadOverBypass(aPreferCacheLoadOverBypass);
// This is to mark that the results are going to the content process.
if (httpChannelImpl) {
httpChannelImpl->SetAltDataForChild(true);
}
}
httpChannel->SetContentType(aContentTypeHint);
if (priority != nsISupportsPriority::PRIORITY_NORMAL) {
httpChannel->SetPriority(priority);
}
if (classOfService.Flags() || classOfService.Incremental()) {
httpChannel->SetClassOfService(classOfService);
}
httpChannel->SetRedirectionLimit(redirectionLimit);
httpChannel->SetAllowSTS(allowSTS);
httpChannel->SetThirdPartyFlags(thirdPartyFlags);
httpChannel->SetAllowSpdy(allowSpdy);
httpChannel->SetAllowHttp3(allowHttp3);
httpChannel->SetAllowAltSvc(allowAltSvc);
httpChannel->SetBeConservative(beConservative);
httpChannel->SetTlsFlags(tlsFlags);
httpChannel->SetInitialRwin(aInitialRwin);
httpChannel->SetBlockAuthPrompt(aBlockAuthPrompt);
httpChannel->SetLaunchServiceWorkerStart(aLaunchServiceWorkerStart);
httpChannel->SetLaunchServiceWorkerEnd(aLaunchServiceWorkerEnd);
httpChannel->SetDispatchFetchEventStart(aDispatchFetchEventStart);
httpChannel->SetDispatchFetchEventEnd(aDispatchFetchEventEnd);
httpChannel->SetHandleFetchEventStart(aHandleFetchEventStart);
httpChannel->SetHandleFetchEventEnd(aHandleFetchEventEnd);
httpChannel->SetNavigationStartTimeStamp(aNavigationStartTimeStamp);
httpChannel->SetRequestContextID(aRequestContextID);
// Store the strong reference of channel and parent listener object until
// all the initialization procedure is complete without failure, to remove
// cycle reference in fail case and to avoid memory leakage.
mChannel = std::move(httpChannel);
mParentListener = std::move(parentListener);
mChannel->SetNotificationCallbacks(mParentListener);
MOZ_ASSERT(!mBgParent);
MOZ_ASSERT(mPromise.IsEmpty());
// Wait for HttpBackgrounChannel to continue the async open procedure.
++mAsyncOpenBarrier;
RefPtr<HttpChannelParent> self = this;
WaitForBgParent(mChannel->ChannelId())
->Then(
GetMainThreadSerialEventTarget(), __func__,
[self]() {
self->mRequest.Complete();
self->TryInvokeAsyncOpen(NS_OK);
},
[self](nsresult aStatus) {
self->mRequest.Complete();
self->TryInvokeAsyncOpen(aStatus);
})
->Track(mRequest);
return true;
}
RefPtr<GenericNonExclusivePromise> HttpChannelParent::WaitForBgParent(
uint64_t aChannelId) {
LOG(("HttpChannelParent::WaitForBgParent [this=%p]\n", this));
MOZ_ASSERT(!mBgParent);
if (!mChannel && !mEarlyHintPreloaderId) {
return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_FAILURE,
__func__);
}
nsCOMPtr<nsIBackgroundChannelRegistrar> registrar =
BackgroundChannelRegistrar::GetOrCreate();
MOZ_ASSERT(registrar);
registrar->LinkHttpChannel(aChannelId, this);
if (mBgParent) {
return GenericNonExclusivePromise::CreateAndResolve(true, __func__);
}
return mPromise.Ensure(__func__);
}
bool HttpChannelParent::ConnectChannel(const uint32_t& registrarId) {
nsresult rv;
LOG(
("HttpChannelParent::ConnectChannel: Looking for a registered channel "
"[this=%p, id=%" PRIu32 "]\n",
this, registrarId));
nsCOMPtr<nsIChannel> channel;
rv = NS_LinkRedirectChannels(registrarId, this, getter_AddRefs(channel));
if (NS_FAILED(rv)) {
NS_WARNING("Could not find the http channel to connect its IPC parent");
// This makes the channel delete itself safely. It's the only thing
// we can do now, since this parent channel cannot be used and there is
// no other way to tell the child side there were something wrong.
Delete();
return true;
}
LOG((" found channel %p, rv=%08" PRIx32, channel.get(),
static_cast<uint32_t>(rv)));
mChannel = do_QueryObject(channel);
if (!mChannel) {
LOG((" but it's not HttpBaseChannel"));
Delete();
return true;
}
LOG((" and it is HttpBaseChannel %p", mChannel.get()));
RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
if (httpChannelImpl) {
httpChannelImpl->SetWarningReporter(this);
}
if (mPBOverride != kPBOverride_Unset) {
// redirected-to channel may not support PB
nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryObject(mChannel);
if (pbChannel) {
pbChannel->SetPrivate(mPBOverride == kPBOverride_Private);
}
}
MOZ_ASSERT(!mBgParent);
MOZ_ASSERT(mPromise.IsEmpty());
// Waiting for background channel
RefPtr<HttpChannelParent> self = this;
WaitForBgParent(mChannel->ChannelId())
->Then(
GetMainThreadSerialEventTarget(), __func__,
[self]() { self->mRequest.Complete(); },
[self](const nsresult& aResult) {
NS_ERROR("failed to establish the background channel");
self->mRequest.Complete();
})
->Track(mRequest);
return true;
}
mozilla::ipc::IPCResult HttpChannelParent::RecvSetPriority(
const int16_t& priority) {
LOG(("HttpChannelParent::RecvSetPriority [this=%p, priority=%d]\n", this,
priority));
AUTO_PROFILER_LABEL("HttpChannelParent::RecvSetPriority", NETWORK);
if (mChannel) {
mChannel->SetPriority(priority);
}
nsCOMPtr<nsISupportsPriority> priorityRedirectChannel =
do_QueryInterface(mRedirectChannel);
if (priorityRedirectChannel) priorityRedirectChannel->SetPriority(priority);
return IPC_OK();
}
mozilla::ipc::IPCResult HttpChannelParent::RecvSetClassOfService(
const ClassOfService& cos) {
if (mChannel) {
mChannel->SetClassOfService(cos);
}
return IPC_OK();
}
mozilla::ipc::IPCResult HttpChannelParent::RecvSuspend() {
LOG(("HttpChannelParent::RecvSuspend [this=%p]\n", this));
if (mChannel) {
mChannel->Suspend();
}
return IPC_OK();
}
mozilla::ipc::IPCResult HttpChannelParent::RecvResume() {
LOG(("HttpChannelParent::RecvResume [this=%p]\n", this));
if (mChannel) {
mChannel->Resume();
}
return IPC_OK();
}
mozilla::ipc::IPCResult HttpChannelParent::RecvCancel(
const nsresult& status, const uint32_t& requestBlockingReason,
const nsACString& reason, const mozilla::Maybe<nsCString>& logString) {
LOG(("HttpChannelParent::RecvCancel [this=%p, reason=%s]\n", this,
PromiseFlatCString(reason).get()));
// logging child cancel reason on the parent side
if (logString.isSome()) {
LOG(("HttpChannelParent::RecvCancel: %s", logString->get()));
}
// May receive cancel before channel has been constructed!
if (mChannel) {
mChannel->CancelWithReason(status, reason);
if (MOZ_UNLIKELY(requestBlockingReason !=
nsILoadInfo::BLOCKING_REASON_NONE)) {
nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
loadInfo->SetRequestBlockingReason(requestBlockingReason);
}
// Once we receive |Cancel|, child will stop sending RecvBytesRead. Force
// the channel resumed if needed.
if (mSuspendedForFlowControl) {
LOG((" resume the channel due to e10s backpressure relief by cancel"));
Unused << mChannel->Resume();
mSuspendedForFlowControl = false;
}
} else if (!mIPCClosed) {
// Make sure that the child correctly delivers all stream listener
// notifications.
Unused << SendFailedAsyncOpen(status);
}
// We won't need flow control anymore. Toggle the flag to avoid |Suspend|
// since OnDataAvailable could be off-main-thread.
mCacheNeedFlowControlInitialized = true;
mNeedFlowControl = false;
// If the channel is cancelled before the redirect is completed
// RecvRedirect2Verify will not be called, so we must clear the callback.
if (mRedirectCallback) {
mRedirectCallback->OnRedirectVerifyCallback(NS_ERROR_UNEXPECTED);
mRedirectCallback = nullptr;
}
return IPC_OK();
}
mozilla::ipc::IPCResult HttpChannelParent::RecvRedirect2Verify(
const nsresult& aResult, const RequestHeaderTuples& changedHeaders,
const uint32_t& aSourceRequestBlockingReason,
const Maybe<ChildLoadInfoForwarderArgs>& aTargetLoadInfoForwarder,
const uint32_t& loadFlags, nsIReferrerInfo* aReferrerInfo,
nsIURI* aAPIRedirectURI,
const Maybe<CorsPreflightArgs>& aCorsPreflightArgs) {
LOG(("HttpChannelParent::RecvRedirect2Verify [this=%p result=%" PRIx32 "]\n",
this, static_cast<uint32_t>(aResult)));
// Result from the child. If something fails here, we might overwrite a
// success with a further failure.
nsresult result = aResult;
// Local results.
nsresult rv;
if (NS_SUCCEEDED(result)) {
nsCOMPtr<nsIHttpChannel> newHttpChannel =
do_QueryInterface(mRedirectChannel);
if (newHttpChannel) {
if (aAPIRedirectURI) {
rv = newHttpChannel->RedirectTo(aAPIRedirectURI);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
for (uint32_t i = 0; i < changedHeaders.Length(); i++) {
if (changedHeaders[i].mEmpty) {
rv = newHttpChannel->SetEmptyRequestHeader(changedHeaders[i].mHeader);
} else {
rv = newHttpChannel->SetRequestHeader(changedHeaders[i].mHeader,
changedHeaders[i].mValue,
changedHeaders[i].mMerge);
}
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
// A successfully redirected channel must have the LOAD_REPLACE flag.
MOZ_ASSERT(loadFlags & nsIChannel::LOAD_REPLACE);
if (loadFlags & nsIChannel::LOAD_REPLACE) {
newHttpChannel->SetLoadFlags(loadFlags);
}
if (aCorsPreflightArgs.isSome()) {
nsCOMPtr<nsIHttpChannelInternal> newInternalChannel =
do_QueryInterface(newHttpChannel);
MOZ_RELEASE_ASSERT(newInternalChannel);
const CorsPreflightArgs& args = aCorsPreflightArgs.ref();
newInternalChannel->SetCorsPreflightParameters(args.unsafeHeaders(),
false, false);
}
if (aReferrerInfo) {
RefPtr<HttpBaseChannel> baseChannel = do_QueryObject(newHttpChannel);
MOZ_ASSERT(baseChannel);
if (baseChannel) {
// Referrer header is computed in child no need to recompute here
rv = baseChannel->SetReferrerInfoInternal(aReferrerInfo, false, false,
true);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
}
if (aTargetLoadInfoForwarder.isSome()) {
nsCOMPtr<nsILoadInfo> newLoadInfo = newHttpChannel->LoadInfo();
rv = MergeChildLoadInfoForwarder(aTargetLoadInfoForwarder.ref(),
newLoadInfo);
if (NS_FAILED(rv) && NS_SUCCEEDED(result)) {
result = rv;
}
}
}
}
// If the redirect is vetoed, reason is set on the source (current) channel's
// load info, so we must carry iver the change.
// The channel may have already been cleaned up, so there is nothing we can
// do.
if (MOZ_UNLIKELY(aSourceRequestBlockingReason !=
nsILoadInfo::BLOCKING_REASON_NONE) &&
mChannel) {
nsCOMPtr<nsILoadInfo> sourceLoadInfo = mChannel->LoadInfo();
sourceLoadInfo->SetRequestBlockingReason(aSourceRequestBlockingReason);
}
// Continue the verification procedure if child has veto the redirection.
if (NS_FAILED(result)) {
ContinueRedirect2Verify(result);
return IPC_OK();
}
// Wait for background channel ready on target channel
nsCOMPtr<nsIRedirectChannelRegistrar> redirectReg =
RedirectChannelRegistrar::GetOrCreate();
MOZ_ASSERT(redirectReg);
nsCOMPtr<nsIParentChannel> redirectParentChannel;
rv = redirectReg->GetParentChannel(mRedirectChannelId,
getter_AddRefs(redirectParentChannel));
if (!redirectParentChannel) {
ContinueRedirect2Verify(rv);
return IPC_OK();
}
nsCOMPtr<nsIParentRedirectingChannel> redirectedParent =
do_QueryInterface(redirectParentChannel);
if (!redirectedParent) {
// Continue verification procedure if redirecting to non-Http protocol
ContinueRedirect2Verify(result);
return IPC_OK();
}
// Ask redirected channel if verification can proceed.
// ContinueRedirect2Verify will be invoked when redirected channel is ready.
redirectedParent->ContinueVerification(this);
return IPC_OK();
}