919 lines
31 KiB
C++
919 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/NetwerkMetrics.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->UseDirectTaskDispatch(__func__);
|
|
mTimingPromise->UseDirectTaskDispatch(__func__);
|
|
mEndPromise->UseDirectTaskDispatch(__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) {
|
|
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<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->SetIsThirdPartyContext(Some(args.mIsThirdPartyContext));
|
|
mFetchDriver->SetIsOn3PCBExceptionList(args.mIsOn3PCBExceptionList);
|
|
}
|
|
|
|
if (mArgsType == FetchArgsType::MainThreadFetch) {
|
|
auto& args = mArgs.as<MainThreadFetchArgs>();
|
|
mFetchDriver->SetIsThirdPartyContext(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();
|
|
|
|
// 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 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 = 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->IsResponseTimingPromiseResolved()) {
|
|
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->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<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
|