summaryrefslogtreecommitdiffstats
path: root/netwerk/protocol/http/HttpChannelParent.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/protocol/http/HttpChannelParent.cpp')
-rw-r--r--netwerk/protocol/http/HttpChannelParent.cpp2202
1 files changed, 2202 insertions, 0 deletions
diff --git a/netwerk/protocol/http/HttpChannelParent.cpp b/netwerk/protocol/http/HttpChannelParent.cpp
new file mode 100644
index 0000000000..e734d15a7d
--- /dev/null
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -0,0 +1,2202 @@
+/* -*- 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);
+ }
+
+ 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);
+ }
+
+ 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)->AboutToLoadHttpFtpDocumentForChild(
+ 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