diff options
Diffstat (limited to '')
-rw-r--r-- | dom/fetch/FetchService.cpp | 448 |
1 files changed, 448 insertions, 0 deletions
diff --git a/dom/fetch/FetchService.cpp b/dom/fetch/FetchService.cpp new file mode 100644 index 0000000000..ba395030b1 --- /dev/null +++ b/dom/fetch/FetchService.cpp @@ -0,0 +1,448 @@ +/* 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 "nsContentUtils.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/ScopeExit.h" +#include "mozilla/UniquePtr.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/ipc/BackgroundUtils.h" + +namespace mozilla::dom { + +mozilla::LazyLogModule gFetchLog("Fetch"); + +// FetchServicePromises + +FetchServicePromises::FetchServicePromises() + : mAvailablePromise( + new FetchServiceResponseAvailablePromise::Private(__func__)), + mEndPromise(new FetchServiceResponseEndPromise::Private(__func__)) { + mAvailablePromise->UseSynchronousTaskDispatch(__func__); + mEndPromise->UseSynchronousTaskDispatch(__func__); +} + +RefPtr<FetchServiceResponseAvailablePromise> +FetchServicePromises::GetResponseAvailablePromise() { + return mAvailablePromise; +} + +RefPtr<FetchServiceResponseEndPromise> +FetchServicePromises::GetResponseEndPromise() { + return mEndPromise; +} + +void FetchServicePromises::ResolveResponseAvailablePromise( + FetchServiceResponse&& aResponse, const char* aMethodName) { + if (mAvailablePromise) { + mAvailablePromise->Resolve(std::move(aResponse), aMethodName); + } +} + +void FetchServicePromises::RejectResponseAvailablePromise( + const CopyableErrorResult&& aError, const char* aMethodName) { + if (mAvailablePromise) { + mAvailablePromise->Reject(aError, aMethodName); + } +} + +void FetchServicePromises::ResolveResponseEndPromise(ResponseEndArgs&& aArgs, + const char* aMethodName) { + if (mEndPromise) { + mEndPromise->Resolve(std::move(aArgs), aMethodName); + } +} + +void FetchServicePromises::RejectResponseEndPromise( + const CopyableErrorResult&& aError, const char* aMethodName) { + if (mEndPromise) { + mEndPromise->Reject(aError, aMethodName); + } +} + +// FetchInstance + +FetchService::FetchInstance::FetchInstance(SafeRefPtr<InternalRequest> aRequest) + : mRequest(std::move(aRequest)) {} + +FetchService::FetchInstance::~FetchInstance() = default; + +nsresult FetchService::FetchInstance::Initialize(nsIChannel* aChannel) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + + // Get needed information for FetchDriver from passed-in channel. + if (aChannel) { + FETCH_LOG(("FetchInstance::Initialize [%p] aChannel[%p]", this, aChannel)); + + nsresult rv; + nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); + MOZ_ASSERT(loadInfo); + + nsCOMPtr<nsIURI> channelURI; + rv = aChannel->GetURI(getter_AddRefs(channelURI)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsIScriptSecurityManager* securityManager = + nsContentUtils::GetSecurityManager(); + if (securityManager) { + securityManager->GetChannelResultPrincipal(aChannel, + getter_AddRefs(mPrincipal)); + } + + if (!mPrincipal) { + return NS_ERROR_UNEXPECTED; + } + + // Get loadGroup from channel + rv = aChannel->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 { + // TODO: + // Get information from InternalRequest and PFetch IPC parameters. + // This will be implemented in bug 1351231. + return NS_ERROR_NOT_IMPLEMENTED; + } + + 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; + + // Create a FetchDriver instance + mFetchDriver = MakeRefPtr<FetchDriver>( + mRequest.clonePtr(), // Fetch Request + mPrincipal, // Principal + mLoadGroup, // LoadGroup + GetMainThreadEventTarget(), // MainThreadEventTarget + mCookieJarSettings, // CookieJarSettings + mPerformanceStorage, // PerformanceStorage + false // IsTrackingFetch + ); + + // 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); + } + + mPromises = MakeRefPtr<FetchServicePromises>(); + + return mPromises; +} + +void FetchService::FetchInstance::Cancel() { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + + FETCH_LOG(("FetchInstance::Cancel() [%p]", this)); + + if (mFetchDriver) { + mFetchDriver->RunAbortAlgorithm(); + } + + if (mPromises) { + mPromises->ResolveResponseAvailablePromise( + InternalResponse::NetworkError(NS_ERROR_DOM_ABORT_ERR), __func__); + + mPromises->ResolveResponseEndPromise( + ResponseEndArgs(FetchDriverObserver::eAborted, Nothing()), __func__); + mPromises = nullptr; + } +} + +void FetchService::FetchInstance::OnResponseEnd( + FetchDriverObserver::EndReason aReason, + JS::Handle<JS::Value> aReasonDetails) { + FETCH_LOG(("FetchInstance::OnResponseEnd [%p]", this)); + if (aReason == eAborted) { + FETCH_LOG(("FetchInstance::OnResponseEnd end with eAborted")); + if (mPromises) { + mPromises->ResolveResponseEndPromise( + ResponseEndArgs(FetchDriverObserver::eAborted, Nothing()), __func__); + } + return; + } + if (mPromises) { + // Remove the FetchInstance from FetchInstanceTable + RefPtr<FetchService> fetchService = FetchService::GetInstance(); + MOZ_ASSERT(fetchService); + auto entry = fetchService->mFetchInstanceTable.Lookup(mPromises); + MOZ_ASSERT(entry); + entry.Remove(); + FETCH_LOG( + ("FetchInstance::OnResponseEnd entry[%p] of FetchInstance[%p] is " + "removed", + mPromises.get(), this)); + + // Get PerformanceTimingData from FetchDriver. + ResponseTiming timing; + UniquePtr<PerformanceTimingData> performanceTiming( + mFetchDriver->GetPerformanceTimingData(timing.initiatorType(), + timing.entryName())); + if (performanceTiming != nullptr) { + timing.timingData() = performanceTiming->ToIPC(); + } + + timing.initiatorType() = u"navigation"_ns; + + // Resolve the ResponseEndPromise + mPromises->ResolveResponseEndPromise(ResponseEndArgs(aReason, Some(timing)), + __func__); + // Release promises + mPromises = nullptr; + } +} + +void FetchService::FetchInstance::OnResponseAvailableInternal( + SafeRefPtr<InternalResponse> aResponse) { + FETCH_LOG(("FetchInstance::OnResponseAvailableInternal [%p]", this)); + if (mPromises) { + // Resolve the ResponseAvailablePromise + mPromises->ResolveResponseAvailablePromise(std::move(aResponse), __func__); + } +} + +// TODO: +// Following methods would not be used for navigation preload, but would be used +// with PFetch. They will be implemented in bug 1351231. +bool FetchService::FetchInstance::NeedOnDataAvailable() { return false; } +void FetchService::FetchInstance::OnDataAvailable() {} +void FetchService::FetchInstance::FlushConsoleReport() {} + +// 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) { + RefPtr<FetchServicePromises> promises = MakeRefPtr<FetchServicePromises>(); + promises->ResolveResponseAvailablePromise(InternalResponse::NetworkError(aRv), + __func__); + promises->ResolveResponseEndPromise( + ResponseEndArgs(FetchDriverObserver::eAborted, Nothing()), __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; +} + +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 running fetchs. + for (auto it = mFetchInstanceTable.begin(), end = mFetchInstanceTable.end(); + it != end; ++it) { + it->GetData()->Cancel(); + } + mFetchInstanceTable.Clear(); + } + return NS_OK; +} + +RefPtr<FetchServicePromises> FetchService::Fetch( + SafeRefPtr<InternalRequest> aRequest, nsIChannel* aChannel) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + + FETCH_LOG(("FetchService::Fetch aRequest[%p], aChannel[%p], mOffline: %s", + aRequest.unsafeGetRawPtr(), aChannel, + mOffline ? "true" : "false")); + + if (mOffline) { + FETCH_LOG(("FetchService::Fetch network offline")); + return NetworkErrorResponse(NS_ERROR_OFFLINE); + } + + // Create FetchInstance + RefPtr<FetchInstance> fetch = MakeRefPtr<FetchInstance>(aRequest.clonePtr()); + + // Call FetchInstance::Initialize() to get needed information for FetchDriver, + nsresult rv = fetch->Initialize(aChannel); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NetworkErrorResponse(rv); + } + + // Call FetchInstance::Fetch() to start an asynchronous fetching. + RefPtr<FetchServicePromises> promises = fetch->Fetch(); + + 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_LOG(("FetchService::Fetch entry[%p] of FetchInstance[%p] added", + promises.get(), fetch.get())); + } + return promises; +} + +void FetchService::CancelFetch(RefPtr<FetchServicePromises>&& aPromises) { + 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(); + + entry.Remove(); + FETCH_LOG( + ("FetchService::CancelFetch entry [%p] removed", aPromises.get())); + } +} + +} // namespace mozilla::dom |