This patch updates InternalRequest and InternalResponse such that it parses the URL passed to it at creation time, rather than when the channel is created. This should improve the behaviour of these types around Blob URLs, so that extra state, such as the "revoked" flag, is calculated at the correct time as-per spec. While making this change, I noticed that a previous Cache DB migration could insert an "" URL in the DB when migrating from a single response URL to a URL list, rather than storing an empty URL list. The choice was made to do filtering when reading, rather than performing a migration, to avoid migration first-time start-up overhead. Differential Revision: https://phabricator.services.mozilla.com/D276047
910 lines
30 KiB
C++
910 lines
30 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 "mozilla/dom/FetchService.h"
|
|
|
|
#include "FetchLog.h"
|
|
#include "FetchParent.h"
|
|
#include "mozilla/BasePrincipal.h"
|
|
#include "mozilla/ClearOnShutdown.h"
|
|
#include "mozilla/SchedulerGroup.h"
|
|
#include "mozilla/UniquePtr.h"
|
|
#include "mozilla/dom/ClientInfo.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/ipc/BackgroundUtils.h"
|
|
#include "mozilla/net/CookieJarSettings.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsIContentSecurityPolicy.h"
|
|
#include "nsICookieJarSettings.h"
|
|
#include "nsIIOService.h"
|
|
#include "nsILoadGroup.h"
|
|
#include "nsILoadInfo.h"
|
|
#include "nsIObserverService.h"
|
|
#include "nsIPrincipal.h"
|
|
#include "nsIScriptSecurityManager.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "nsXULAppAPI.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));
|
|
nsCOMPtr<nsIURI> requestURL = mRequest->GetURL();
|
|
FETCH_LOG(("FetchInstance::Fetch [%p], mRequest URL: %s mPrincipal: %s", this,
|
|
requestURL->GetSpecOrDefault().get(), principalSpec.get()));
|
|
|
|
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
|
|
net::ClassificationFlags({0, 0}) // TrackingFlags
|
|
);
|
|
|
|
if (mArgsType == FetchArgsType::WorkerFetch) {
|
|
auto& args = mArgs.as<WorkerFetchArgs>();
|
|
mFetchDriver->SetWorkerScript(args.mWorkerScript);
|
|
mFetchDriver->SetClientInfo(args.mClientInfo);
|
|
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->SetClientInfo(args.mClientInfo);
|
|
mFetchDriver->SetAssociatedBrowsingContextID(
|
|
args.mAssociatedBrowsingContextID);
|
|
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 &&
|
|
mArgsType != FetchArgsType::MainThreadFetch) {
|
|
// Fetch type not supported
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
|
|
__func__, [actorID = GetActorID(), channelID = aChannelID]() {
|
|
FETCH_LOG(
|
|
("FetchInstance::OnNotifyNetworkMonitorAlternateStack, Runnable"));
|
|
RefPtr<FetchParent> actor = FetchParent::GetActorByID(actorID);
|
|
if (actor) {
|
|
actor->OnNotifyNetworkMonitorAlternateStack(channelID);
|
|
}
|
|
});
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
GetBackgroundEventTarget()->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()) {
|
|
return true;
|
|
}
|
|
|
|
if (mPendingKeepAliveRequestsPerOrigin.Get(origin) >=
|
|
StaticPrefs::dom_fetchKeepalive_request_limit_per_origin()) {
|
|
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
|