/* 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/. */ #include "FetchLog.h" #include "FetchParent.h" #include "nsContentUtils.h" #include "nsIContentSecurityPolicy.h" #include "nsICookieJarSettings.h" #include "nsILoadGroup.h" #include "nsILoadInfo.h" #include "nsIIOService.h" #include "nsIObserverService.h" #include "nsIPrincipal.h" #include "nsIScriptSecurityManager.h" #include "nsNetUtil.h" #include "nsThreadUtils.h" #include "nsXULAppAPI.h" #include "mozilla/BasePrincipal.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/SchedulerGroup.h" #include "mozilla/ScopeExit.h" #include "mozilla/UniquePtr.h" #include "mozilla/dom/ClientInfo.h" #include "mozilla/dom/FetchService.h" #include "mozilla/dom/InternalRequest.h" #include "mozilla/dom/InternalResponse.h" #include "mozilla/dom/PerformanceStorage.h" #include "mozilla/dom/PerformanceTiming.h" #include "mozilla/dom/ServiceWorkerDescriptor.h" #include "mozilla/glean/NetwerkMetrics.h" #include "mozilla/ipc/BackgroundUtils.h" #include "mozilla/net/CookieJarSettings.h" namespace mozilla::dom { mozilla::LazyLogModule gFetchLog("Fetch"); // FetchServicePromises FetchServicePromises::FetchServicePromises() : mAvailablePromise( MakeRefPtr(__func__)), mTimingPromise( MakeRefPtr(__func__)), mEndPromise( MakeRefPtr(__func__)) { mAvailablePromise->UseDirectTaskDispatch(__func__); mTimingPromise->UseDirectTaskDispatch(__func__); mEndPromise->UseDirectTaskDispatch(__func__); } RefPtr FetchServicePromises::GetResponseAvailablePromise() { return mAvailablePromise; } RefPtr FetchServicePromises::GetResponseTimingPromise() { return mTimingPromise; } RefPtr FetchServicePromises::GetResponseEndPromise() { return mEndPromise; } void FetchServicePromises::ResolveResponseAvailablePromise( FetchServiceResponse&& aResponse, StaticString aMethodName) { if (mAvailablePromise) { mAvailablePromiseResolved = true; mAvailablePromise->Resolve(std::move(aResponse), aMethodName); } } void FetchServicePromises::RejectResponseAvailablePromise( const CopyableErrorResult&& aError, StaticString aMethodName) { if (mAvailablePromise) { mAvailablePromise->Reject(aError, aMethodName); } } void FetchServicePromises::ResolveResponseTimingPromise( ResponseTiming&& aTiming, StaticString aMethodName) { if (mTimingPromise) { mTimingPromiseResolved = true; mTimingPromise->Resolve(std::move(aTiming), aMethodName); } } void FetchServicePromises::RejectResponseTimingPromise( const CopyableErrorResult&& aError, StaticString aMethodName) { if (mTimingPromise) { mTimingPromise->Reject(aError, aMethodName); } } void FetchServicePromises::ResolveResponseEndPromise(ResponseEndArgs&& aArgs, StaticString aMethodName) { if (mEndPromise) { mEndPromiseResolved = true; mEndPromise->Resolve(std::move(aArgs), aMethodName); } } void FetchServicePromises::RejectResponseEndPromise( const CopyableErrorResult&& aError, StaticString aMethodName) { if (mEndPromise) { mEndPromise->Reject(aError, aMethodName); } } // FetchInstance nsresult FetchService::FetchInstance::Initialize(FetchArgs&& aArgs) { MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!aArgs.is() && mArgs.is()); mArgs = std::move(aArgs); // Get needed information for FetchDriver from passed-in channel. if (mArgs.is()) { mRequest = mArgs.as().mRequest.clonePtr(); mArgsType = FetchArgsType::NavigationPreload; nsIChannel* channel = mArgs.as().mChannel; FETCH_LOG(("FetchInstance::Initialize [%p] request[%p], channel[%p]", this, mRequest.unsafeGetRawPtr(), channel)); nsresult rv; nsCOMPtr loadInfo = channel->LoadInfo(); MOZ_ASSERT(loadInfo); nsCOMPtr channelURI; rv = channel->GetURI(getter_AddRefs(channelURI)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsIScriptSecurityManager* securityManager = nsContentUtils::GetSecurityManager(); if (securityManager) { securityManager->GetChannelResultPrincipal(channel, getter_AddRefs(mPrincipal)); } if (!mPrincipal) { return NS_ERROR_UNEXPECTED; } // Get loadGroup from channel rv = channel->GetLoadGroup(getter_AddRefs(mLoadGroup)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!mLoadGroup) { rv = NS_NewLoadGroup(getter_AddRefs(mLoadGroup), mPrincipal); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } // Get CookieJarSettings from channel rv = loadInfo->GetCookieJarSettings(getter_AddRefs(mCookieJarSettings)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Get PerformanceStorage from channel mPerformanceStorage = loadInfo->GetPerformanceStorage(); } else if (mArgs.is()) { mArgsType = FetchArgsType::MainThreadFetch; mRequest = mArgs.as().mRequest.clonePtr(); FETCH_LOG(("FetchInstance::Initialize [%p] request[%p]", this, mRequest.unsafeGetRawPtr())); auto principalOrErr = PrincipalInfoToPrincipal( mArgs.as().mPrincipalInfo); if (principalOrErr.isErr()) { return principalOrErr.unwrapErr(); } mPrincipal = principalOrErr.unwrap(); nsresult rv = NS_NewLoadGroup(getter_AddRefs(mLoadGroup), mPrincipal); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (mArgs.as().mCookieJarSettings.isSome()) { net::CookieJarSettings::Deserialize( mArgs.as().mCookieJarSettings.ref(), getter_AddRefs(mCookieJarSettings)); } return NS_OK; } else { mRequest = mArgs.as().mRequest.clonePtr(); mArgsType = FetchArgsType::WorkerFetch; FETCH_LOG(("FetchInstance::Initialize [%p] request[%p]", this, mRequest.unsafeGetRawPtr())); auto principalOrErr = PrincipalInfoToPrincipal(mArgs.as().mPrincipalInfo); if (principalOrErr.isErr()) { return principalOrErr.unwrapErr(); } mPrincipal = principalOrErr.unwrap(); nsresult rv = NS_NewLoadGroup(getter_AddRefs(mLoadGroup), mPrincipal); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (mArgs.as().mCookieJarSettings.isSome()) { net::CookieJarSettings::Deserialize( mArgs.as().mCookieJarSettings.ref(), getter_AddRefs(mCookieJarSettings)); } } return NS_OK; } RefPtr FetchService::FetchInstance::Fetch() { MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mPrincipal); MOZ_ASSERT(mLoadGroup); nsAutoCString principalSpec; MOZ_ALWAYS_SUCCEEDS(mPrincipal->GetAsciiSpec(principalSpec)); nsAutoCString requestURL; mRequest->GetURL(requestURL); FETCH_LOG(("FetchInstance::Fetch [%p], mRequest URL: %s mPrincipal: %s", this, requestURL.BeginReading(), principalSpec.BeginReading())); nsresult rv; if (mRequest->GetKeepalive()) { nsAutoCString origin; MOZ_ASSERT(mPrincipal); mPrincipal->GetOrigin(origin); RefPtr fetchService = FetchService::GetInstance(); MOZ_ASSERT(fetchService); if (fetchService->DoesExceedsKeepaliveResourceLimits(origin)) { FETCH_LOG(("FetchInstance::Fetch Keepalive request exceeds limit")); return FetchService::NetworkErrorResponse(NS_ERROR_DOM_ABORT_ERR, mArgs); } fetchService->IncrementKeepAliveRequestCount(origin); } // Create a FetchDriver instance mFetchDriver = MakeRefPtr( mRequest.clonePtr(), // Fetch Request mPrincipal, // Principal mLoadGroup, // LoadGroup GetMainThreadSerialEventTarget(), // MainThreadEventTarget mCookieJarSettings, // CookieJarSettings mPerformanceStorage, // PerformanceStorage // For service workers we set // tracking fetch to false, but for Keepalive // requests from main thread this needs to be // changed. See Bug 1892406 false // IsTrackingFetch ); if (mArgsType == FetchArgsType::WorkerFetch) { auto& args = mArgs.as(); mFetchDriver->SetWorkerScript(args.mWorkerScript); MOZ_ASSERT(args.mClientInfo.isSome()); mFetchDriver->SetClientInfo(args.mClientInfo.ref()); mFetchDriver->SetController(args.mController); if (args.mCSPEventListener) { mFetchDriver->SetCSPEventListener(args.mCSPEventListener); } mFetchDriver->SetAssociatedBrowsingContextID( args.mAssociatedBrowsingContextID); mFetchDriver->SetIsThirdPartyContext(Some(args.mIsThirdPartyContext)); mFetchDriver->SetIsOn3PCBExceptionList(args.mIsOn3PCBExceptionList); } if (mArgsType == FetchArgsType::MainThreadFetch) { auto& args = mArgs.as(); mFetchDriver->SetIsThirdPartyContext(Some(args.mIsThirdPartyContext)); } mFetchDriver->EnableNetworkInterceptControl(); mPromises = MakeRefPtr(); // Call FetchDriver::Fetch to start fetching. // Pass AbortSignalImpl as nullptr since we no need support AbortSignalImpl // with FetchService. AbortSignalImpl related information should be passed // through PFetch or InterceptedHttpChannel, then call // FetchService::CancelFetch() to abort the running fetch. rv = mFetchDriver->Fetch(nullptr, this); if (NS_WARN_IF(NS_FAILED(rv))) { FETCH_LOG( ("FetchInstance::Fetch FetchDriver::Fetch failed(0x%X)", (uint32_t)rv)); return FetchService::NetworkErrorResponse(rv, mArgs); } return mPromises; } bool FetchService::FetchInstance::IsLocalHostFetch() const { if (!mPrincipal) { return false; } bool res; nsresult rv = mPrincipal->GetIsLoopbackHost(&res); if (NS_WARN_IF(NS_FAILED(rv))) { return false; } return res; } void FetchService::FetchInstance::Cancel(bool aForceAbort) { MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(NS_IsMainThread()); FETCH_LOG(("FetchInstance::Cancel() [%p]", this)); // If mFetchDriver is not null here, FetchInstance::Fetch() has already // started, let mFetchDriver::RunAbortAlgorithm() to call // FetchInstance::OnResponseEnd() to resolve the pending promises. // Otherwise, resolving the pending promises here. if (mFetchDriver) { // if keepalive is active and it is NOT user initiated Abort, then // do not cancel the request. if (mRequest->GetKeepalive() && !aForceAbort) { FETCH_LOG(("Cleaning up the worker for keepalive[%p]", this)); MOZ_ASSERT(mArgs.is()); if (mArgs.is()) { // delete the actors for cleanup for worker keep-alive requests. // Non-worker keepalive requests need actors to be active until request // completion, because we update request quota per load-group in // FetchChild::ActorDestroy. MOZ_ASSERT((mArgs.as().mFetchParentPromise)); if (mArgs.as().mResponseEndPromiseHolder.Exists()) { FETCH_LOG( ("FetchInstance::Cancel() [%p] mResponseEndPromiseHolder exists", this)); mArgs.as().mResponseEndPromiseHolder.Disconnect(); // the parent promise resolution leads to deleting of actors // mActorDying prevents further access to FetchParent mActorDying = true; mArgs.as().mFetchParentPromise->Resolve(true, __func__); } } return; } mFetchDriver->RunAbortAlgorithm(); return; } MOZ_ASSERT(mPromises); mPromises->ResolveResponseAvailablePromise( InternalResponse::NetworkError(NS_ERROR_DOM_ABORT_ERR), __func__); mPromises->ResolveResponseTimingPromise(ResponseTiming(), __func__); mPromises->ResolveResponseEndPromise( ResponseEndArgs(FetchDriverObserver::eAborted), __func__); } void FetchService::FetchInstance::OnResponseEnd( FetchDriverObserver::EndReason aReason, JS::Handle aReasonDetails) { FETCH_LOG(("FetchInstance::OnResponseEnd [%p] %s", this, aReason == eAborted ? "eAborted" : "eNetworking")); if (mRequest->GetKeepalive()) { nsAutoCString origin; MOZ_ASSERT(mPrincipal); mPrincipal->GetOrigin(origin); RefPtr fetchService = FetchService::GetInstance(); fetchService->DecrementKeepAliveRequestCount(origin); } MOZ_ASSERT(mRequest); if (mArgsType != FetchArgsType::NavigationPreload) { FlushConsoleReport(); nsCOMPtr r = NS_NewRunnableFunction( __func__, [endArgs = ResponseEndArgs(aReason), actorID = GetActorID()]() { FETCH_LOG(("FetchInstance::OnResponseEnd, Runnable")); RefPtr actor = FetchParent::GetActorByID(actorID); if (actor) { actor->OnResponseEnd(std::move(endArgs)); } }); MOZ_ALWAYS_SUCCEEDS( GetBackgroundEventTarget()->Dispatch(r, nsIThread::DISPATCH_NORMAL)); } MOZ_ASSERT(mPromises); if (mArgs.is() && mArgs.as().mResponseEndPromiseHolder.Exists()) { mArgs.as().mResponseEndPromiseHolder.Complete(); } if (aReason == eAborted) { // If ResponseAvailablePromise has not resolved yet, resolved with // NS_ERROR_DOM_ABORT_ERR response. If the promise is already resolved, // this will have no effect. mPromises->ResolveResponseAvailablePromise( InternalResponse::NetworkError(NS_ERROR_DOM_ABORT_ERR), __func__); // If ResponseTimingPromise has not resolved yet, resolved with empty // ResponseTiming. If the promise is already resolved, this has no effect. mPromises->ResolveResponseTimingPromise(ResponseTiming(), __func__); // Resolve the ResponseEndPromise mPromises->ResolveResponseEndPromise(ResponseEndArgs(aReason), __func__); return; } MOZ_ASSERT(mPromises->IsResponseAvailablePromiseResolved() && mPromises->IsResponseTimingPromiseResolved()); // Resolve the ResponseEndPromise mPromises->ResolveResponseEndPromise(ResponseEndArgs(aReason), __func__); // Remove the FetchInstance from FetchInstanceTable RefPtr fetchService = FetchService::GetInstance(); MOZ_ASSERT(fetchService); auto entry = fetchService->mFetchInstanceTable.Lookup(mPromises); if (entry) { entry.Remove(); FETCH_LOG( ("FetchInstance::OnResponseEnd entry of responsePromise[%p] is " "removed", mPromises.get())); } } void FetchService::FetchInstance::OnResponseAvailableInternal( SafeRefPtr aResponse) { FETCH_LOG(("FetchInstance::OnResponseAvailableInternal [%p]", this)); mResponse = std::move(aResponse); nsCOMPtr body; mResponse->GetUnfilteredBody(getter_AddRefs(body)); FETCH_LOG( ("FetchInstance::OnResponseAvailableInternal [%p] response body: %p", this, body.get())); MOZ_ASSERT(mRequest); if (mArgsType != FetchArgsType::NavigationPreload && !mActorDying) { nsCOMPtr r = NS_NewRunnableFunction( __func__, [response = mResponse.clonePtr(), actorID = GetActorID()]() mutable { FETCH_LOG(("FetchInstance::OnResponseAvailableInternal Runnable")); RefPtr actor = FetchParent::GetActorByID(actorID); if (actor) { actor->OnResponseAvailableInternal(std::move(response)); } }); MOZ_ALWAYS_SUCCEEDS( GetBackgroundEventTarget()->Dispatch(r, nsIThread::DISPATCH_NORMAL)); } MOZ_ASSERT(mPromises); // Resolve the ResponseAvailablePromise mPromises->ResolveResponseAvailablePromise(mResponse.clonePtr(), __func__); } bool FetchService::FetchInstance::NeedOnDataAvailable() { if (mArgs.is()) { return mArgs.as().mNeedOnDataAvailable; } if (mArgs.is()) { return mArgs.as().mNeedOnDataAvailable; } return false; } void FetchService::FetchInstance::OnDataAvailable() { FETCH_LOG(("FetchInstance::OnDataAvailable [%p]", this)); if (!NeedOnDataAvailable()) { return; } MOZ_ASSERT(mRequest); if (mArgsType != FetchArgsType::NavigationPreload && !mActorDying) { nsCOMPtr r = NS_NewRunnableFunction(__func__, [actorID = GetActorID()]() { FETCH_LOG(("FetchInstance::OnDataAvailable, Runnable")); RefPtr actor = FetchParent::GetActorByID(actorID); if (actor) { actor->OnDataAvailable(); } }); MOZ_ALWAYS_SUCCEEDS( GetBackgroundEventTarget()->Dispatch(r, nsIThread::DISPATCH_NORMAL)); } } void FetchService::FetchInstance::FlushConsoleReport() { FETCH_LOG(("FetchInstance::FlushConsoleReport [%p]", this)); if (mArgsType != FetchArgsType::NavigationPreload && !mActorDying) { if (!mReporter) { return; } nsTArray reports; mReporter->StealConsoleReports(reports); nsCOMPtr r = NS_NewRunnableFunction( __func__, [actorID = GetActorID(), consoleReports = std::move(reports)]() { FETCH_LOG(("FetchInstance::FlushConsolReport, Runnable")); RefPtr actor = FetchParent::GetActorByID(actorID); if (actor) { actor->OnFlushConsoleReport(std::move(consoleReports)); } }); MOZ_ALWAYS_SUCCEEDS( GetBackgroundEventTarget()->Dispatch(r, nsIThread::DISPATCH_NORMAL)); } } void FetchService::FetchInstance::OnReportPerformanceTiming() { FETCH_LOG(("FetchInstance::OnReportPerformanceTiming [%p]", this)); MOZ_ASSERT(mFetchDriver); MOZ_ASSERT(mPromises); if (mPromises->IsResponseTimingPromiseResolved()) { return; } ResponseTiming timing; UniquePtr performanceTiming( mFetchDriver->GetPerformanceTimingData(timing.initiatorType(), timing.entryName())); // FetchDriver has no corresponding performance timing when fetch() failed. // Resolve the ResponseTimingPromise with empty timing. if (!performanceTiming) { mPromises->ResolveResponseTimingPromise(ResponseTiming(), __func__); return; } timing.timingData() = performanceTiming->ToIPC(); // Force replace initiatorType for ServiceWorkerNavgationPreload. if (mArgsType == FetchArgsType::NavigationPreload) { timing.initiatorType() = u"navigation"_ns; } else if (!mActorDying) { nsCOMPtr r = NS_NewRunnableFunction( __func__, [actorID = GetActorID(), timing = timing]() { FETCH_LOG(("FetchInstance::OnReportPerformanceTiming, Runnable")); RefPtr actor = FetchParent::GetActorByID(actorID); if (actor) { actor->OnReportPerformanceTiming(std::move(timing)); } }); MOZ_ALWAYS_SUCCEEDS( GetBackgroundEventTarget()->Dispatch(r, nsIThread::DISPATCH_NORMAL)); } mPromises->ResolveResponseTimingPromise(std::move(timing), __func__); } void FetchService::FetchInstance::OnNotifyNetworkMonitorAlternateStack( uint64_t aChannelID) { FETCH_LOG(("FetchInstance::OnNotifyNetworkMonitorAlternateStack [%p]", this)); MOZ_ASSERT(mFetchDriver); MOZ_ASSERT(mPromises); if (mArgsType != FetchArgsType::WorkerFetch) { // We need to support this for Main thread fetch requests as well // See Bug 1897129 return; } nsCOMPtr r = NS_NewRunnableFunction( __func__, [actorID = mArgs.as().mActorID, channelID = aChannelID]() { FETCH_LOG( ("FetchInstance::NotifyNetworkMonitorAlternateStack, Runnable")); RefPtr actor = FetchParent::GetActorByID(actorID); if (actor) { actor->OnNotifyNetworkMonitorAlternateStack(channelID); } }); MOZ_ALWAYS_SUCCEEDS(mArgs.as().mEventTarget->Dispatch( r, nsIThread::DISPATCH_NORMAL)); } nsID FetchService::FetchInstance::GetActorID() { if (mArgsType == FetchArgsType::WorkerFetch) { return mArgs.as().mActorID; } if (mArgsType == FetchArgsType::MainThreadFetch) { return mArgs.as().mActorID; } MOZ_ASSERT_UNREACHABLE("GetActorID called for unexpected mArgsType"); return {}; } nsCOMPtr FetchService::FetchInstance::GetBackgroundEventTarget() { if (mArgsType == FetchArgsType::WorkerFetch) { return mArgs.as().mEventTarget; } if (mArgsType == FetchArgsType::MainThreadFetch) { return mArgs.as().mEventTarget; } MOZ_ASSERT_UNREACHABLE( "GetBackgroundEventTarget called for unexpected mArgsType"); return {}; } // FetchService NS_IMPL_ISUPPORTS(FetchService, nsIObserver) StaticRefPtr gInstance; /*static*/ already_AddRefed FetchService::GetInstance() { MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(NS_IsMainThread()); if (!gInstance) { gInstance = MakeRefPtr(); nsresult rv = gInstance->RegisterNetworkObserver(); if (NS_WARN_IF(NS_FAILED(rv))) { gInstance = nullptr; return nullptr; } ClearOnShutdown(&gInstance); } RefPtr service = gInstance; return service.forget(); } /*static*/ RefPtr FetchService::NetworkErrorResponse( nsresult aRv, const FetchArgs& aArgs) { if (aArgs.is()) { const WorkerFetchArgs& args = aArgs.as(); nsCOMPtr r = NS_NewRunnableFunction( __func__, [aRv, actorID = args.mActorID]() mutable { FETCH_LOG( ("FetchService::PropagateErrorResponse runnable aError: 0x%X", (uint32_t)aRv)); RefPtr actor = FetchParent::GetActorByID(actorID); if (actor) { actor->OnResponseAvailableInternal( InternalResponse::NetworkError(aRv)); actor->OnResponseEnd( ResponseEndArgs(FetchDriverObserver::eAborted)); } }); MOZ_ALWAYS_SUCCEEDS( args.mEventTarget->Dispatch(r, nsIThread::DISPATCH_NORMAL)); } else if (aArgs.is()) { const MainThreadFetchArgs& args = aArgs.as(); nsCOMPtr r = NS_NewRunnableFunction( __func__, [aRv, actorID = args.mActorID]() mutable { FETCH_LOG( ("FetchService::PropagateErrorResponse runnable aError: 0x%X", (uint32_t)aRv)); RefPtr actor = FetchParent::GetActorByID(actorID); if (actor) { actor->OnResponseAvailableInternal( InternalResponse::NetworkError(aRv)); actor->OnResponseEnd( ResponseEndArgs(FetchDriverObserver::eAborted)); } }); MOZ_ALWAYS_SUCCEEDS( args.mEventTarget->Dispatch(r, nsIThread::DISPATCH_NORMAL)); } RefPtr promises = MakeRefPtr(); promises->ResolveResponseAvailablePromise(InternalResponse::NetworkError(aRv), __func__); promises->ResolveResponseTimingPromise(ResponseTiming(), __func__); promises->ResolveResponseEndPromise( ResponseEndArgs(FetchDriverObserver::eAborted), __func__); return promises; } FetchService::FetchService() { MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(NS_IsMainThread()); } FetchService::~FetchService() { MOZ_ALWAYS_SUCCEEDS(UnregisterNetworkObserver()); } nsresult FetchService::RegisterNetworkObserver() { AssertIsOnMainThread(); nsCOMPtr observerService = services::GetObserverService(); if (!observerService) { return NS_ERROR_UNEXPECTED; } nsCOMPtr ioService = services::GetIOService(); if (!ioService) { return NS_ERROR_UNEXPECTED; } nsresult rv = observerService->AddObserver( this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC, false); NS_ENSURE_SUCCESS(rv, rv); rv = observerService->AddObserver(this, "xpcom-shutdown", false); NS_ENSURE_SUCCESS(rv, rv); rv = ioService->GetOffline(&mOffline); NS_ENSURE_SUCCESS(rv, rv); mObservingNetwork = true; return NS_OK; } nsresult FetchService::UnregisterNetworkObserver() { AssertIsOnMainThread(); nsresult rv; if (mObservingNetwork) { nsCOMPtr observerService = mozilla::services::GetObserverService(); if (observerService) { rv = observerService->RemoveObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC); NS_ENSURE_SUCCESS(rv, rv); rv = observerService->RemoveObserver(this, "xpcom-shutdown"); NS_ENSURE_SUCCESS(rv, rv); } mObservingNetwork = false; } return NS_OK; } void FetchService::IncrementKeepAliveRequestCount(const nsACString& aOrigin) { MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(NS_IsMainThread()); FETCH_LOG(("FetchService::IncrementKeepAliveRequestCount [origin=%s]\n", PromiseFlatCString(aOrigin).get())); ++mTotalKeepAliveRequests; uint32_t count = mPendingKeepAliveRequestsPerOrigin.Get(aOrigin) + 1; mPendingKeepAliveRequestsPerOrigin.InsertOrUpdate(aOrigin, count); } void FetchService::DecrementKeepAliveRequestCount(const nsACString& aOrigin) { MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(NS_IsMainThread()); FETCH_LOG(("FetchService::DecrementKeepAliveRequestCount [origin=%s]\n", PromiseFlatCString(aOrigin).get())); MOZ_ASSERT(mTotalKeepAliveRequests > 0); if (mTotalKeepAliveRequests) { --mTotalKeepAliveRequests; } uint32_t count = mPendingKeepAliveRequestsPerOrigin.Get(aOrigin); MOZ_ASSERT(count > 0); if (count) { --count; if (count == 0) { mPendingKeepAliveRequestsPerOrigin.Remove(aOrigin); } else { mPendingKeepAliveRequestsPerOrigin.InsertOrUpdate(aOrigin, count); } } } bool FetchService::DoesExceedsKeepaliveResourceLimits( const nsACString& origin) { if (mTotalKeepAliveRequests >= StaticPrefs::dom_fetchKeepalive_total_request_limit()) { // Count keep-alive request discards due to // exceeding the total keep-alive request limit. mozilla::glean::networking::fetch_keepalive_discard_count .Get("total_keepalive_limit"_ns) .Add(1); return true; } if (mPendingKeepAliveRequestsPerOrigin.Get(origin) >= StaticPrefs::dom_fetchKeepalive_request_limit_per_origin()) { // Count keep-alive request discards due to // exceeding the per-origin request limit. mozilla::glean::networking::fetch_keepalive_discard_count .Get("per_origin_limit"_ns) .Add(1); return true; } return false; } NS_IMETHODIMP FetchService::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { FETCH_LOG(("FetchService::Observe topic: %s", aTopic)); AssertIsOnMainThread(); MOZ_ASSERT(!strcmp(aTopic, NS_IOSERVICE_OFFLINE_STATUS_TOPIC) || !strcmp(aTopic, "xpcom-shutdown")); if (!strcmp(aTopic, "xpcom-shutdown")) { // Going to shutdown, unregister the network status observer to avoid // receiving nsresult rv = UnregisterNetworkObserver(); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } if (nsDependentString(aData).EqualsLiteral(NS_IOSERVICE_ONLINE)) { mOffline = false; } else { mOffline = true; // Network is offline, cancel the running fetch that is not to local server. mFetchInstanceTable.RemoveIf([](auto& entry) { bool res = entry.Data()->IsLocalHostFetch(); if (res) { return false; } entry.Data()->Cancel(true); return true; }); } return NS_OK; } RefPtr FetchService::Fetch(FetchArgs&& aArgs) { MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(NS_IsMainThread()); FETCH_LOG(("FetchService::Fetch (%s)", aArgs.is() ? "NavigationPreload" : "WorkerFetch")); // Create FetchInstance RefPtr fetch = MakeRefPtr(); // Call FetchInstance::Initialize() to get needed information for // FetchDriver nsresult rv = fetch->Initialize(std::move(aArgs)); if (NS_WARN_IF(NS_FAILED(rv))) { return NetworkErrorResponse(rv, fetch->Args()); } if (mOffline && !fetch->IsLocalHostFetch()) { FETCH_LOG(("FetchService::Fetch network offline")); return NetworkErrorResponse(NS_ERROR_OFFLINE, fetch->Args()); } // Call FetchInstance::Fetch() to start an asynchronous fetching. RefPtr promises = fetch->Fetch(); MOZ_ASSERT(promises); if (!promises->IsResponseAvailablePromiseResolved()) { // Insert the created FetchInstance into FetchInstanceTable. if (!mFetchInstanceTable.WithEntryHandle(promises, [&](auto&& entry) { if (entry.HasEntry()) { return false; } entry.Insert(fetch); return true; })) { FETCH_LOG( ("FetchService::Fetch entry[%p] already exists", promises.get())); return NetworkErrorResponse(NS_ERROR_UNEXPECTED, fetch->Args()); } FETCH_LOG(("FetchService::Fetch entry[%p] of FetchInstance[%p] added", promises.get(), fetch.get())); } return promises; } void FetchService::CancelFetch(const RefPtr&& aPromises, bool aForceAbort) { MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aPromises); FETCH_LOG(("FetchService::CancelFetch aPromises[%p]", aPromises.get())); auto entry = mFetchInstanceTable.Lookup(aPromises); if (entry) { // Notice any modifications here before entry.Remove() probably should be // reflected to Observe() offline case. entry.Data()->Cancel(aForceAbort); entry.Remove(); FETCH_LOG( ("FetchService::CancelFetch entry [%p] removed", aPromises.get())); } } MozPromiseRequestHolder& FetchService::GetResponseEndPromiseHolder( const RefPtr& aPromises) { MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aPromises); auto entry = mFetchInstanceTable.Lookup(aPromises); MOZ_ASSERT(entry); return entry.Data()->GetResponseEndPromiseHolder(); } } // namespace mozilla::dom