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
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// 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/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 =
do_GetService(NS_WINDOWWATCHER_CONTRACTID, &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, {}, 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();
}
// from nsIParentRedirectingChannel
NS_IMETHODIMP
HttpChannelParent::ContinueVerification(
nsIAsyncVerifyRedirectReadyCallback* aCallback) {
LOG(("HttpChannelParent::ContinueVerification [this=%p callback=%p]\n", this,
aCallback));
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aCallback);
// Continue the verification procedure if background channel is ready.
if (mBgParent) {
aCallback->ReadyToVerify(NS_OK);
return NS_OK;
}
// ConnectChannel must be received before Redirect2Verify.
MOZ_ASSERT(!mPromise.IsEmpty());
// Otherwise, wait for the background channel.
nsCOMPtr<nsIAsyncVerifyRedirectReadyCallback> callback = aCallback;
if (mChannel) {
WaitForBgParent(mChannel->ChannelId())
->Then(
GetMainThreadSerialEventTarget(), __func__,
[callback]() { callback->ReadyToVerify(NS_OK); },
[callback](const nsresult& aResult) {
NS_ERROR("failed to establish the background channel");
callback->ReadyToVerify(aResult);
});
} else {
// mChannel can be null for several reasons (AsyncOpenFailed, etc)
NS_ERROR("No channel for ContinueVerification");
GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
__func__, [callback] { callback->ReadyToVerify(NS_ERROR_FAILURE); }));
}
return NS_OK;
}
void HttpChannelParent::ContinueRedirect2Verify(const nsresult& aResult) {
LOG(
("HttpChannelParent::ContinueRedirect2Verify "
"[this=%p result=%" PRIx32 "]\n",
this, static_cast<uint32_t>(aResult)));
if (mRedirectCallback) {
LOG(
("HttpChannelParent::ContinueRedirect2Verify call "
"OnRedirectVerifyCallback"
" [this=%p result=%" PRIx32 ", mRedirectCallback=%p]\n",
this, static_cast<uint32_t>(aResult), mRedirectCallback.get()));
mRedirectCallback->OnRedirectVerifyCallback(aResult);
mRedirectCallback = nullptr;
} else {
LOG(
("RecvRedirect2Verify[%p]: NO CALLBACKS! | "
"mRedirectChannelId: %" PRIx64 ", mRedirectChannel: %p",
this, mRedirectChannelId, mRedirectChannel.get()));
}
}
mozilla::ipc::IPCResult HttpChannelParent::RecvDocumentChannelCleanup(
const bool& clearCacheEntry) {
CleanupBackgroundChannel(); // Background channel can be closed.
mChannel = nullptr; // Reclaim some memory sooner.
if (clearCacheEntry) {
mCacheEntry = nullptr; // Else we'll block other channels reading same URI
}
return IPC_OK();
}
mozilla::ipc::IPCResult HttpChannelParent::RecvRemoveCorsPreflightCacheEntry(
nsIURI* uri, const mozilla::ipc::PrincipalInfo& requestingPrincipal,
const OriginAttributes& originAttributes) {
if (!uri) {
return IPC_FAIL_NO_REASON(this);
}
auto principalOrErr = PrincipalInfoToPrincipal(requestingPrincipal);
if (NS_WARN_IF(principalOrErr.isErr())) {
return IPC_FAIL_NO_REASON(this);
}
nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
nsCORSListenerProxy::RemoveFromCorsPreflightCache(uri, principal,
originAttributes);
return IPC_OK();
}
mozilla::ipc::IPCResult HttpChannelParent::RecvSetCookies(
const nsACString& aBaseDomain, const OriginAttributes& aOriginAttributes,
nsIURI* aHost, const bool& aFromHttp, nsTArray<CookieStruct>&& aCookies) {
net::PCookieServiceParent* csParent =
LoneManagedOrNullAsserts(Manager()->ManagedPCookieServiceParent());
NS_ENSURE_TRUE(csParent, IPC_OK());
auto* cs = static_cast<net::CookieServiceParent*>(csParent);
BrowsingContext* browsingContext = nullptr;
if (mBrowserParent) {
browsingContext = mBrowserParent->GetBrowsingContext();
}
return cs->SetCookies(nsCString(aBaseDomain), aOriginAttributes, aHost,
aFromHttp, aCookies, browsingContext);
}
//-----------------------------------------------------------------------------
// HttpChannelParent::nsIRequestObserver
//-----------------------------------------------------------------------------
static ResourceTimingStructArgs GetTimingAttributes(HttpBaseChannel* aChannel) {
ResourceTimingStructArgs args;
TimeStamp timeStamp;
aChannel->GetDomainLookupStart(&timeStamp);
args.domainLookupStart() = timeStamp;
aChannel->GetDomainLookupEnd(&timeStamp);
args.domainLookupEnd() = timeStamp;
aChannel->GetConnectStart(&timeStamp);
args.connectStart() = timeStamp;
aChannel->GetTcpConnectEnd(&timeStamp);
args.tcpConnectEnd() = timeStamp;
aChannel->GetSecureConnectionStart(&timeStamp);
args.secureConnectionStart() = timeStamp;
aChannel->GetConnectEnd(&timeStamp);
args.connectEnd() = timeStamp;
aChannel->GetRequestStart(&timeStamp);
args.requestStart() = timeStamp;
aChannel->GetResponseStart(&timeStamp);
args.responseStart() = timeStamp;
aChannel->GetResponseEnd(&timeStamp);
args.responseEnd() = timeStamp;
aChannel->GetAsyncOpen(&timeStamp);
args.fetchStart() = timeStamp;
aChannel->GetRedirectStart(&timeStamp);
args.redirectStart() = timeStamp;
aChannel->GetRedirectEnd(&timeStamp);
args.redirectEnd() = timeStamp;
uint64_t size = 0;
aChannel->GetTransferSize(&size);
args.transferSize() = size;
aChannel->GetEncodedBodySize(&size);
args.encodedBodySize() = size;
// decodedBodySize can be computed in the child process so it doesn't need
// to be passed down.
aChannel->GetCacheReadStart(&timeStamp);
args.cacheReadStart() = timeStamp;
aChannel->GetCacheReadEnd(&timeStamp);
args.cacheReadEnd() = timeStamp;
aChannel->GetTransactionPending(&timeStamp);
args.transactionPending() = timeStamp;
return args;
}
NS_IMETHODIMP
HttpChannelParent::OnStartRequest(nsIRequest* aRequest) {
nsresult rv;
LOG(("HttpChannelParent::OnStartRequest [this=%p, aRequest=%p]\n", this,
aRequest));
MOZ_ASSERT(NS_IsMainThread());
Maybe<uint32_t> multiPartID;
bool isFirstPartOfMultiPart = false;
bool isLastPartOfMultiPart = false;
DebugOnly<bool> isMultiPart = false;
RefPtr<HttpBaseChannel> chan = do_QueryObject(aRequest);
if (!chan) {
if (nsCOMPtr<nsIMultiPartChannel> multiPartChannel =
do_QueryInterface(aRequest)) {
isMultiPart = true;
nsCOMPtr<nsIChannel> baseChannel;
multiPartChannel->GetBaseChannel(getter_AddRefs(baseChannel));
chan = do_QueryObject(baseChannel);
uint32_t partID = 0;
multiPartChannel->GetPartID(&partID);
multiPartID = Some(partID);
multiPartChannel->GetIsFirstPart(&isFirstPartOfMultiPart);
multiPartChannel->GetIsLastPart(&isLastPartOfMultiPart);
} else if (nsCOMPtr<nsIViewSourceChannel> viewSourceChannel =
do_QueryInterface(aRequest)) {
chan = do_QueryObject(viewSourceChannel->GetInnerChannel());
}
}
MOZ_ASSERT(multiPartID || !isMultiPart, "Changed multi-part state?");
if (!chan) {
LOG((" aRequest is not HttpBaseChannel"));
NS_ERROR(
"Expecting only HttpBaseChannel as aRequest in "
"HttpChannelParent::OnStartRequest");
return NS_ERROR_UNEXPECTED;
}
mAfterOnStartRequestBegun = true;
// Todo: re-enable when bug 1589749 is fixed.
/*MOZ_ASSERT(mChannel == chan,
"HttpChannelParent getting OnStartRequest from a different "
"HttpBaseChannel instance");*/
HttpChannelOnStartRequestArgs args;
// Send down any permissions/cookies which are relevant to this URL if we are
// performing a document load. We can't do that if mIPCClosed is set.
if (!mIPCClosed) {
PContentParent* pcp = Manager()->Manager();
MOZ_ASSERT(pcp, "We should have a manager if our IPC isn't closed");
DebugOnly<nsresult> rv =
static_cast<ContentParent*>(pcp)->AboutToLoadHttpDocumentForChild(
chan, &args.shouldWaitForOnStartRequestSent());
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
args.multiPartID() = multiPartID;
args.isFirstPartOfMultiPart() = isFirstPartOfMultiPart;
args.isLastPartOfMultiPart() = isLastPartOfMultiPart;
args.cacheExpirationTime() = nsICacheEntry::NO_EXPIRATION_TIME;
RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(chan);
if (httpChannelImpl) {
httpChannelImpl->IsFromCache(&args.isFromCache());
httpChannelImpl->IsRacing(&args.isRacing());
httpChannelImpl->GetCacheEntryId(&args.cacheEntryId());
httpChannelImpl->GetCacheTokenFetchCount(&args.cacheFetchCount());
httpChannelImpl->GetCacheTokenExpirationTime(&args.cacheExpirationTime());
httpChannelImpl->GetProtocolVersion(args.protocolVersion());
mDataSentToChildProcess = httpChannelImpl->DataSentToChildProcess();
// If RCWN is enabled and cache wins, we can't use the ODA from socket
// process.
if (args.isRacing()) {
mDataSentToChildProcess =
httpChannelImpl->DataSentToChildProcess() && !args.isFromCache();
}
args.dataFromSocketProcess() = mDataSentToChildProcess;
}
// Propagate whether or not conversion should occur from the parent-side
// channel to the child-side channel. Then disable the parent-side
// conversion so that it only occurs in the child.
Unused << chan->GetApplyConversion(&args.applyConversion());
chan->SetApplyConversion(false);
// If we've already applied the conversion (as can happen if we installed
// a multipart converted), then don't apply it again on the child.
if (chan->HasAppliedConversion()) {
args.applyConversion() = false;
}
chan->GetStatus(&args.channelStatus());
// Keep the cache entry for future use when opening alternative streams.
// It could be already released by nsHttpChannel at that time.
nsCOMPtr<nsISupports> cacheEntry;
if (httpChannelImpl) {
httpChannelImpl->GetCacheToken(getter_AddRefs(cacheEntry));
mCacheEntry = do_QueryInterface(cacheEntry);
args.cacheEntryAvailable() = static_cast<bool>(mCacheEntry);
httpChannelImpl->GetCacheKey(&args.cacheKey());
httpChannelImpl->GetAlternativeDataType(args.altDataType());
}
args.altDataLength() = chan->GetAltDataLength();
args.deliveringAltData() = chan->IsDeliveringAltData();
args.securityInfo() = SecurityInfo();
chan->GetRedirectCount(&args.redirectCount());
chan->GetHasHTTPSRR(&args.hasHTTPSRR());
chan->GetIsProxyUsed(&args.isProxyUsed());
nsCOMPtr<nsILoadInfo> loadInfo = chan->LoadInfo();
mozilla::ipc::LoadInfoToParentLoadInfoForwarder(loadInfo,
&args.loadInfoForwarder());
nsHttpResponseHead* responseHead = chan->GetResponseHead();
bool useResponseHead = !!responseHead;
nsHttpResponseHead cleanedUpResponseHead;
if (responseHead &&
(responseHead->HasHeader(nsHttp::Set_Cookie) || multiPartID)) {
cleanedUpResponseHead = *responseHead;
cleanedUpResponseHead.ClearHeader(nsHttp::Set_Cookie);
if (multiPartID) {
nsCOMPtr<nsIChannel> multiPartChannel = do_QueryInterface(aRequest);
// For the multipart channel, use the parsed subtype instead. Note that
// `chan` is the underlying base channel of the multipart channel in this
// case, which is different from `multiPartChannel`.
MOZ_ASSERT(multiPartChannel);
nsAutoCString contentType;
multiPartChannel->GetContentType(contentType);
cleanedUpResponseHead.SetContentType(contentType);
}
responseHead = &cleanedUpResponseHead;
}
if (!responseHead) {
responseHead = &cleanedUpResponseHead;
}
if (chan->ChannelBlockedByOpaqueResponse() &&
chan->CachedOpaqueResponseBlockingPref()) {
responseHead->ClearHeaders();
}
chan->GetIsResolvedByTRR(&args.isResolvedByTRR());
chan->GetAllRedirectsSameOrigin(&args.allRedirectsSameOrigin());
chan->GetCrossOriginOpenerPolicy(&args.openerPolicy());
args.selfAddr() = chan->GetSelfAddr();
args.peerAddr() = chan->GetPeerAddr();
args.timing() = GetTimingAttributes(mChannel);
if (mOverrideReferrerInfo) {
args.overrideReferrerInfo() = ToRefPtr(std::move(mOverrideReferrerInfo));
}
if (!mCookie.IsEmpty()) {
args.cookie() = std::move(mCookie);
}
nsHttpRequestHead* requestHead = chan->GetRequestHead();
// !!! We need to lock headers and please don't forget to unlock them !!!
requestHead->Enter();
nsHttpHeaderArray cleanedUpRequestHeaders;
bool cleanedUpRequest = false;
if (requestHead->HasHeader(nsHttp::Cookie)) {
cleanedUpRequestHeaders = requestHead->Headers();
cleanedUpRequestHeaders.ClearHeader(nsHttp::Cookie);
cleanedUpRequest = true;
}
rv = NS_OK;
nsCOMPtr<nsICacheEntry> altDataSource;
nsCOMPtr<nsICacheInfoChannel> cacheChannel =
do_QueryInterface(static_cast<nsIChannel*>(mChannel.get()));
if (cacheChannel) {
for (const auto& pref : cacheChannel->PreferredAlternativeDataTypes()) {
if (pref.type() == args.altDataType() &&
pref.deliverAltData() ==
nsICacheInfoChannel::PreferredAlternativeDataDeliveryType::
SERIALIZE) {
altDataSource = mCacheEntry;
break;
}
}
}
nsIRequest::TRRMode effectiveMode = nsIRequest::TRR_DEFAULT_MODE;
mChannel->GetEffectiveTRRMode(&effectiveMode);
args.effectiveTRRMode() = effectiveMode;
TRRSkippedReason reason = TRRSkippedReason::TRR_UNSET;
mChannel->GetTrrSkipReason(&reason);
args.trrSkipReason() = reason;
if (mIPCClosed ||
!mBgParent->OnStartRequest(
*responseHead, useResponseHead,
cleanedUpRequest ? cleanedUpRequestHeaders : requestHead->Headers(),
args, altDataSource, chan->GetOnStartRequestStartTime())) {
rv = NS_ERROR_UNEXPECTED;
}
requestHead->Exit();
// Need to wait for the cookies/permissions to content process, which is sent
// via PContent in AboutToLoadHttpFtpDocumentForChild. For multipart channel,
// send only one time since the cookies/permissions are the same.
if (NS_SUCCEEDED(rv) && args.shouldWaitForOnStartRequestSent() &&
multiPartID.valueOr(0) == 0) {
LOG(("HttpChannelParent::SendOnStartRequestSent\n"));
Unused << SendOnStartRequestSent();
}
if (!args.timing().domainLookupEnd().IsNull() &&
!args.timing().connectStart().IsNull()) {
nsAutoCString protocolVersion;
mChannel->GetProtocolVersion(protocolVersion);
uint32_t classOfServiceFlags = 0;
mChannel->GetClassFlags(&classOfServiceFlags);
nsAutoCString cosString;
ClassOfService::ToString(classOfServiceFlags, cosString);
nsAutoCString key(
nsPrintfCString("%s_%s", protocolVersion.get(), cosString.get()));
Telemetry::AccumulateTimeDelta(
Telemetry::NETWORK_DNS_END_TO_CONNECT_START_EXP_MS, key,
args.timing().domainLookupEnd(), args.timing().connectStart());
}
return rv;
}
NS_IMETHODIMP
HttpChannelParent::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
LOG(("HttpChannelParent::OnStopRequest: [this=%p aRequest=%p status=%" PRIx32
"]\n",
this, aRequest, static_cast<uint32_t>(aStatusCode)));
MOZ_ASSERT(NS_IsMainThread());
RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
if (httpChannelImpl) {
httpChannelImpl->SetWarningReporter(nullptr);
}
nsHttpHeaderArray* responseTrailer = mChannel->GetResponseTrailers();
nsTArray<ConsoleReportCollected> consoleReports;
RefPtr<HttpBaseChannel> httpChannel = do_QueryObject(mChannel);
TimeStamp onStopRequestStart;
if (httpChannel) {
httpChannel->StealConsoleReports(consoleReports);
onStopRequestStart = httpChannel->GetOnStopRequestStartTime();
}
// Either IPC channel is closed or background channel
// is ready to send OnStopRequest.
MOZ_ASSERT(mIPCClosed || mBgParent);
if (mDataSentToChildProcess) {
if (mIPCClosed || !mBgParent ||
!mBgParent->OnConsoleReport(consoleReports)) {
return NS_ERROR_UNEXPECTED;
}
return NS_OK;
}
// If we're handling a multi-part stream, then send this directly
// over PHttpChannel to make synchronization easier.
if (mIPCClosed || !mBgParent ||
!mBgParent->OnStopRequest(
aStatusCode, GetTimingAttributes(mChannel),
responseTrailer ? *responseTrailer : nsHttpHeaderArray(),
consoleReports, onStopRequestStart)) {
return NS_ERROR_UNEXPECTED;
}
if (NeedFlowControl()) {
bool isLocal = false;
NetAddr peerAddr = mChannel->GetPeerAddr();
#if defined(XP_UNIX)
// Unix-domain sockets are always local.
isLocal = (peerAddr.raw.family == PR_AF_LOCAL);
#endif
isLocal = isLocal || peerAddr.IsLoopbackAddr();
if (!isLocal) {
if (!mHasSuspendedByBackPressure) {
AccumulateCategorical(
Telemetry::LABELS_NETWORK_BACK_PRESSURE_SUSPENSION_RATE_V2::
NotSuspended);
} else {
AccumulateCategorical(
Telemetry::LABELS_NETWORK_BACK_PRESSURE_SUSPENSION_RATE_V2::
Suspended);
// Only analyze non-local suspended cases, which we are interested in.
nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
Telemetry::Accumulate(
Telemetry::NETWORK_BACK_PRESSURE_SUSPENSION_CP_TYPE,
loadInfo->InternalContentPolicyType());
}
} else {
if (!mHasSuspendedByBackPressure) {
AccumulateCategorical(
Telemetry::LABELS_NETWORK_BACK_PRESSURE_SUSPENSION_RATE_V2::
NotSuspendedLocal);
} else {
AccumulateCategorical(
Telemetry::LABELS_NETWORK_BACK_PRESSURE_SUSPENSION_RATE_V2::
SuspendedLocal);
}
}
}
return NS_OK;
}
//-----------------------------------------------------------------------------
// HttpChannelParent::nsIMultiPartChannelListener
//-----------------------------------------------------------------------------
NS_IMETHODIMP
HttpChannelParent::OnAfterLastPart(nsresult aStatus) {
LOG(("HttpChannelParent::OnAfterLastPart [this=%p]\n", this));
MOZ_ASSERT(NS_IsMainThread());
// If IPC channel is closed, there is nothing we can do. Just return NS_OK.
if (mIPCClosed) {
return NS_OK;
}
// If IPC channel is open, background channel should be ready to send
// OnAfterLastPart.
MOZ_ASSERT(mBgParent);
if (!mBgParent || !mBgParent->OnAfterLastPart(aStatus)) {
return NS_ERROR_UNEXPECTED;
}
return NS_OK;
}
//-----------------------------------------------------------------------------
// HttpChannelParent::nsIStreamListener
//-----------------------------------------------------------------------------
NS_IMETHODIMP
HttpChannelParent::OnDataAvailable(nsIRequest* aRequest,
nsIInputStream* aInputStream,
uint64_t aOffset, uint32_t aCount) {
LOG(("HttpChannelParent::OnDataAvailable [this=%p aRequest=%p offset=%" PRIu64
" count=%" PRIu32 "]\n",
this, aRequest, aOffset, aCount));
MOZ_ASSERT(NS_IsMainThread());
if (mDataSentToChildProcess) {
uint32_t n;
return aInputStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &n);
}
nsresult channelStatus = NS_OK;
mChannel->GetStatus(&channelStatus);
nsresult transportStatus = NS_NET_STATUS_RECEIVING_FROM;
RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
TimeStamp onDataAvailableStart = TimeStamp::Now();
if (httpChannelImpl) {
if (httpChannelImpl->IsReadingFromCache()) {
transportStatus = NS_NET_STATUS_READING;
}
onDataAvailableStart = httpChannelImpl->GetDataAvailableStartTime();
}
nsCString data;
nsresult rv = NS_ReadInputStreamToString(aInputStream, data, aCount);
if (NS_FAILED(rv)) {
return rv;
}
// Either IPC channel is closed or background channel
// is ready to send OnTransportAndData.
MOZ_ASSERT(mIPCClosed || mBgParent);
if (mIPCClosed || !mBgParent ||
!mBgParent->OnTransportAndData(channelStatus, transportStatus, aOffset,
aCount, data, onDataAvailableStart)) {
return NS_ERROR_UNEXPECTED;
}
int32_t count = static_cast<int32_t>(aCount);
if (NeedFlowControl()) {
// We're going to run out of sending window size
if (mSendWindowSize > 0 && mSendWindowSize <= count) {
MOZ_ASSERT(!mSuspendedForFlowControl);
LOG((" suspend the channel due to e10s backpressure"));
Unused << mChannel->Suspend();
mSuspendedForFlowControl = true;
mHasSuspendedByBackPressure = true;
} else if (!mResumedTimestamp.IsNull()) {
// Calculate the delay when the first packet arrived after resume
Telemetry::AccumulateTimeDelta(
Telemetry::NETWORK_BACK_PRESSURE_SUSPENSION_DELAY_TIME_MS,
mResumedTimestamp);
mResumedTimestamp = TimeStamp();
}
mSendWindowSize -= count;
}
return NS_OK;
}
bool HttpChannelParent::NeedFlowControl() {
if (mCacheNeedFlowControlInitialized) {
return mNeedFlowControl;
}
int64_t contentLength = -1;
RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
// By design, we won't trigger the flow control if
// a. pref-out
// b. the resource is from cache or partial cache
// c. the resource is small
// d. data will be sent from socket process to child process directly
// Note that we served the cached resource first for partical cache, which is
// ignored here since we only take the first ODA into consideration.
if (gHttpHandler->SendWindowSize() == 0 || !httpChannelImpl ||
httpChannelImpl->IsReadingFromCache() ||
NS_FAILED(httpChannelImpl->GetContentLength(&contentLength)) ||
contentLength < gHttpHandler->SendWindowSize() ||
mDataSentToChildProcess) {
mNeedFlowControl = false;
}
mCacheNeedFlowControlInitialized = true;
return mNeedFlowControl;
}
mozilla::ipc::IPCResult HttpChannelParent::RecvBytesRead(
const int32_t& aCount) {
if (!NeedFlowControl()) {
return IPC_OK();
}
LOG(("HttpChannelParent::RecvBytesRead [this=%p count=%" PRId32 "]\n", this,
aCount));
if (mSendWindowSize <= 0 && mSendWindowSize + aCount > 0) {
MOZ_ASSERT(mSuspendedForFlowControl);
LOG((" resume the channel due to e10s backpressure relief"));
Unused << mChannel->Resume();
mSuspendedForFlowControl = false;
mResumedTimestamp = TimeStamp::Now();
}
mSendWindowSize += aCount;
return IPC_OK();
}
mozilla::ipc::IPCResult HttpChannelParent::RecvOpenOriginalCacheInputStream() {
if (mIPCClosed) {
return IPC_OK();
}
Maybe<IPCStream> ipcStream;
if (mCacheEntry) {
nsCOMPtr<nsIInputStream> inputStream;
nsresult rv = mCacheEntry->OpenInputStream(0, getter_AddRefs(inputStream));
if (NS_SUCCEEDED(rv)) {
Unused << mozilla::ipc::SerializeIPCStream(
inputStream.forget(), ipcStream, /* aAllowLazy */ false);
}
}
Unused << SendOriginalCacheInputStreamAvailable(ipcStream);
return IPC_OK();
}
//-----------------------------------------------------------------------------
// HttpChannelParent::nsIProgressEventSink
//-----------------------------------------------------------------------------
NS_IMETHODIMP
HttpChannelParent::OnProgress(nsIRequest* aRequest, int64_t aProgress,
int64_t aProgressMax) {
LOG(("HttpChannelParent::OnProgress [this=%p progress=%" PRId64 "max=%" PRId64
"]\n",
this, aProgress, aProgressMax));
MOZ_ASSERT(NS_IsMainThread());
// If IPC channel is closed, there is nothing we can do. Just return NS_OK.
if (mIPCClosed) {
return NS_OK;
}
// If it indicates this precedes OnDataAvailable, child can derive the value
// in ODA.
if (mIgnoreProgress) {
mIgnoreProgress = false;
return NS_OK;
}
// If IPC channel is open, background channel should be ready to send
// OnProgress.
MOZ_ASSERT(mBgParent);
// Send OnProgress events to the child for data upload progress notifications
// (i.e. status == NS_NET_STATUS_SENDING_TO) or if the channel has
// LOAD_BACKGROUND set.
if (!mBgParent || !mBgParent->OnProgress(aProgress, aProgressMax)) {
return NS_ERROR_UNEXPECTED;
}
return NS_OK;
}
NS_IMETHODIMP
HttpChannelParent::OnStatus(nsIRequest* aRequest, nsresult aStatus,
const char16_t* aStatusArg) {
LOG(("HttpChannelParent::OnStatus [this=%p status=%" PRIx32 "]\n", this,
static_cast<uint32_t>(aStatus)));
MOZ_ASSERT(NS_IsMainThread());
// If IPC channel is closed, there is nothing we can do. Just return NS_OK.
if (mIPCClosed) {
return NS_OK;
}
// If this precedes OnDataAvailable, transportStatus will be derived in ODA.
if (aStatus == NS_NET_STATUS_RECEIVING_FROM ||
aStatus == NS_NET_STATUS_READING) {
// The transport status and progress generated by ODA will be coalesced
// into one IPC message. Therefore, we can ignore the next OnProgress event
// since it is generated by ODA as well.
mIgnoreProgress = true;
return NS_OK;
}
// If IPC channel is open, background channel should be ready to send
// OnStatus.
MOZ_ASSERT(mIPCClosed || mBgParent);
// Otherwise, send to child now
if (!mBgParent || !mBgParent->OnStatus(aStatus)) {
return NS_ERROR_UNEXPECTED;
}
return NS_OK;
}
//-----------------------------------------------------------------------------
// HttpChannelParent::nsIParentChannel
//-----------------------------------------------------------------------------
NS_IMETHODIMP
HttpChannelParent::SetParentListener(ParentChannelListener* aListener) {
LOG(("HttpChannelParent::SetParentListener [this=%p aListener=%p]\n", this,
aListener));
MOZ_ASSERT(aListener);
MOZ_ASSERT(!mParentListener,
"SetParentListener should only be called for "
"new HttpChannelParents after a redirect, when "
"mParentListener is null.");
mParentListener = aListener;
return NS_OK;
}
NS_IMETHODIMP
HttpChannelParent::SetClassifierMatchedInfo(const nsACString& aList,
const nsACString& aProvider,
const nsACString& aFullHash) {
LOG(("HttpChannelParent::SetClassifierMatchedInfo [this=%p]\n", this));
if (!mIPCClosed) {
MOZ_ASSERT(mBgParent);
Unused << mBgParent->OnSetClassifierMatchedInfo(aList, aProvider,
aFullHash);
}
return NS_OK;
}
NS_IMETHODIMP
HttpChannelParent::SetClassifierMatchedTrackingInfo(
const nsACString& aLists, const nsACString& aFullHashes) {
LOG(("HttpChannelParent::SetClassifierMatchedTrackingInfo [this=%p]\n",
this));
if (!mIPCClosed) {
MOZ_ASSERT(mBgParent);
Unused << mBgParent->OnSetClassifierMatchedTrackingInfo(aLists,
aFullHashes);
}
return NS_OK;
}
NS_IMETHODIMP
HttpChannelParent::NotifyClassificationFlags(uint32_t aClassificationFlags,
bool aIsThirdParty) {
LOG(
("HttpChannelParent::NotifyClassificationFlags "
"classificationFlags=%" PRIu32 ", thirdparty=%d [this=%p]\n",
aClassificationFlags, static_cast<int>(aIsThirdParty), this));
if (!mIPCClosed) {
MOZ_ASSERT(mBgParent);
Unused << mBgParent->OnNotifyClassificationFlags(aClassificationFlags,
aIsThirdParty);
}
return NS_OK;
}
NS_IMETHODIMP
HttpChannelParent::Delete() {
if (!mIPCClosed) Unused << DoSendDeleteSelf();
return NS_OK;
}
NS_IMETHODIMP
HttpChannelParent::GetRemoteType(nsACString& aRemoteType) {
if (!CanSend()) {
return NS_ERROR_UNEXPECTED;
}
dom::PContentParent* pcp = Manager()->Manager();
aRemoteType = static_cast<dom::ContentParent*>(pcp)->GetRemoteType();
return NS_OK;
}
bool HttpChannelParent::IsRedirectDueToAuthRetry(uint32_t redirectFlags) {
return (redirectFlags & nsIChannelEventSink::REDIRECT_AUTH_RETRY);
}
//-----------------------------------------------------------------------------
// HttpChannelParent::nsIParentRedirectingChannel
//-----------------------------------------------------------------------------
NS_IMETHODIMP
HttpChannelParent::StartRedirect(nsIChannel* newChannel, uint32_t redirectFlags,
nsIAsyncVerifyRedirectCallback* callback) {
nsresult rv;
LOG(("HttpChannelParent::StartRedirect [this=%p, newChannel=%p callback=%p]",
this, newChannel, callback));
// Register the new channel and obtain id for it
nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
RedirectChannelRegistrar::GetOrCreate();
MOZ_ASSERT(registrar);
mRedirectChannelId = nsContentUtils::GenerateLoadIdentifier();
rv = registrar->RegisterChannel(newChannel, mRedirectChannelId);
NS_ENSURE_SUCCESS(rv, rv);
LOG(("Registered %p channel under id=%" PRIx64, newChannel,
mRedirectChannelId));
if (mIPCClosed) {
return NS_BINDING_ABORTED;
}
// If this is an internal redirect for service worker interception or
// internal redirect due to auth retries, then hide it from the child
// process. The original e10s interception code was not designed with this
// in mind and its not necessary to replace the HttpChannelChild/Parent
// objects in this case.
if (redirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
nsCOMPtr<nsIInterceptedChannel> oldIntercepted =
do_QueryInterface(static_cast<nsIChannel*>(mChannel.get()));
nsCOMPtr<nsIInterceptedChannel> newIntercepted =
do_QueryInterface(newChannel);
// 1. We only want to hide the special internal redirects from
// nsHttpChannel to InterceptedHttpChannel.
// 2. We want to allow through internal redirects
// initiated from the InterceptedHttpChannel even if they are to another
// InterceptedHttpChannel, except the interception reset, since
// corresponding HttpChannelChild/Parent objects can be reused for reset
// case.
// 3. If this is an internal redirect due to auth retry then we will
// hide it from the child process
if ((!oldIntercepted && newIntercepted) ||
(oldIntercepted && !newIntercepted && oldIntercepted->IsReset()) ||
(IsRedirectDueToAuthRetry(redirectFlags))) {
// We need to move across the reserved and initial client information
// to the new channel. Normally this would be handled by the child
// ClientChannelHelper, but that is not notified of this redirect since
// we're not propagating it back to the child process.
nsCOMPtr<nsILoadInfo> oldLoadInfo = mChannel->LoadInfo();
nsCOMPtr<nsILoadInfo> newLoadInfo = newChannel->LoadInfo();
Maybe<ClientInfo> reservedClientInfo(
oldLoadInfo->GetReservedClientInfo());
if (reservedClientInfo.isSome()) {
newLoadInfo->SetReservedClientInfo(reservedClientInfo.ref());
}
Maybe<ClientInfo> initialClientInfo(oldLoadInfo->GetInitialClientInfo());
if (initialClientInfo.isSome()) {
newLoadInfo->SetInitialClientInfo(initialClientInfo.ref());
}
// If this is ServiceWorker fallback redirect, info HttpChannelChild to
// detach StreamFilters. Otherwise StreamFilters will be attached twice
// on the same HttpChannelChild when opening the new nsHttpChannel.
if (oldIntercepted) {
Unused << DetachStreamFilters();
}
// Re-link the HttpChannelParent to the new channel.
nsCOMPtr<nsIChannel> linkedChannel;
rv = NS_LinkRedirectChannels(mRedirectChannelId, this,
getter_AddRefs(linkedChannel));
NS_ENSURE_SUCCESS(rv, rv);
MOZ_ASSERT(linkedChannel == newChannel);
// We immediately store the channel as our nested mChannel.
// None of the redirect IPC messaging takes place.
mChannel = do_QueryObject(newChannel);
callback->OnRedirectVerifyCallback(NS_OK);
return NS_OK;
}
}
// Sending down the original URI, because that is the URI we have
// to construct the channel from - this is the URI we've been actually
// redirected to. URI of the channel may be an inner channel URI.
// URI of the channel will be reconstructed by the protocol handler
// on the child process, no need to send it then.
nsCOMPtr<nsIURI> newOriginalURI;
newChannel->GetOriginalURI(getter_AddRefs(newOriginalURI));
uint32_t newLoadFlags = nsIRequest::LOAD_NORMAL;
MOZ_ALWAYS_SUCCEEDS(newChannel->GetLoadFlags(&newLoadFlags));
nsCOMPtr<nsITransportSecurityInfo> securityInfo(SecurityInfo());
// If the channel is a HTTP channel, we also want to inform the child
// about the parent's channelId attribute, so that both parent and child
// share the same ID. Useful for monitoring channel activity in devtools.
uint64_t channelId = 0;
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
if (httpChannel) {
rv = httpChannel->GetChannelId(&channelId);
NS_ENSURE_SUCCESS(rv, NS_BINDING_ABORTED);
}
nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
ParentLoadInfoForwarderArgs loadInfoForwarderArg;
mozilla::ipc::LoadInfoToParentLoadInfoForwarder(loadInfo,
&loadInfoForwarderArg);
nsHttpResponseHead* responseHead = mChannel->GetResponseHead();
nsHttpResponseHead cleanedUpResponseHead;
if (responseHead && responseHead->HasHeader(nsHttp::Set_Cookie)) {
cleanedUpResponseHead = *responseHead;
cleanedUpResponseHead.ClearHeader(nsHttp::Set_Cookie);
responseHead = &cleanedUpResponseHead;
}
if (!responseHead) {
responseHead = &cleanedUpResponseHead;
}
if (!mIPCClosed) {
if (!SendRedirect1Begin(mRedirectChannelId, newOriginalURI, newLoadFlags,
redirectFlags, loadInfoForwarderArg, *responseHead,
securityInfo, channelId, mChannel->GetPeerAddr(),
GetTimingAttributes(mChannel))) {
return NS_BINDING_ABORTED;
}
}
// Result is handled in RecvRedirect2Verify above
mRedirectChannel = newChannel;
mRedirectCallback = callback;
return NS_OK;
}
NS_IMETHODIMP
HttpChannelParent::CompleteRedirect(nsresult status) {
LOG(("HttpChannelParent::CompleteRedirect [this=%p status=0x%X]\n", this,
static_cast<uint32_t>(status)));
// If this was an internal redirect for a service worker interception then
// we will not have a redirecting channel here. Hide this redirect from
// the child.
if (!mRedirectChannel) {
return NS_OK;
}
if (!mIPCClosed) {
// TODO: check return value: assume child dead if failed
if (NS_SUCCEEDED(status)) {
Unused << SendRedirect3Complete();
} else {
Unused << SendRedirectFailed(status);
}
}
mRedirectChannel = nullptr;
return NS_OK;
}
nsresult HttpChannelParent::OpenAlternativeOutputStream(
const nsACString& type, int64_t predictedSize,
nsIAsyncOutputStream** _retval) {
// We need to make sure the child does not call SendDocumentChannelCleanup()
// before opening the altOutputStream, because that clears mCacheEntry.
if (!mCacheEntry) {
return NS_ERROR_NOT_AVAILABLE;
}
nsresult rv =
mCacheEntry->OpenAlternativeOutputStream(type, predictedSize, _retval);
if (NS_SUCCEEDED(rv)) {
mCacheEntry->SetMetaDataElement("alt-data-from-child", "1");
}
return rv;
}
already_AddRefed<nsITransportSecurityInfo> HttpChannelParent::SecurityInfo() {
if (!mChannel) {
return nullptr;
}
nsCOMPtr<nsITransportSecurityInfo> securityInfo;
mChannel->GetSecurityInfo(getter_AddRefs(securityInfo));
return securityInfo.forget();
}
bool HttpChannelParent::DoSendDeleteSelf() {
mIPCClosed = true;
bool rv = SendDeleteSelf();
CleanupBackgroundChannel();
return rv;
}
mozilla::ipc::IPCResult HttpChannelParent::RecvDeletingChannel() {
// We need to ensure that the parent channel will not be sending any more IPC
// messages after this, as the child is going away. DoSendDeleteSelf will
// set mIPCClosed = true;
if (!DoSendDeleteSelf()) {
return IPC_FAIL_NO_REASON(this);
}
return IPC_OK();
}
//-----------------------------------------------------------------------------
// HttpChannelSecurityWarningReporter
//-----------------------------------------------------------------------------
nsresult HttpChannelParent::ReportSecurityMessage(
const nsAString& aMessageTag, const nsAString& aMessageCategory) {
if (mIPCClosed || NS_WARN_IF(!SendReportSecurityMessage(
nsString(aMessageTag), nsString(aMessageCategory)))) {
return NS_ERROR_UNEXPECTED;
}
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsIAsyncVerifyRedirectReadyCallback
//-----------------------------------------------------------------------------
NS_IMETHODIMP
HttpChannelParent::ReadyToVerify(nsresult aResult) {
LOG(("HttpChannelParent::ReadyToVerify [this=%p result=%" PRIx32 "]\n", this,
static_cast<uint32_t>(aResult)));
MOZ_ASSERT(NS_IsMainThread());
ContinueRedirect2Verify(aResult);
return NS_OK;
}
void HttpChannelParent::DoSendSetPriority(int16_t aValue) {
if (!mIPCClosed) {
Unused << SendSetPriority(aValue);
}
}
nsresult HttpChannelParent::LogBlockedCORSRequest(const nsAString& aMessage,
const nsACString& aCategory,
bool aIsWarning) {
if (mIPCClosed ||
NS_WARN_IF(!SendLogBlockedCORSRequest(
nsString(aMessage), nsCString(aCategory), aIsWarning))) {
return NS_ERROR_UNEXPECTED;
}
return NS_OK;
}
nsresult HttpChannelParent::LogMimeTypeMismatch(const nsACString& aMessageName,
bool aWarning,
const nsAString& aURL,
const nsAString& aContentType) {
if (mIPCClosed || NS_WARN_IF(!SendLogMimeTypeMismatch(
nsCString(aMessageName), aWarning, nsString(aURL),
nsString(aContentType)))) {
return NS_ERROR_UNEXPECTED;
}
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsIChannelEventSink
//-----------------------------------------------------------------------------
NS_IMETHODIMP
HttpChannelParent::AsyncOnChannelRedirect(
nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aRedirectFlags,
nsIAsyncVerifyRedirectCallback* aCallback) {
LOG(
("HttpChannelParent::AsyncOnChannelRedirect [this=%p, old=%p, "
"new=%p, flags=%u]",
this, aOldChannel, aNewChannel, aRedirectFlags));
return StartRedirect(aNewChannel, aRedirectFlags, aCallback);
}
//-----------------------------------------------------------------------------
// nsIRedirectResultListener
//-----------------------------------------------------------------------------
NS_IMETHODIMP
HttpChannelParent::OnRedirectResult(nsresult status) {
LOG(("HttpChannelParent::OnRedirectResult [this=%p, status=0x%X]", this,
static_cast<uint32_t>(status)));
nsresult rv = NS_OK;
nsCOMPtr<nsIParentChannel> redirectChannel;
if (mRedirectChannelId) {
nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
RedirectChannelRegistrar::GetOrCreate();
MOZ_ASSERT(registrar);
rv = registrar->GetParentChannel(mRedirectChannelId,
getter_AddRefs(redirectChannel));
if (NS_FAILED(rv) || !redirectChannel) {
// Redirect might get canceled before we got AsyncOnChannelRedirect
LOG(("Registered parent channel not found under id=%" PRIx64,
mRedirectChannelId));
nsCOMPtr<nsIChannel> newChannel;
rv = registrar->GetRegisteredChannel(mRedirectChannelId,
getter_AddRefs(newChannel));
MOZ_ASSERT(newChannel, "Already registered channel not found");
if (NS_SUCCEEDED(rv)) {
newChannel->Cancel(NS_BINDING_ABORTED);
}
}
// Release all previously registered channels, they are no longer need to be
// kept in the registrar from this moment.
registrar->DeregisterChannels(mRedirectChannelId);
mRedirectChannelId = 0;
}
if (!redirectChannel) {
if (NS_FAILED(rv)) {
status = rv;
} else {
status = NS_ERROR_NULL_POINTER;
}
}
CompleteRedirect(status);
if (NS_SUCCEEDED(status)) {
if (!SameCOMIdentity(redirectChannel,
static_cast<nsIParentRedirectingChannel*>(this))) {
Delete();
mParentListener->SetListenerAfterRedirect(redirectChannel);
redirectChannel->SetParentListener(mParentListener);
}
} else if (redirectChannel) {
// Delete the redirect target channel: continue using old channel
redirectChannel->Delete();
}
return NS_OK;
}
void HttpChannelParent::OverrideReferrerInfoDuringBeginConnect(
nsIReferrerInfo* aReferrerInfo) {
MOZ_ASSERT(aReferrerInfo);
MOZ_ASSERT(!mAfterOnStartRequestBegun);
mOverrideReferrerInfo = aReferrerInfo;
}
auto HttpChannelParent::AttachStreamFilter(
Endpoint<extensions::PStreamFilterParent>&& aParentEndpoint,
Endpoint<extensions::PStreamFilterChild>&& aChildEndpoint)
-> RefPtr<ChildEndpointPromise> {
LOG(("HttpChannelParent::AttachStreamFilter [this=%p]", this));
MOZ_ASSERT(!mAfterOnStartRequestBegun);
if (mIPCClosed) {
return ChildEndpointPromise::CreateAndReject(false, __func__);
}
// If IPC channel is open, background channel should be ready to send
// SendAttachStreamFilter.
MOZ_ASSERT(mBgParent);
return InvokeAsync(mBgParent->GetBackgroundTarget(), mBgParent.get(),
__func__, &HttpBackgroundChannelParent::AttachStreamFilter,
std::move(aParentEndpoint), std::move(aChildEndpoint));
}
auto HttpChannelParent::DetachStreamFilters() -> RefPtr<GenericPromise> {
LOG(("HttpChannelParent::DeattachStreamFilter [this=%p]", this));
MOZ_ASSERT(!mAfterOnStartRequestBegun);
if (NS_WARN_IF(mIPCClosed)) {
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
}
MOZ_ASSERT(mBgParent);
return InvokeAsync(mBgParent->GetBackgroundTarget(), mBgParent.get(),
__func__,
&HttpBackgroundChannelParent::DetachStreamFilters);
}
void HttpChannelParent::SetHttpChannelFromEarlyHintPreloader(
HttpBaseChannel* aChannel) {
MOZ_ASSERT(aChannel);
if (mChannel) {
MOZ_ASSERT(false, "SetHttpChannel called with mChannel aready set");
return;
}
mChannel = aChannel;
}
void HttpChannelParent::SetCookie(nsCString&& aCookie) {
LOG(("HttpChannelParent::SetCookie [this=%p]", this));
MOZ_ASSERT(!mAfterOnStartRequestBegun);
MOZ_ASSERT(mCookie.IsEmpty());
// The loadGroup of the channel in the parent process could be null in the
// XPCShell content process test, see test_cookiejars_wrap.js. In this case,
// we cannot explicitly set the loadGroup for the parent channel because it's
// created from the content process. To workaround this, we add a testing pref
// to skip this check.
if (!Preferences::GetBool(
"network.cookie.skip_browsing_context_check_in_parent_for_testing") &&
mChannel->IsBrowsingContextDiscarded()) {
return;
}
mCookie = std::move(aCookie);
}
} // namespace mozilla::net