Backed out changeset 77b4cc920fe4 (bug 1915355) Backed out changeset da9446e06954 (bug 1915355) Backed out changeset 20708fe16d2a (bug 1915355) Backed out changeset 7c4b003d1e48 (bug 1915355) Backed out changeset 36163490219b (bug 1915355) Backed out changeset 8f867025b79b (bug 1915355) Backed out changeset 3d647f81adb9 (bug 1915355)
918 lines
31 KiB
C++
918 lines
31 KiB
C++
/* 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/GleanMetrics.h"
|
|
#include "mozilla/ipc/BackgroundUtils.h"
|
|
#include "mozilla/net/CookieJarSettings.h"
|
|
|
|
namespace mozilla::dom {
|
|
|
|
mozilla::LazyLogModule gFetchLog("Fetch");
|
|
|
|
// FetchServicePromises
|
|
|
|
FetchServicePromises::FetchServicePromises()
|
|
: mAvailablePromise(
|
|
MakeRefPtr<FetchServiceResponseAvailablePromise::Private>(__func__)),
|
|
mTimingPromise(
|
|
MakeRefPtr<FetchServiceResponseTimingPromise::Private>(__func__)),
|
|
mEndPromise(
|
|
MakeRefPtr<FetchServiceResponseEndPromise::Private>(__func__)) {
|
|
mAvailablePromise->UseSynchronousTaskDispatch(__func__);
|
|
mTimingPromise->UseSynchronousTaskDispatch(__func__);
|
|
mEndPromise->UseSynchronousTaskDispatch(__func__);
|
|
}
|
|
|
|
RefPtr<FetchServiceResponseAvailablePromise>
|
|
FetchServicePromises::GetResponseAvailablePromise() {
|
|
return mAvailablePromise;
|
|
}
|
|
|
|
RefPtr<FetchServiceResponseTimingPromise>
|
|
FetchServicePromises::GetResponseTimingPromise() {
|
|
return mTimingPromise;
|
|
}
|
|
|
|
RefPtr<FetchServiceResponseEndPromise>
|
|
FetchServicePromises::GetResponseEndPromise() {
|
|
return mEndPromise;
|
|
}
|
|
|
|
void FetchServicePromises::ResolveResponseAvailablePromise(
|
|
FetchServiceResponse&& aResponse, StaticString aMethodName) {
|
|
if (mAvailablePromise) {
|
|
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) {
|
|
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) {
|
|
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<UnknownArgs>() && mArgs.is<UnknownArgs>());
|
|
|
|
mArgs = std::move(aArgs);
|
|
|
|
// Get needed information for FetchDriver from passed-in channel.
|
|
if (mArgs.is<NavigationPreloadArgs>()) {
|
|
mRequest = mArgs.as<NavigationPreloadArgs>().mRequest.clonePtr();
|
|
mArgsType = FetchArgsType::NavigationPreload;
|
|
nsIChannel* channel = mArgs.as<NavigationPreloadArgs>().mChannel;
|
|
FETCH_LOG(("FetchInstance::Initialize [%p] request[%p], channel[%p]", this,
|
|
mRequest.unsafeGetRawPtr(), channel));
|
|
|
|
nsresult rv;
|
|
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
|
|
MOZ_ASSERT(loadInfo);
|
|
|
|
nsCOMPtr<nsIURI> 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<MainThreadFetchArgs>()) {
|
|
mArgsType = FetchArgsType::MainThreadFetch;
|
|
|
|
mRequest = mArgs.as<MainThreadFetchArgs>().mRequest.clonePtr();
|
|
|
|
FETCH_LOG(("FetchInstance::Initialize [%p] request[%p]", this,
|
|
mRequest.unsafeGetRawPtr()));
|
|
|
|
auto principalOrErr = PrincipalInfoToPrincipal(
|
|
mArgs.as<MainThreadFetchArgs>().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<MainThreadFetchArgs>().mCookieJarSettings.isSome()) {
|
|
net::CookieJarSettings::Deserialize(
|
|
mArgs.as<MainThreadFetchArgs>().mCookieJarSettings.ref(),
|
|
getter_AddRefs(mCookieJarSettings));
|
|
}
|
|
|
|
return NS_OK;
|
|
|
|
} else {
|
|
mRequest = mArgs.as<WorkerFetchArgs>().mRequest.clonePtr();
|
|
mArgsType = FetchArgsType::WorkerFetch;
|
|
|
|
FETCH_LOG(("FetchInstance::Initialize [%p] request[%p]", this,
|
|
mRequest.unsafeGetRawPtr()));
|
|
|
|
auto principalOrErr =
|
|
PrincipalInfoToPrincipal(mArgs.as<WorkerFetchArgs>().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<WorkerFetchArgs>().mCookieJarSettings.isSome()) {
|
|
net::CookieJarSettings::Deserialize(
|
|
mArgs.as<WorkerFetchArgs>().mCookieJarSettings.ref(),
|
|
getter_AddRefs(mCookieJarSettings));
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
RefPtr<FetchServicePromises> 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 = 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<FetchDriver>(
|
|
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<WorkerFetchArgs>();
|
|
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->SetIsThirdPartyWorker(Some(args.mIsThirdPartyContext));
|
|
}
|
|
|
|
mFetchDriver->EnableNetworkInterceptControl();
|
|
mPromises = MakeRefPtr<FetchServicePromises>();
|
|
|
|
// 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<WorkerFetchArgs>());
|
|
if (mArgs.is<WorkerFetchArgs>()) {
|
|
// 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<WorkerFetchArgs>().mFetchParentPromise));
|
|
if (mArgs.as<WorkerFetchArgs>().mResponseEndPromiseHolder.Exists()) {
|
|
FETCH_LOG(
|
|
("FetchInstance::Cancel() [%p] mResponseEndPromiseHolder exists",
|
|
this));
|
|
|
|
mArgs.as<WorkerFetchArgs>().mResponseEndPromiseHolder.Disconnect();
|
|
|
|
MOZ_ASSERT(
|
|
!mArgs.as<WorkerFetchArgs>().mFetchParentPromise->IsResolved());
|
|
if (!mArgs.as<WorkerFetchArgs>().mFetchParentPromise->IsResolved()) {
|
|
// the parent promise resolution leads to deleting of actors
|
|
// mActorDying prevents further access to FetchParent
|
|
mActorDying = true;
|
|
mArgs.as<WorkerFetchArgs>().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<JS::Value> 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 = FetchService::GetInstance();
|
|
fetchService->DecrementKeepAliveRequestCount(origin);
|
|
}
|
|
|
|
MOZ_ASSERT(mRequest);
|
|
if (mArgsType != FetchArgsType::NavigationPreload) {
|
|
FlushConsoleReport();
|
|
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
|
|
__func__,
|
|
[endArgs = ResponseEndArgs(aReason), actorID = GetActorID()]() {
|
|
FETCH_LOG(("FetchInstance::OnResponseEnd, Runnable"));
|
|
RefPtr<FetchParent> 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<WorkerFetchArgs>() &&
|
|
mArgs.as<WorkerFetchArgs>().mResponseEndPromiseHolder.Exists()) {
|
|
mArgs.as<WorkerFetchArgs>().mResponseEndPromiseHolder.Complete();
|
|
}
|
|
|
|
if (aReason == eAborted) {
|
|
// If ResponseAvailablePromise has not resolved yet, resolved with
|
|
// NS_ERROR_DOM_ABORT_ERR response.
|
|
if (!mPromises->GetResponseAvailablePromise()->IsResolved()) {
|
|
mPromises->ResolveResponseAvailablePromise(
|
|
InternalResponse::NetworkError(NS_ERROR_DOM_ABORT_ERR), __func__);
|
|
}
|
|
|
|
// If ResponseTimingPromise has not resolved yet, resolved with empty
|
|
// ResponseTiming.
|
|
if (!mPromises->GetResponseTimingPromise()->IsResolved()) {
|
|
mPromises->ResolveResponseTimingPromise(ResponseTiming(), __func__);
|
|
}
|
|
// Resolve the ResponseEndPromise
|
|
mPromises->ResolveResponseEndPromise(ResponseEndArgs(aReason), __func__);
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(mPromises->GetResponseAvailablePromise()->IsResolved() &&
|
|
mPromises->GetResponseTimingPromise()->IsResolved());
|
|
|
|
// Resolve the ResponseEndPromise
|
|
mPromises->ResolveResponseEndPromise(ResponseEndArgs(aReason), __func__);
|
|
|
|
// Remove the FetchInstance from FetchInstanceTable
|
|
RefPtr<FetchService> 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<InternalResponse> aResponse) {
|
|
FETCH_LOG(("FetchInstance::OnResponseAvailableInternal [%p]", this));
|
|
mResponse = std::move(aResponse);
|
|
|
|
nsCOMPtr<nsIInputStream> 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<nsIRunnable> r = NS_NewRunnableFunction(
|
|
__func__,
|
|
[response = mResponse.clonePtr(), actorID = GetActorID()]() mutable {
|
|
FETCH_LOG(("FetchInstance::OnResponseAvailableInternal Runnable"));
|
|
RefPtr<FetchParent> 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<WorkerFetchArgs>()) {
|
|
return mArgs.as<WorkerFetchArgs>().mNeedOnDataAvailable;
|
|
}
|
|
|
|
if (mArgs.is<MainThreadFetchArgs>()) {
|
|
return mArgs.as<MainThreadFetchArgs>().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<nsIRunnable> r =
|
|
NS_NewRunnableFunction(__func__, [actorID = GetActorID()]() {
|
|
FETCH_LOG(("FetchInstance::OnDataAvailable, Runnable"));
|
|
RefPtr<FetchParent> 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<net::ConsoleReportCollected> reports;
|
|
mReporter->StealConsoleReports(reports);
|
|
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
|
|
__func__,
|
|
[actorID = GetActorID(), consoleReports = std::move(reports)]() {
|
|
FETCH_LOG(("FetchInstance::FlushConsolReport, Runnable"));
|
|
RefPtr<FetchParent> 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->GetResponseTimingPromise()->IsResolved()) {
|
|
return;
|
|
}
|
|
|
|
ResponseTiming timing;
|
|
UniquePtr<PerformanceTimingData> 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<nsIRunnable> r = NS_NewRunnableFunction(
|
|
__func__, [actorID = GetActorID(), timing = timing]() {
|
|
FETCH_LOG(("FetchInstance::OnReportPerformanceTiming, Runnable"));
|
|
RefPtr<FetchParent> 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<nsIRunnable> r = NS_NewRunnableFunction(
|
|
__func__, [actorID = mArgs.as<WorkerFetchArgs>().mActorID,
|
|
channelID = aChannelID]() {
|
|
FETCH_LOG(
|
|
("FetchInstance::NotifyNetworkMonitorAlternateStack, Runnable"));
|
|
RefPtr<FetchParent> actor = FetchParent::GetActorByID(actorID);
|
|
if (actor) {
|
|
actor->OnNotifyNetworkMonitorAlternateStack(channelID);
|
|
}
|
|
});
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(mArgs.as<WorkerFetchArgs>().mEventTarget->Dispatch(
|
|
r, nsIThread::DISPATCH_NORMAL));
|
|
}
|
|
|
|
nsID FetchService::FetchInstance::GetActorID() {
|
|
if (mArgsType == FetchArgsType::WorkerFetch) {
|
|
return mArgs.as<WorkerFetchArgs>().mActorID;
|
|
}
|
|
|
|
if (mArgsType == FetchArgsType::MainThreadFetch) {
|
|
return mArgs.as<MainThreadFetchArgs>().mActorID;
|
|
}
|
|
|
|
MOZ_ASSERT_UNREACHABLE("GetActorID called for unexpected mArgsType");
|
|
|
|
return {};
|
|
}
|
|
|
|
nsCOMPtr<nsISerialEventTarget>
|
|
FetchService::FetchInstance::GetBackgroundEventTarget() {
|
|
if (mArgsType == FetchArgsType::WorkerFetch) {
|
|
return mArgs.as<WorkerFetchArgs>().mEventTarget;
|
|
}
|
|
|
|
if (mArgsType == FetchArgsType::MainThreadFetch) {
|
|
return mArgs.as<MainThreadFetchArgs>().mEventTarget;
|
|
}
|
|
|
|
MOZ_ASSERT_UNREACHABLE(
|
|
"GetBackgroundEventTarget called for unexpected mArgsType");
|
|
|
|
return {};
|
|
}
|
|
|
|
// FetchService
|
|
|
|
NS_IMPL_ISUPPORTS(FetchService, nsIObserver)
|
|
|
|
StaticRefPtr<FetchService> gInstance;
|
|
|
|
/*static*/
|
|
already_AddRefed<FetchService> FetchService::GetInstance() {
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!gInstance) {
|
|
gInstance = MakeRefPtr<FetchService>();
|
|
nsresult rv = gInstance->RegisterNetworkObserver();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
gInstance = nullptr;
|
|
return nullptr;
|
|
}
|
|
ClearOnShutdown(&gInstance);
|
|
}
|
|
RefPtr<FetchService> service = gInstance;
|
|
return service.forget();
|
|
}
|
|
|
|
/*static*/
|
|
RefPtr<FetchServicePromises> FetchService::NetworkErrorResponse(
|
|
nsresult aRv, const FetchArgs& aArgs) {
|
|
if (aArgs.is<WorkerFetchArgs>()) {
|
|
const WorkerFetchArgs& args = aArgs.as<WorkerFetchArgs>();
|
|
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
|
|
__func__, [aRv, actorID = args.mActorID]() mutable {
|
|
FETCH_LOG(
|
|
("FetchService::PropagateErrorResponse runnable aError: 0x%X",
|
|
(uint32_t)aRv));
|
|
RefPtr<FetchParent> 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<MainThreadFetchArgs>()) {
|
|
const MainThreadFetchArgs& args = aArgs.as<MainThreadFetchArgs>();
|
|
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
|
|
__func__, [aRv, actorID = args.mActorID]() mutable {
|
|
FETCH_LOG(
|
|
("FetchService::PropagateErrorResponse runnable aError: 0x%X",
|
|
(uint32_t)aRv));
|
|
RefPtr<FetchParent> 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<FetchServicePromises> promises = MakeRefPtr<FetchServicePromises>();
|
|
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<nsIObserverService> observerService = services::GetObserverService();
|
|
if (!observerService) {
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
nsCOMPtr<nsIIOService> 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<nsIObserverService> 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<FetchServicePromises> FetchService::Fetch(FetchArgs&& aArgs) {
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
FETCH_LOG(("FetchService::Fetch (%s)", aArgs.is<NavigationPreloadArgs>()
|
|
? "NavigationPreload"
|
|
: "WorkerFetch"));
|
|
// Create FetchInstance
|
|
RefPtr<FetchInstance> fetch = MakeRefPtr<FetchInstance>();
|
|
|
|
// 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<FetchServicePromises> promises = fetch->Fetch();
|
|
MOZ_ASSERT(promises);
|
|
|
|
if (!promises->GetResponseAvailablePromise()->IsResolved()) {
|
|
// 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<FetchServicePromises>&& 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<FetchServiceResponseEndPromise>&
|
|
FetchService::GetResponseEndPromiseHolder(
|
|
const RefPtr<FetchServicePromises>& 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
|