diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /dom/serviceworkers/ServiceWorkerPrivate.cpp | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/serviceworkers/ServiceWorkerPrivate.cpp')
-rw-r--r-- | dom/serviceworkers/ServiceWorkerPrivate.cpp | 1686 |
1 files changed, 1686 insertions, 0 deletions
diff --git a/dom/serviceworkers/ServiceWorkerPrivate.cpp b/dom/serviceworkers/ServiceWorkerPrivate.cpp new file mode 100644 index 0000000000..2f4fd3f730 --- /dev/null +++ b/dom/serviceworkers/ServiceWorkerPrivate.cpp @@ -0,0 +1,1686 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "ServiceWorkerPrivate.h" + +#include <utility> + +#include "MainThreadUtils.h" +#include "ServiceWorkerCloneData.h" +#include "ServiceWorkerManager.h" +#include "ServiceWorkerRegistrationInfo.h" +#include "ServiceWorkerUtils.h" +#include "js/ErrorReport.h" +#include "mozIThirdPartyUtil.h" +#include "mozilla/Assertions.h" +#include "mozilla/CycleCollectedJSContext.h" // for MicroTaskRunnable +#include "mozilla/ErrorResult.h" +#include "mozilla/JSObjectHolder.h" +#include "mozilla/Maybe.h" +#include "mozilla/Preferences.h" +#include "mozilla/RemoteLazyInputStreamStorage.h" +#include "mozilla/Result.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/StoragePrincipalHelper.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/ClientIPCTypes.h" +#include "mozilla/dom/DOMTypes.h" +#include "mozilla/dom/FetchEventOpChild.h" +#include "mozilla/dom/InternalHeaders.h" +#include "mozilla/dom/InternalRequest.h" +#include "mozilla/dom/ReferrerInfo.h" +#include "mozilla/dom/RemoteType.h" +#include "mozilla/dom/RemoteWorkerControllerChild.h" +#include "mozilla/dom/RemoteWorkerManager.h" // RemoteWorkerManager::GetRemoteType +#include "mozilla/dom/ServiceWorkerBinding.h" +#include "mozilla/extensions/WebExtensionPolicy.h" // WebExtensionPolicy +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/ipc/IPCStreamUtils.h" +#include "mozilla/ipc/PBackgroundChild.h" +#include "mozilla/ipc/URIUtils.h" +#include "mozilla/net/CookieJarSettings.h" +#include "nsContentUtils.h" +#include "nsDebug.h" +#include "nsError.h" +#include "nsICacheInfoChannel.h" +#include "nsIChannel.h" +#include "nsIHttpChannel.h" +#include "nsIHttpChannelInternal.h" +#include "nsIHttpHeaderVisitor.h" +#include "nsINetworkInterceptController.h" +#include "nsINamed.h" +#include "nsIObserverService.h" +#include "nsIRedirectHistoryEntry.h" +#include "nsIScriptError.h" +#include "nsIScriptSecurityManager.h" +#include "nsISupportsImpl.h" +#include "nsIURI.h" +#include "nsIUploadChannel2.h" +#include "nsNetUtil.h" +#include "nsProxyRelease.h" +#include "nsQueryObject.h" +#include "nsStreamUtils.h" +#include "nsStringStream.h" +#include "nsThreadUtils.h" + +#include "mozilla/dom/Client.h" +#include "mozilla/dom/FetchUtil.h" +#include "mozilla/dom/IndexedDatabaseManager.h" +#include "mozilla/dom/NotificationEvent.h" +#include "mozilla/dom/PromiseNativeHandler.h" +#include "mozilla/dom/PushEventBinding.h" +#include "mozilla/dom/RequestBinding.h" +#include "mozilla/dom/RootedDictionary.h" +#include "mozilla/dom/WorkerDebugger.h" +#include "mozilla/dom/WorkerRef.h" +#include "mozilla/dom/WorkerRunnable.h" +#include "mozilla/dom/WorkerScope.h" +#include "mozilla/dom/ipc/StructuredCloneData.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "mozilla/net/NeckoChannelParams.h" +#include "mozilla/StaticPrefs_privacy.h" +#include "nsIReferrerInfo.h" + +extern mozilla::LazyLogModule sWorkerTelemetryLog; + +#ifdef LOG +# undef LOG +#endif +#define LOG(_args) MOZ_LOG(sWorkerTelemetryLog, LogLevel::Debug, _args); + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::ipc; + +namespace mozilla::dom { + +uint32_t ServiceWorkerPrivate::sRunningServiceWorkers = 0; +uint32_t ServiceWorkerPrivate::sRunningServiceWorkersFetch = 0; +uint32_t ServiceWorkerPrivate::sRunningServiceWorkersMax = 0; +uint32_t ServiceWorkerPrivate::sRunningServiceWorkersFetchMax = 0; + +// Tracks the "dom.serviceWorkers.disable_open_click_delay" preference. Modified +// on main thread, read on worker threads. +// It is updated every time a "notificationclick" event is dispatched. While +// this is done without synchronization, at the worst, the thread will just get +// an older value within which a popup is allowed to be displayed, which will +// still be a valid value since it was set prior to dispatching the runnable. +Atomic<uint32_t> gDOMDisableOpenClickDelay(0); + +/** + * KeepAliveToken + */ +KeepAliveToken::KeepAliveToken(ServiceWorkerPrivate* aPrivate) + : mPrivate(aPrivate) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aPrivate); + mPrivate->AddToken(); +} + +KeepAliveToken::~KeepAliveToken() { + MOZ_ASSERT(NS_IsMainThread()); + mPrivate->ReleaseToken(); +} + +NS_IMPL_ISUPPORTS0(KeepAliveToken) + +/** + * RAIIActorPtrHolder + */ +ServiceWorkerPrivate::RAIIActorPtrHolder::RAIIActorPtrHolder( + already_AddRefed<RemoteWorkerControllerChild> aActor) + : mActor(aActor) { + AssertIsOnMainThread(); + MOZ_ASSERT(mActor); + MOZ_ASSERT(mActor->Manager()); +} + +ServiceWorkerPrivate::RAIIActorPtrHolder::~RAIIActorPtrHolder() { + AssertIsOnMainThread(); + + mDestructorPromiseHolder.ResolveIfExists(true, __func__); + + mActor->MaybeSendDelete(); +} + +RemoteWorkerControllerChild* +ServiceWorkerPrivate::RAIIActorPtrHolder::operator->() const { + AssertIsOnMainThread(); + + return get(); +} + +RemoteWorkerControllerChild* ServiceWorkerPrivate::RAIIActorPtrHolder::get() + const { + AssertIsOnMainThread(); + + return mActor.get(); +} + +RefPtr<GenericPromise> +ServiceWorkerPrivate::RAIIActorPtrHolder::OnDestructor() { + AssertIsOnMainThread(); + + return mDestructorPromiseHolder.Ensure(__func__); +} + +/** + * PendingFunctionEvent + */ +ServiceWorkerPrivate::PendingFunctionalEvent::PendingFunctionalEvent( + ServiceWorkerPrivate* aOwner, + RefPtr<ServiceWorkerRegistrationInfo>&& aRegistration) + : mOwner(aOwner), mRegistration(std::move(aRegistration)) { + AssertIsOnMainThread(); + MOZ_ASSERT(mOwner); + MOZ_ASSERT(mOwner->mInfo); + MOZ_ASSERT(mOwner->mInfo->State() == ServiceWorkerState::Activating); + MOZ_ASSERT(mRegistration); +} + +ServiceWorkerPrivate::PendingFunctionalEvent::~PendingFunctionalEvent() { + AssertIsOnMainThread(); +} + +ServiceWorkerPrivate::PendingPushEvent::PendingPushEvent( + ServiceWorkerPrivate* aOwner, + RefPtr<ServiceWorkerRegistrationInfo>&& aRegistration, + ServiceWorkerPushEventOpArgs&& aArgs) + : PendingFunctionalEvent(aOwner, std::move(aRegistration)), + mArgs(std::move(aArgs)) { + AssertIsOnMainThread(); +} + +nsresult ServiceWorkerPrivate::PendingPushEvent::Send() { + AssertIsOnMainThread(); + MOZ_ASSERT(mOwner); + MOZ_ASSERT(mOwner->mInfo); + + return mOwner->SendPushEventInternal(std::move(mRegistration), + std::move(mArgs)); +} + +ServiceWorkerPrivate::PendingFetchEvent::PendingFetchEvent( + ServiceWorkerPrivate* aOwner, + RefPtr<ServiceWorkerRegistrationInfo>&& aRegistration, + ParentToParentServiceWorkerFetchEventOpArgs&& aArgs, + nsCOMPtr<nsIInterceptedChannel>&& aChannel, + RefPtr<FetchServicePromises>&& aPreloadResponseReadyPromises) + : PendingFunctionalEvent(aOwner, std::move(aRegistration)), + mArgs(std::move(aArgs)), + mChannel(std::move(aChannel)), + mPreloadResponseReadyPromises(std::move(aPreloadResponseReadyPromises)) { + AssertIsOnMainThread(); + MOZ_ASSERT(mChannel); +} + +nsresult ServiceWorkerPrivate::PendingFetchEvent::Send() { + AssertIsOnMainThread(); + MOZ_ASSERT(mOwner); + MOZ_ASSERT(mOwner->mInfo); + + return mOwner->SendFetchEventInternal( + std::move(mRegistration), std::move(mArgs), std::move(mChannel), + std::move(mPreloadResponseReadyPromises)); +} + +ServiceWorkerPrivate::PendingFetchEvent::~PendingFetchEvent() { + AssertIsOnMainThread(); + + if (NS_WARN_IF(mChannel)) { + mChannel->CancelInterception(NS_ERROR_INTERCEPTION_FAILED); + } +} + +namespace { + +class HeaderFiller final : public nsIHttpHeaderVisitor { + public: + NS_DECL_ISUPPORTS + + explicit HeaderFiller(HeadersGuardEnum aGuard) + : mInternalHeaders(new InternalHeaders(aGuard)) { + MOZ_ASSERT(mInternalHeaders); + } + + NS_IMETHOD + VisitHeader(const nsACString& aHeader, const nsACString& aValue) override { + ErrorResult result; + mInternalHeaders->Append(aHeader, aValue, result); + + if (NS_WARN_IF(result.Failed())) { + return result.StealNSResult(); + } + + return NS_OK; + } + + RefPtr<InternalHeaders> Extract() { + return RefPtr<InternalHeaders>(std::move(mInternalHeaders)); + } + + private: + ~HeaderFiller() = default; + + RefPtr<InternalHeaders> mInternalHeaders; +}; + +NS_IMPL_ISUPPORTS(HeaderFiller, nsIHttpHeaderVisitor) + +Result<IPCInternalRequest, nsresult> GetIPCInternalRequest( + nsIInterceptedChannel* aChannel) { + AssertIsOnMainThread(); + + nsCOMPtr<nsIURI> uri; + MOZ_TRY(aChannel->GetSecureUpgradedChannelURI(getter_AddRefs(uri))); + + nsCOMPtr<nsIURI> uriNoFragment; + MOZ_TRY(NS_GetURIWithoutRef(uri, getter_AddRefs(uriNoFragment))); + + nsCOMPtr<nsIChannel> underlyingChannel; + MOZ_TRY(aChannel->GetChannel(getter_AddRefs(underlyingChannel))); + + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(underlyingChannel); + MOZ_ASSERT(httpChannel, "How come we don't have an HTTP channel?"); + + nsCOMPtr<nsIHttpChannelInternal> internalChannel = + do_QueryInterface(httpChannel); + NS_ENSURE_TRUE(internalChannel, Err(NS_ERROR_NOT_AVAILABLE)); + + nsCOMPtr<nsICacheInfoChannel> cacheInfoChannel = + do_QueryInterface(underlyingChannel); + + nsAutoCString spec; + MOZ_TRY(uriNoFragment->GetSpec(spec)); + + nsAutoCString fragment; + MOZ_TRY(uri->GetRef(fragment)); + + nsAutoCString method; + MOZ_TRY(httpChannel->GetRequestMethod(method)); + + // This is safe due to static_asserts in ServiceWorkerManager.cpp + uint32_t cacheModeInt; + MOZ_ALWAYS_SUCCEEDS(internalChannel->GetFetchCacheMode(&cacheModeInt)); + RequestCache cacheMode = static_cast<RequestCache>(cacheModeInt); + + RequestMode requestMode = + InternalRequest::MapChannelToRequestMode(underlyingChannel); + + // This is safe due to static_asserts in ServiceWorkerManager.cpp + uint32_t redirectMode; + MOZ_ALWAYS_SUCCEEDS(internalChannel->GetRedirectMode(&redirectMode)); + RequestRedirect requestRedirect = static_cast<RequestRedirect>(redirectMode); + + RequestCredentials requestCredentials = + InternalRequest::MapChannelToRequestCredentials(underlyingChannel); + + nsAutoString referrer; + ReferrerPolicy referrerPolicy = ReferrerPolicy::_empty; + ReferrerPolicy environmentReferrerPolicy = ReferrerPolicy::_empty; + + nsCOMPtr<nsIReferrerInfo> referrerInfo = httpChannel->GetReferrerInfo(); + if (referrerInfo) { + referrerPolicy = referrerInfo->ReferrerPolicy(); + Unused << referrerInfo->GetComputedReferrerSpec(referrer); + } + + uint32_t loadFlags; + MOZ_TRY(underlyingChannel->GetLoadFlags(&loadFlags)); + + nsCOMPtr<nsILoadInfo> loadInfo = underlyingChannel->LoadInfo(); + nsContentPolicyType contentPolicyType = loadInfo->InternalContentPolicyType(); + + nsAutoString integrity; + MOZ_TRY(internalChannel->GetIntegrityMetadata(integrity)); + + RefPtr<HeaderFiller> headerFiller = + MakeRefPtr<HeaderFiller>(HeadersGuardEnum::Request); + MOZ_TRY(httpChannel->VisitNonDefaultRequestHeaders(headerFiller)); + + RefPtr<InternalHeaders> internalHeaders = headerFiller->Extract(); + + ErrorResult result; + internalHeaders->SetGuard(HeadersGuardEnum::Immutable, result); + if (NS_WARN_IF(result.Failed())) { + return Err(result.StealNSResult()); + } + + nsTArray<HeadersEntry> ipcHeaders; + HeadersGuardEnum ipcHeadersGuard; + internalHeaders->ToIPC(ipcHeaders, ipcHeadersGuard); + + nsAutoCString alternativeDataType; + if (cacheInfoChannel && + !cacheInfoChannel->PreferredAlternativeDataTypes().IsEmpty()) { + // TODO: the internal request probably needs all the preferred types. + alternativeDataType.Assign( + cacheInfoChannel->PreferredAlternativeDataTypes()[0].type()); + } + + Maybe<PrincipalInfo> principalInfo; + Maybe<PrincipalInfo> interceptionPrincipalInfo; + if (loadInfo->TriggeringPrincipal()) { + principalInfo.emplace(); + interceptionPrincipalInfo.emplace(); + MOZ_ALWAYS_SUCCEEDS(PrincipalToPrincipalInfo( + loadInfo->TriggeringPrincipal(), principalInfo.ptr())); + MOZ_ALWAYS_SUCCEEDS(PrincipalToPrincipalInfo( + loadInfo->TriggeringPrincipal(), interceptionPrincipalInfo.ptr())); + } + + nsTArray<RedirectHistoryEntryInfo> redirectChain; + for (const nsCOMPtr<nsIRedirectHistoryEntry>& redirectEntry : + loadInfo->RedirectChain()) { + RedirectHistoryEntryInfo* entry = redirectChain.AppendElement(); + MOZ_ALWAYS_SUCCEEDS(RHEntryToRHEntryInfo(redirectEntry, entry)); + } + + bool isThirdPartyChannel; + // ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance(); + nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil = + do_GetService(THIRDPARTYUTIL_CONTRACTID); + if (thirdPartyUtil) { + nsCOMPtr<nsIURI> uri; + MOZ_TRY(underlyingChannel->GetURI(getter_AddRefs(uri))); + MOZ_TRY(thirdPartyUtil->IsThirdPartyChannel(underlyingChannel, uri, + &isThirdPartyChannel)); + } + + nsILoadInfo::CrossOriginEmbedderPolicy embedderPolicy = + loadInfo->GetLoadingEmbedderPolicy(); + + // Note: all the arguments are copied rather than moved, which would be more + // efficient, because there's no move-friendly constructor generated. + return IPCInternalRequest( + method, {spec}, ipcHeadersGuard, ipcHeaders, Nothing(), -1, + alternativeDataType, contentPolicyType, referrer, referrerPolicy, + environmentReferrerPolicy, requestMode, requestCredentials, cacheMode, + requestRedirect, integrity, fragment, principalInfo, + interceptionPrincipalInfo, contentPolicyType, redirectChain, + isThirdPartyChannel, embedderPolicy); +} + +nsresult MaybeStoreStreamForBackgroundThread(nsIInterceptedChannel* aChannel, + IPCInternalRequest& aIPCRequest) { + nsCOMPtr<nsIChannel> channel; + MOZ_ALWAYS_SUCCEEDS(aChannel->GetChannel(getter_AddRefs(channel))); + + Maybe<BodyStreamVariant> body; + nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(channel); + + if (uploadChannel) { + nsCOMPtr<nsIInputStream> uploadStream; + MOZ_TRY(uploadChannel->CloneUploadStream(&aIPCRequest.bodySize(), + getter_AddRefs(uploadStream))); + + if (uploadStream) { + Maybe<BodyStreamVariant>& body = aIPCRequest.body(); + body.emplace(ParentToParentStream()); + + MOZ_TRY( + nsID::GenerateUUIDInPlace(body->get_ParentToParentStream().uuid())); + + auto storageOrErr = RemoteLazyInputStreamStorage::Get(); + if (NS_WARN_IF(storageOrErr.isErr())) { + return storageOrErr.unwrapErr(); + } + + auto storage = storageOrErr.unwrap(); + storage->AddStream(uploadStream, body->get_ParentToParentStream().uuid()); + } + } + + return NS_OK; +} + +} // anonymous namespace + +/** + * ServiceWorkerPrivate + */ +ServiceWorkerPrivate::ServiceWorkerPrivate(ServiceWorkerInfo* aInfo) + : mInfo(aInfo), mDebuggerCount(0), mTokenCount(0) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aInfo); + MOZ_ASSERT(!mControllerChild); + + mIdleWorkerTimer = NS_NewTimer(); + MOZ_ASSERT(mIdleWorkerTimer); + + // Assert in all debug builds as well as non-debug Nightly and Dev Edition. +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(Initialize())); +#else + MOZ_ALWAYS_SUCCEEDS(Initialize()); +#endif +} + +ServiceWorkerPrivate::~ServiceWorkerPrivate() { + MOZ_ASSERT(!mTokenCount); + MOZ_ASSERT(!mInfo); + MOZ_ASSERT(!mControllerChild); + MOZ_ASSERT(mIdlePromiseHolder.IsEmpty()); + + mIdleWorkerTimer->Cancel(); +} + +nsresult ServiceWorkerPrivate::Initialize() { + AssertIsOnMainThread(); + MOZ_ASSERT(mInfo); + + nsCOMPtr<nsIPrincipal> principal = mInfo->Principal(); + + nsCOMPtr<nsIURI> uri; + auto* basePrin = BasePrincipal::Cast(principal); + nsresult rv = basePrin->GetURI(getter_AddRefs(uri)); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (NS_WARN_IF(!uri)) { + return NS_ERROR_FAILURE; + } + + URIParams baseScriptURL; + SerializeURI(uri, baseScriptURL); + + nsString id; + rv = mInfo->GetId(id); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + PrincipalInfo principalInfo; + rv = PrincipalToPrincipalInfo(principal, &principalInfo); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + + if (NS_WARN_IF(!swm)) { + return NS_ERROR_DOM_ABORT_ERR; + } + + RefPtr<ServiceWorkerRegistrationInfo> regInfo = + swm->GetRegistration(principal, mInfo->Scope()); + + if (NS_WARN_IF(!regInfo)) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + nsCOMPtr<nsICookieJarSettings> cookieJarSettings = + net::CookieJarSettings::Create(principal); + MOZ_ASSERT(cookieJarSettings); + + // We can populate the partitionKey from the originAttribute of the principal + // if it has partitionKey set. It's because ServiceWorker is using the foreign + // partitioned principal and it implies that it's a third-party service + // worker. So, the cookieJarSettings can directly use the partitionKey from + // it. For first-party case, we can populate the partitionKey from the + // principal URI. + if (!principal->OriginAttributesRef().mPartitionKey.IsEmpty()) { + net::CookieJarSettings::Cast(cookieJarSettings) + ->SetPartitionKey(principal->OriginAttributesRef().mPartitionKey); + } else { + net::CookieJarSettings::Cast(cookieJarSettings)->SetPartitionKey(uri); + } + + net::CookieJarSettingsArgs cjsData; + net::CookieJarSettings::Cast(cookieJarSettings)->Serialize(cjsData); + + nsCOMPtr<nsIPrincipal> partitionedPrincipal; + rv = StoragePrincipalHelper::CreatePartitionedPrincipalForServiceWorker( + principal, cookieJarSettings, getter_AddRefs(partitionedPrincipal)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + PrincipalInfo partitionedPrincipalInfo; + rv = + PrincipalToPrincipalInfo(partitionedPrincipal, &partitionedPrincipalInfo); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + StorageAccess storageAccess = + StorageAllowedForServiceWorker(principal, cookieJarSettings); + + ServiceWorkerData serviceWorkerData; + serviceWorkerData.cacheName() = mInfo->CacheName(); + serviceWorkerData.loadFlags() = static_cast<uint32_t>( + mInfo->GetImportsLoadFlags() | nsIChannel::LOAD_BYPASS_SERVICE_WORKER); + serviceWorkerData.id() = std::move(id); + + nsAutoCString domain; + rv = uri->GetHost(domain); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + auto remoteType = RemoteWorkerManager::GetRemoteType( + principal, WorkerKind::WorkerKindService); + if (NS_WARN_IF(remoteType.isErr())) { + return remoteType.unwrapErr(); + } + + // Determine if the service worker is registered under a third-party context + // by checking if it's running under a partitioned principal. + bool isThirdPartyContextToTopWindow = + !principal->OriginAttributesRef().mPartitionKey.IsEmpty(); + + mRemoteWorkerData = RemoteWorkerData( + NS_ConvertUTF8toUTF16(mInfo->ScriptSpec()), baseScriptURL, baseScriptURL, + /* name */ VoidString(), + /* workerType */ WorkerType::Classic, + /* credentials */ RequestCredentials::Omit, + /* loading principal */ principalInfo, principalInfo, + partitionedPrincipalInfo, + /* useRegularPrincipal */ true, + + // ServiceWorkers run as first-party, no storage-access permission needed. + /* hasStorageAccessPermissionGranted */ false, + + cjsData, domain, + /* isSecureContext */ true, + /* clientInfo*/ Nothing(), + + // The RemoteWorkerData CTOR doesn't allow to set the referrerInfo via + // already_AddRefed<>. Let's set it to null. + /* referrerInfo */ nullptr, + + storageAccess, isThirdPartyContextToTopWindow, + nsContentUtils::ShouldResistFingerprinting_dangerous( + principal, + "Service Workers exist outside a Document or Channel; as a property " + "of the domain (and origin attributes). We don't have a " + "CookieJarSettings to perform the nested check, but we can rely on" + "the FPI/dFPI partition key check."), + // Origin trials are associated to a window, so it doesn't make sense on + // service workers. + OriginTrials(), std::move(serviceWorkerData), regInfo->AgentClusterId(), + remoteType.unwrap()); + + mRemoteWorkerData.referrerInfo() = MakeAndAddRef<ReferrerInfo>(); + + // This fills in the rest of mRemoteWorkerData.serviceWorkerData(). + RefreshRemoteWorkerData(regInfo); + + return NS_OK; +} + +nsresult ServiceWorkerPrivate::CheckScriptEvaluation( + RefPtr<LifeCycleEventCallback> aCallback) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aCallback); + + RefPtr<ServiceWorkerPrivate> self = this; + + /** + * We need to capture the actor associated with the current Service Worker so + * we can terminate it if script evaluation failed. + */ + nsresult rv = SpawnWorkerIfNeeded(); + + if (NS_WARN_IF(NS_FAILED(rv))) { + aCallback->SetResult(false); + aCallback->Run(); + + return rv; + } + + MOZ_ASSERT(mControllerChild); + + RefPtr<RAIIActorPtrHolder> holder = mControllerChild; + + return ExecServiceWorkerOp( + ServiceWorkerCheckScriptEvaluationOpArgs(), + [self = std::move(self), holder = std::move(holder), + callback = aCallback](ServiceWorkerOpResult&& aResult) mutable { + if (aResult.type() == ServiceWorkerOpResult:: + TServiceWorkerCheckScriptEvaluationOpResult) { + auto& result = + aResult.get_ServiceWorkerCheckScriptEvaluationOpResult(); + + if (result.workerScriptExecutedSuccessfully()) { + self->SetHandlesFetch(result.fetchHandlerWasAdded()); + if (self->mHandlesFetch == Unknown) { + self->mHandlesFetch = + result.fetchHandlerWasAdded() ? Enabled : Disabled; + // Update telemetry for # of running SW - the already-running SW + // handles fetch + if (self->mHandlesFetch == Enabled) { + self->UpdateRunning(0, 1); + } + } + + callback->SetResult(result.workerScriptExecutedSuccessfully()); + callback->Run(); + return; + } + } + + /** + * If script evaluation failed, first terminate the Service Worker + * before invoking the callback. + */ + MOZ_ASSERT_IF(aResult.type() == ServiceWorkerOpResult::Tnsresult, + NS_FAILED(aResult.get_nsresult())); + + // If a termination operation was already issued using `holder`... + if (self->mControllerChild != holder) { + holder->OnDestructor()->Then( + GetCurrentSerialEventTarget(), __func__, + [callback = std::move(callback)]( + const GenericPromise::ResolveOrRejectValue&) { + callback->SetResult(false); + callback->Run(); + }); + + return; + } + + RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + MOZ_ASSERT(swm); + + auto shutdownStateId = swm->MaybeInitServiceWorkerShutdownProgress(); + + RefPtr<GenericNonExclusivePromise> promise = + self->ShutdownInternal(shutdownStateId); + + swm->BlockShutdownOn(promise, shutdownStateId); + + promise->Then( + GetCurrentSerialEventTarget(), __func__, + [callback = std::move(callback)]( + const GenericNonExclusivePromise::ResolveOrRejectValue&) { + callback->SetResult(false); + callback->Run(); + }); + }, + [callback = aCallback] { + callback->SetResult(false); + callback->Run(); + }); +} + +nsresult ServiceWorkerPrivate::SendMessageEvent( + RefPtr<ServiceWorkerCloneData>&& aData, + const ClientInfoAndState& aClientInfoAndState) { + AssertIsOnMainThread(); + MOZ_ASSERT(aData); + + auto scopeExit = MakeScopeExit([&] { Shutdown(); }); + + PBackgroundChild* bgChild = BackgroundChild::GetForCurrentThread(); + + if (NS_WARN_IF(!bgChild)) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + ServiceWorkerMessageEventOpArgs args; + args.clientInfoAndState() = aClientInfoAndState; + if (!aData->BuildClonedMessageData(args.clonedData())) { + return NS_ERROR_DOM_DATA_CLONE_ERR; + } + + scopeExit.release(); + + return ExecServiceWorkerOp( + std::move(args), [](ServiceWorkerOpResult&& aResult) { + MOZ_ASSERT(aResult.type() == ServiceWorkerOpResult::Tnsresult); + }); +} + +nsresult ServiceWorkerPrivate::SendLifeCycleEvent( + const nsAString& aEventType, RefPtr<LifeCycleEventCallback> aCallback) { + AssertIsOnMainThread(); + MOZ_ASSERT(aCallback); + + return ExecServiceWorkerOp( + ServiceWorkerLifeCycleEventOpArgs(nsString(aEventType)), + [callback = aCallback](ServiceWorkerOpResult&& aResult) { + MOZ_ASSERT(aResult.type() == ServiceWorkerOpResult::Tnsresult); + + callback->SetResult(NS_SUCCEEDED(aResult.get_nsresult())); + callback->Run(); + }, + [callback = aCallback] { + callback->SetResult(false); + callback->Run(); + }); +} + +nsresult ServiceWorkerPrivate::SendPushEvent( + const nsAString& aMessageId, const Maybe<nsTArray<uint8_t>>& aData, + RefPtr<ServiceWorkerRegistrationInfo> aRegistration) { + AssertIsOnMainThread(); + MOZ_ASSERT(mInfo); + MOZ_ASSERT(aRegistration); + + ServiceWorkerPushEventOpArgs args; + args.messageId() = nsString(aMessageId); + + if (aData) { + args.data() = aData.ref(); + } else { + args.data() = void_t(); + } + + if (mInfo->State() == ServiceWorkerState::Activating) { + UniquePtr<PendingFunctionalEvent> pendingEvent = + MakeUnique<PendingPushEvent>(this, std::move(aRegistration), + std::move(args)); + + mPendingFunctionalEvents.AppendElement(std::move(pendingEvent)); + + return NS_OK; + } + + MOZ_ASSERT(mInfo->State() == ServiceWorkerState::Activated); + + return SendPushEventInternal(std::move(aRegistration), std::move(args)); +} + +nsresult ServiceWorkerPrivate::SendPushEventInternal( + RefPtr<ServiceWorkerRegistrationInfo>&& aRegistration, + ServiceWorkerPushEventOpArgs&& aArgs) { + AssertIsOnMainThread(); + MOZ_ASSERT(aRegistration); + + return ExecServiceWorkerOp( + std::move(aArgs), + [registration = aRegistration](ServiceWorkerOpResult&& aResult) { + MOZ_ASSERT(aResult.type() == ServiceWorkerOpResult::Tnsresult); + + registration->MaybeScheduleTimeCheckAndUpdate(); + }, + [registration = aRegistration]() { + registration->MaybeScheduleTimeCheckAndUpdate(); + }); +} + +nsresult ServiceWorkerPrivate::SendPushSubscriptionChangeEvent() { + AssertIsOnMainThread(); + + return ExecServiceWorkerOp( + ServiceWorkerPushSubscriptionChangeEventOpArgs(), + [](ServiceWorkerOpResult&& aResult) { + MOZ_ASSERT(aResult.type() == ServiceWorkerOpResult::Tnsresult); + }); +} + +nsresult ServiceWorkerPrivate::SendNotificationEvent( + const nsAString& aEventName, const nsAString& aID, const nsAString& aTitle, + const nsAString& aDir, const nsAString& aLang, const nsAString& aBody, + const nsAString& aTag, const nsAString& aIcon, const nsAString& aData, + const nsAString& aBehavior, const nsAString& aScope) { + MOZ_ASSERT(NS_IsMainThread()); + + if (aEventName.EqualsLiteral(NOTIFICATION_CLICK_EVENT_NAME)) { + gDOMDisableOpenClickDelay = + Preferences::GetInt("dom.serviceWorkers.disable_open_click_delay"); + } else if (!aEventName.EqualsLiteral(NOTIFICATION_CLOSE_EVENT_NAME)) { + MOZ_ASSERT_UNREACHABLE("Invalid notification event name"); + return NS_ERROR_FAILURE; + } + + ServiceWorkerNotificationEventOpArgs args; + args.eventName() = nsString(aEventName); + args.id() = nsString(aID); + args.title() = nsString(aTitle); + args.dir() = nsString(aDir); + args.lang() = nsString(aLang); + args.body() = nsString(aBody); + args.tag() = nsString(aTag); + args.icon() = nsString(aIcon); + args.data() = nsString(aData); + args.behavior() = nsString(aBehavior); + args.scope() = nsString(aScope); + args.disableOpenClickDelay() = gDOMDisableOpenClickDelay; + + return ExecServiceWorkerOp( + std::move(args), [](ServiceWorkerOpResult&& aResult) { + MOZ_ASSERT(aResult.type() == ServiceWorkerOpResult::Tnsresult); + }); +} + +nsresult ServiceWorkerPrivate::SendFetchEvent( + nsCOMPtr<nsIInterceptedChannel> aChannel, nsILoadGroup* aLoadGroup, + const nsAString& aClientId, const nsAString& aResultingClientId) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aChannel); + + RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + if (NS_WARN_IF(!mInfo || !swm)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIChannel> channel; + nsresult rv = aChannel->GetChannel(getter_AddRefs(channel)); + NS_ENSURE_SUCCESS(rv, rv); + bool isNonSubresourceRequest = + nsContentUtils::IsNonSubresourceRequest(channel); + + RefPtr<ServiceWorkerRegistrationInfo> registration; + if (isNonSubresourceRequest) { + registration = swm->GetRegistration(mInfo->Principal(), mInfo->Scope()); + } else { + nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); + + // We'll check for a null registration below rather than an error code here. + Unused << swm->GetClientRegistration(loadInfo->GetClientInfo().ref(), + getter_AddRefs(registration)); + } + + // Its possible the registration is removed between starting the interception + // and actually dispatching the fetch event. In these cases we simply + // want to restart the original network request. Since this is a normal + // condition we handle the reset here instead of returning an error which + // would in turn trigger a console report. + if (!registration) { + nsresult rv = aChannel->ResetInterception(false); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to resume intercepted network request"); + aChannel->CancelInterception(rv); + } + return NS_OK; + } + + // Handle Fetch algorithm - step 16. If the service worker didn't register + // any fetch event handlers, then abort the interception and maybe trigger + // the soft update algorithm. + if (!mInfo->HandlesFetch()) { + nsresult rv = aChannel->ResetInterception(false); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to resume intercepted network request"); + aChannel->CancelInterception(rv); + } + + // Trigger soft updates if necessary. + registration->MaybeScheduleTimeCheckAndUpdate(); + + return NS_OK; + } + + auto scopeExit = MakeScopeExit([&] { + aChannel->CancelInterception(NS_ERROR_INTERCEPTION_FAILED); + Shutdown(); + }); + + IPCInternalRequest request; + MOZ_TRY_VAR(request, GetIPCInternalRequest(aChannel)); + + scopeExit.release(); + + bool preloadNavigation = isNonSubresourceRequest && + request.method().LowerCaseEqualsASCII("get") && + registration->GetNavigationPreloadState().enabled(); + + RefPtr<FetchServicePromises> preloadResponsePromises; + if (preloadNavigation) { + preloadResponsePromises = SetupNavigationPreload(aChannel, registration); + } + + ParentToParentServiceWorkerFetchEventOpArgs args( + ServiceWorkerFetchEventOpArgsCommon( + mInfo->ScriptSpec(), request, nsString(aClientId), + nsString(aResultingClientId), isNonSubresourceRequest, + preloadNavigation, mInfo->TestingInjectCancellation()), + Nothing(), Nothing(), Nothing()); + + if (mInfo->State() == ServiceWorkerState::Activating) { + UniquePtr<PendingFunctionalEvent> pendingEvent = + MakeUnique<PendingFetchEvent>(this, std::move(registration), + std::move(args), std::move(aChannel), + std::move(preloadResponsePromises)); + + mPendingFunctionalEvents.AppendElement(std::move(pendingEvent)); + + return NS_OK; + } + + MOZ_ASSERT(mInfo->State() == ServiceWorkerState::Activated); + + return SendFetchEventInternal(std::move(registration), std::move(args), + std::move(aChannel), + std::move(preloadResponsePromises)); +} + +nsresult ServiceWorkerPrivate::SendFetchEventInternal( + RefPtr<ServiceWorkerRegistrationInfo>&& aRegistration, + ParentToParentServiceWorkerFetchEventOpArgs&& aArgs, + nsCOMPtr<nsIInterceptedChannel>&& aChannel, + RefPtr<FetchServicePromises>&& aPreloadResponseReadyPromises) { + AssertIsOnMainThread(); + + auto scopeExit = MakeScopeExit([&] { Shutdown(); }); + + if (NS_WARN_IF(!mInfo)) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + MOZ_TRY(SpawnWorkerIfNeeded()); + MOZ_TRY(MaybeStoreStreamForBackgroundThread( + aChannel, aArgs.common().internalRequest())); + + scopeExit.release(); + + MOZ_ASSERT(mControllerChild); + + RefPtr<RAIIActorPtrHolder> holder = mControllerChild; + + FetchEventOpChild::SendFetchEvent( + mControllerChild->get(), std::move(aArgs), std::move(aChannel), + std::move(aRegistration), std::move(aPreloadResponseReadyPromises), + CreateEventKeepAliveToken()) + ->Then(GetCurrentSerialEventTarget(), __func__, + [holder = std::move(holder)]( + const GenericPromise::ResolveOrRejectValue& aResult) { + Unused << NS_WARN_IF(aResult.IsReject()); + }); + + return NS_OK; +} + +Result<RefPtr<ServiceWorkerPrivate::PromiseExtensionWorkerHasListener>, + nsresult> +ServiceWorkerPrivate::WakeForExtensionAPIEvent( + const nsAString& aExtensionAPINamespace, + const nsAString& aExtensionAPIEventName) { + AssertIsOnMainThread(); + + ServiceWorkerExtensionAPIEventOpArgs args; + args.apiNamespace() = nsString(aExtensionAPINamespace); + args.apiEventName() = nsString(aExtensionAPIEventName); + + auto promise = + MakeRefPtr<PromiseExtensionWorkerHasListener::Private>(__func__); + + nsresult rv = ExecServiceWorkerOp( + std::move(args), + [promise](ServiceWorkerOpResult&& aResult) { + MOZ_ASSERT( + aResult.type() == + ServiceWorkerOpResult::TServiceWorkerExtensionAPIEventOpResult); + auto& result = aResult.get_ServiceWorkerExtensionAPIEventOpResult(); + promise->Resolve(result.extensionAPIEventListenerWasAdded(), __func__); + }, + [promise]() { promise->Reject(NS_ERROR_FAILURE, __func__); }); + + if (NS_FAILED(rv)) { + promise->Reject(rv, __func__); + } + + RefPtr<PromiseExtensionWorkerHasListener> outPromise(promise); + return outPromise; +} + +nsresult ServiceWorkerPrivate::SpawnWorkerIfNeeded() { + AssertIsOnMainThread(); + + if (mControllerChild) { + RenewKeepAliveToken(); + return NS_OK; + } + + if (!mInfo) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + mServiceWorkerLaunchTimeStart = TimeStamp::Now(); + + PBackgroundChild* bgChild = BackgroundChild::GetForCurrentThread(); + + if (NS_WARN_IF(!bgChild)) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + // If the worker principal is an extension principal, then we should not spawn + // a worker if there is no WebExtensionPolicy associated to that principal + // or if the WebExtensionPolicy is not active. + auto* principal = mInfo->Principal(); + if (principal->SchemeIs("moz-extension")) { + auto* addonPolicy = BasePrincipal::Cast(principal)->AddonPolicy(); + if (!addonPolicy || !addonPolicy->Active()) { + NS_WARNING( + "Trying to wake up a service worker for a disabled webextension."); + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + } + + RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + + if (NS_WARN_IF(!swm)) { + return NS_ERROR_DOM_ABORT_ERR; + } + + RefPtr<ServiceWorkerRegistrationInfo> regInfo = + swm->GetRegistration(principal, mInfo->Scope()); + + if (NS_WARN_IF(!regInfo)) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + RefreshRemoteWorkerData(regInfo); + + RefPtr<RemoteWorkerControllerChild> controllerChild = + new RemoteWorkerControllerChild(this); + + if (NS_WARN_IF(!bgChild->SendPRemoteWorkerControllerConstructor( + controllerChild, mRemoteWorkerData))) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + /** + * Manually `AddRef()` because `DeallocPRemoteWorkerControllerChild()` + * calls `Release()` and the `AllocPRemoteWorkerControllerChild()` function + * is not called. + */ + // NOLINTNEXTLINE(readability-redundant-smartptr-get) + controllerChild.get()->AddRef(); + + mControllerChild = new RAIIActorPtrHolder(controllerChild.forget()); + + // Update Running count here because we may Terminate before we get + // CreationSucceeded(). We'll update if it handles Fetch if that changes + // ( + UpdateRunning(1, mHandlesFetch == Enabled ? 1 : 0); + + return NS_OK; +} + +void ServiceWorkerPrivate::TerminateWorker() { + MOZ_ASSERT(NS_IsMainThread()); + mIdleWorkerTimer->Cancel(); + mIdleKeepAliveToken = nullptr; + Shutdown(); +} + +void ServiceWorkerPrivate::NoteDeadServiceWorkerInfo() { + MOZ_ASSERT(NS_IsMainThread()); + + TerminateWorker(); + mInfo = nullptr; +} + +void ServiceWorkerPrivate::UpdateState(ServiceWorkerState aState) { + AssertIsOnMainThread(); + + if (!mControllerChild) { + return; + } + + nsresult rv = ExecServiceWorkerOp( + ServiceWorkerUpdateStateOpArgs(aState), + [](ServiceWorkerOpResult&& aResult) { + MOZ_ASSERT(aResult.type() == ServiceWorkerOpResult::Tnsresult); + }); + + if (NS_WARN_IF(NS_FAILED(rv))) { + Shutdown(); + return; + } + + if (aState != ServiceWorkerState::Activated) { + return; + } + + for (auto& event : mPendingFunctionalEvents) { + Unused << NS_WARN_IF(NS_FAILED(event->Send())); + } + + mPendingFunctionalEvents.Clear(); +} + +nsresult ServiceWorkerPrivate::GetDebugger(nsIWorkerDebugger** aResult) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aResult); + + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult ServiceWorkerPrivate::AttachDebugger() { + MOZ_ASSERT(NS_IsMainThread()); + + // When the first debugger attaches to a worker, we spawn a worker if needed, + // and cancel the idle timeout. The idle timeout should not be reset until + // the last debugger detached from the worker. + if (!mDebuggerCount) { + nsresult rv = SpawnWorkerIfNeeded(); + NS_ENSURE_SUCCESS(rv, rv); + + /** + * Renewing the idle KeepAliveToken for spawning workers happens + * asynchronously, rather than synchronously. + * The asynchronous renewal is because the actual spawning of workers occurs + * in a content process, so we will only renew once notified that the worker + * has been successfully created + * + * This means that the DevTools way of starting up a worker by calling + * `AttachDebugger` immediately followed by `DetachDebugger` will spawn and + * immediately terminate a worker (because `mTokenCount` is possibly 0 + * due to the idle KeepAliveToken being created asynchronously). So, just + * renew the KeepAliveToken right now. + */ + RenewKeepAliveToken(); + mIdleWorkerTimer->Cancel(); + } + + ++mDebuggerCount; + + return NS_OK; +} + +nsresult ServiceWorkerPrivate::DetachDebugger() { + MOZ_ASSERT(NS_IsMainThread()); + + if (!mDebuggerCount) { + return NS_ERROR_UNEXPECTED; + } + + --mDebuggerCount; + + // When the last debugger detaches from a worker, we either reset the idle + // timeout, or terminate the worker if there are no more active tokens. + if (!mDebuggerCount) { + if (mTokenCount) { + ResetIdleTimeout(); + } else { + TerminateWorker(); + } + } + + return NS_OK; +} + +bool ServiceWorkerPrivate::IsIdle() const { + MOZ_ASSERT(NS_IsMainThread()); + return mTokenCount == 0 || (mTokenCount == 1 && mIdleKeepAliveToken); +} + +RefPtr<GenericPromise> ServiceWorkerPrivate::GetIdlePromise() { +#ifdef DEBUG + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!IsIdle()); + MOZ_ASSERT(!mIdlePromiseObtained, "Idle promise may only be obtained once!"); + mIdlePromiseObtained = true; +#endif + + return mIdlePromiseHolder.Ensure(__func__); +} + +namespace { + +class ServiceWorkerPrivateTimerCallback final : public nsITimerCallback, + public nsINamed { + public: + using Method = void (ServiceWorkerPrivate::*)(nsITimer*); + + ServiceWorkerPrivateTimerCallback(ServiceWorkerPrivate* aServiceWorkerPrivate, + Method aMethod) + : mServiceWorkerPrivate(aServiceWorkerPrivate), mMethod(aMethod) {} + + NS_IMETHOD + Notify(nsITimer* aTimer) override { + (mServiceWorkerPrivate->*mMethod)(aTimer); + mServiceWorkerPrivate = nullptr; + return NS_OK; + } + + NS_IMETHOD + GetName(nsACString& aName) override { + aName.AssignLiteral("ServiceWorkerPrivateTimerCallback"); + return NS_OK; + } + + private: + ~ServiceWorkerPrivateTimerCallback() = default; + + RefPtr<ServiceWorkerPrivate> mServiceWorkerPrivate; + Method mMethod; + + NS_DECL_THREADSAFE_ISUPPORTS +}; + +NS_IMPL_ISUPPORTS(ServiceWorkerPrivateTimerCallback, nsITimerCallback, + nsINamed); + +} // anonymous namespace + +void ServiceWorkerPrivate::NoteIdleWorkerCallback(nsITimer* aTimer) { + MOZ_ASSERT(NS_IsMainThread()); + + MOZ_ASSERT(aTimer == mIdleWorkerTimer, "Invalid timer!"); + + // Release ServiceWorkerPrivate's token, since the grace period has ended. + mIdleKeepAliveToken = nullptr; + + if (mControllerChild) { + // If we still have a living worker at this point it means that either there + // are pending waitUntil promises or the worker is doing some long-running + // computation. Wait a bit more until we forcibly terminate the worker. + uint32_t timeout = + Preferences::GetInt("dom.serviceWorkers.idle_extended_timeout"); + nsCOMPtr<nsITimerCallback> cb = new ServiceWorkerPrivateTimerCallback( + this, &ServiceWorkerPrivate::TerminateWorkerCallback); + DebugOnly<nsresult> rv = mIdleWorkerTimer->InitWithCallback( + cb, timeout, nsITimer::TYPE_ONE_SHOT); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + } +} + +void ServiceWorkerPrivate::TerminateWorkerCallback(nsITimer* aTimer) { + MOZ_ASSERT(NS_IsMainThread()); + + MOZ_ASSERT(aTimer == this->mIdleWorkerTimer, "Invalid timer!"); + + // mInfo must be non-null at this point because NoteDeadServiceWorkerInfo + // which zeroes it calls TerminateWorker which cancels our timer which will + // ensure we don't get invoked even if the nsTimerEvent is in the event queue. + ServiceWorkerManager::LocalizeAndReportToAllClients( + mInfo->Scope(), "ServiceWorkerGraceTimeoutTermination", + nsTArray<nsString>{NS_ConvertUTF8toUTF16(mInfo->Scope())}); + + TerminateWorker(); +} + +void ServiceWorkerPrivate::RenewKeepAliveToken() { + // We should have an active worker if we're renewing the keep alive token. + MOZ_ASSERT(mControllerChild); + + // If there is at least one debugger attached to the worker, the idle worker + // timeout was canceled when the first debugger attached to the worker. It + // should not be reset until the last debugger detaches from the worker. + if (!mDebuggerCount) { + ResetIdleTimeout(); + } + + if (!mIdleKeepAliveToken) { + mIdleKeepAliveToken = new KeepAliveToken(this); + } +} + +void ServiceWorkerPrivate::ResetIdleTimeout() { + uint32_t timeout = Preferences::GetInt("dom.serviceWorkers.idle_timeout"); + nsCOMPtr<nsITimerCallback> cb = new ServiceWorkerPrivateTimerCallback( + this, &ServiceWorkerPrivate::NoteIdleWorkerCallback); + DebugOnly<nsresult> rv = + mIdleWorkerTimer->InitWithCallback(cb, timeout, nsITimer::TYPE_ONE_SHOT); + MOZ_ASSERT(NS_SUCCEEDED(rv)); +} + +void ServiceWorkerPrivate::AddToken() { + MOZ_ASSERT(NS_IsMainThread()); + ++mTokenCount; +} + +void ServiceWorkerPrivate::ReleaseToken() { + MOZ_ASSERT(NS_IsMainThread()); + + MOZ_ASSERT(mTokenCount > 0); + --mTokenCount; + + if (IsIdle()) { + mIdlePromiseHolder.ResolveIfExists(true, __func__); + + if (!mTokenCount) { + TerminateWorker(); + } + + // mInfo can be nullptr here if NoteDeadServiceWorkerInfo() is called while + // the KeepAliveToken is being proxy released as a runnable. + else if (mInfo) { + RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + if (swm) { + swm->WorkerIsIdle(mInfo); + } + } + } +} + +already_AddRefed<KeepAliveToken> +ServiceWorkerPrivate::CreateEventKeepAliveToken() { + MOZ_ASSERT(NS_IsMainThread()); + + // When the WorkerPrivate is in a separate process, we first hold a normal + // KeepAliveToken. Then, after we're notified that the worker is alive, we + // create the idle KeepAliveToken. + MOZ_ASSERT(mIdleKeepAliveToken || mControllerChild); + + RefPtr<KeepAliveToken> ref = new KeepAliveToken(this); + return ref.forget(); +} + +void ServiceWorkerPrivate::SetHandlesFetch(bool aValue) { + MOZ_ASSERT(NS_IsMainThread()); + + if (NS_WARN_IF(!mInfo)) { + return; + } + + mInfo->SetHandlesFetch(aValue); +} + +RefPtr<GenericPromise> ServiceWorkerPrivate::SetSkipWaitingFlag() { + AssertIsOnMainThread(); + MOZ_ASSERT(mInfo); + + RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + + if (!swm) { + return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + + RefPtr<ServiceWorkerRegistrationInfo> regInfo = + swm->GetRegistration(mInfo->Principal(), mInfo->Scope()); + + if (!regInfo) { + return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + + mInfo->SetSkipWaitingFlag(); + + RefPtr<GenericPromise::Private> promise = + new GenericPromise::Private(__func__); + + regInfo->TryToActivateAsync([promise] { promise->Resolve(true, __func__); }); + + return promise; +} + +/* static */ +void ServiceWorkerPrivate::UpdateRunning(int32_t aDelta, int32_t aFetchDelta) { + // Record values for time we were running at the current values + RefPtr<ServiceWorkerManager> manager(ServiceWorkerManager::GetInstance()); + manager->RecordTelemetry(sRunningServiceWorkers, sRunningServiceWorkersFetch); + + MOZ_ASSERT(((int64_t)sRunningServiceWorkers) + aDelta >= 0); + sRunningServiceWorkers += aDelta; + if (sRunningServiceWorkers > sRunningServiceWorkersMax) { + sRunningServiceWorkersMax = sRunningServiceWorkers; + LOG(("ServiceWorker max now %d", sRunningServiceWorkersMax)); + Telemetry::ScalarSet(Telemetry::ScalarID::SERVICEWORKER_RUNNING_MAX, + u"All"_ns, sRunningServiceWorkersMax); + } + MOZ_ASSERT(((int64_t)sRunningServiceWorkersFetch) + aFetchDelta >= 0); + sRunningServiceWorkersFetch += aFetchDelta; + if (sRunningServiceWorkersFetch > sRunningServiceWorkersFetchMax) { + sRunningServiceWorkersFetchMax = sRunningServiceWorkersFetch; + LOG(("ServiceWorker Fetch max now %d", sRunningServiceWorkersFetchMax)); + Telemetry::ScalarSet(Telemetry::ScalarID::SERVICEWORKER_RUNNING_MAX, + u"Fetch"_ns, sRunningServiceWorkersFetchMax); + } + LOG(("ServiceWorkers running now %d/%d", sRunningServiceWorkers, + sRunningServiceWorkersFetch)); +} + +void ServiceWorkerPrivate::CreationFailed() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mControllerChild); + + if (mRemoteWorkerData.remoteType().Find(SERVICEWORKER_REMOTE_TYPE) != + kNotFound) { + Telemetry::AccumulateTimeDelta( + Telemetry::SERVICE_WORKER_ISOLATED_LAUNCH_TIME, + mServiceWorkerLaunchTimeStart); + } else { + Telemetry::AccumulateTimeDelta(Telemetry::SERVICE_WORKER_LAUNCH_TIME_2, + mServiceWorkerLaunchTimeStart); + } + + Shutdown(); +} + +void ServiceWorkerPrivate::CreationSucceeded() { + AssertIsOnMainThread(); + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mInfo); + MOZ_ASSERT(mControllerChild); + + if (mRemoteWorkerData.remoteType().Find(SERVICEWORKER_REMOTE_TYPE) != + kNotFound) { + Telemetry::AccumulateTimeDelta( + Telemetry::SERVICE_WORKER_ISOLATED_LAUNCH_TIME, + mServiceWorkerLaunchTimeStart); + } else { + Telemetry::AccumulateTimeDelta(Telemetry::SERVICE_WORKER_LAUNCH_TIME_2, + mServiceWorkerLaunchTimeStart); + } + + RenewKeepAliveToken(); + + RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + nsCOMPtr<nsIPrincipal> principal = mInfo->Principal(); + RefPtr<ServiceWorkerRegistrationInfo> regInfo = + swm->GetRegistration(principal, mInfo->Scope()); + if (regInfo) { + // If it's already set, we're done and the running count is already set + if (mHandlesFetch == Unknown) { + if (regInfo->GetActive()) { + mHandlesFetch = + regInfo->GetActive()->HandlesFetch() ? Enabled : Disabled; + if (mHandlesFetch == Enabled) { + UpdateRunning(0, 1); + } + } + // else we're likely still in Evaluating state, and don't know if it + // handles fetch. If so, defer updating the counter for Fetch until we + // finish evaluation. We already updated the Running count for All in + // SpawnWorkerIfNeeded(). + } + } +} + +void ServiceWorkerPrivate::ErrorReceived(const ErrorValue& aError) { + AssertIsOnMainThread(); + MOZ_ASSERT(mInfo); + MOZ_ASSERT(mControllerChild); + + RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + MOZ_ASSERT(swm); + + ServiceWorkerInfo* info = mInfo; + + swm->HandleError(nullptr, info->Principal(), info->Scope(), + NS_ConvertUTF8toUTF16(info->ScriptSpec()), u""_ns, u""_ns, + u""_ns, 0, 0, nsIScriptError::errorFlag, JSEXN_ERR); +} + +void ServiceWorkerPrivate::Terminated() { + AssertIsOnMainThread(); + MOZ_ASSERT(mInfo); + MOZ_ASSERT(mControllerChild); + + Shutdown(); +} + +void ServiceWorkerPrivate::RefreshRemoteWorkerData( + const RefPtr<ServiceWorkerRegistrationInfo>& aRegistration) { + AssertIsOnMainThread(); + MOZ_ASSERT(mInfo); + + ServiceWorkerData& serviceWorkerData = + mRemoteWorkerData.serviceWorkerData().get_ServiceWorkerData(); + serviceWorkerData.descriptor() = mInfo->Descriptor().ToIPC(); + serviceWorkerData.registrationDescriptor() = + aRegistration->Descriptor().ToIPC(); +} + +RefPtr<FetchServicePromises> ServiceWorkerPrivate::SetupNavigationPreload( + nsCOMPtr<nsIInterceptedChannel>& aChannel, + const RefPtr<ServiceWorkerRegistrationInfo>& aRegistration) { + MOZ_ASSERT(XRE_IsParentProcess()); + AssertIsOnMainThread(); + + // create IPC request from the intercepted channel. + auto result = GetIPCInternalRequest(aChannel); + if (result.isErr()) { + return nullptr; + } + IPCInternalRequest ipcRequest = result.unwrap(); + + // Step 1. Clone the request for preload + // Create the InternalResponse from the created IPCRequest. + SafeRefPtr<InternalRequest> preloadRequest = + MakeSafeRefPtr<InternalRequest>(ipcRequest); + // Copy the request body from uploadChannel + nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(aChannel); + if (uploadChannel) { + nsCOMPtr<nsIInputStream> uploadStream; + nsresult rv = uploadChannel->CloneUploadStream( + &ipcRequest.bodySize(), getter_AddRefs(uploadStream)); + // Fail to get the request's body, stop navigation preload by returning + // nullptr. + if (NS_WARN_IF(NS_FAILED(rv))) { + return FetchService::NetworkErrorResponse(rv); + } + preloadRequest->SetBody(uploadStream, ipcRequest.bodySize()); + } + + // Set SkipServiceWorker for the navigation preload request + preloadRequest->SetSkipServiceWorker(); + + // Step 2. Append Service-Worker-Navigation-Preload header with + // registration->GetNavigationPreloadState().headerValue() on + // request's header list. + IgnoredErrorResult err; + auto headersGuard = preloadRequest->Headers()->Guard(); + preloadRequest->Headers()->SetGuard(HeadersGuardEnum::None, err); + preloadRequest->Headers()->Append( + "Service-Worker-Navigation-Preload"_ns, + aRegistration->GetNavigationPreloadState().headerValue(), err); + preloadRequest->Headers()->SetGuard(headersGuard, err); + + // Step 3. Perform fetch through FetchService with the cloned request + if (!err.Failed()) { + nsCOMPtr<nsIChannel> underlyingChannel; + MOZ_ALWAYS_SUCCEEDS( + aChannel->GetChannel(getter_AddRefs(underlyingChannel))); + RefPtr<FetchService> fetchService = FetchService::GetInstance(); + return fetchService->Fetch(AsVariant(FetchService::NavigationPreloadArgs{ + std::move(preloadRequest), underlyingChannel})); + } + return FetchService::NetworkErrorResponse(NS_ERROR_UNEXPECTED); +} + +void ServiceWorkerPrivate::Shutdown() { + AssertIsOnMainThread(); + + if (mControllerChild) { + RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + + MOZ_ASSERT(swm, + "All Service Workers should start shutting down before the " + "ServiceWorkerManager does!"); + + auto shutdownStateId = swm->MaybeInitServiceWorkerShutdownProgress(); + + RefPtr<GenericNonExclusivePromise> promise = + ShutdownInternal(shutdownStateId); + swm->BlockShutdownOn(promise, shutdownStateId); + } + + MOZ_ASSERT(!mControllerChild); +} + +RefPtr<GenericNonExclusivePromise> ServiceWorkerPrivate::ShutdownInternal( + uint32_t aShutdownStateId) { + AssertIsOnMainThread(); + MOZ_ASSERT(mControllerChild); + + mPendingFunctionalEvents.Clear(); + + mControllerChild->get()->RevokeObserver(this); + + if (StaticPrefs::dom_serviceWorkers_testing_enabled()) { + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); + if (os) { + os->NotifyObservers(nullptr, "service-worker-shutdown", nullptr); + } + } + + RefPtr<GenericNonExclusivePromise::Private> promise = + new GenericNonExclusivePromise::Private(__func__); + + Unused << ExecServiceWorkerOp( + ServiceWorkerTerminateWorkerOpArgs(aShutdownStateId), + [promise](ServiceWorkerOpResult&& aResult) { + MOZ_ASSERT(aResult.type() == ServiceWorkerOpResult::Tnsresult); + promise->Resolve(true, __func__); + }, + [promise]() { promise->Reject(NS_ERROR_DOM_ABORT_ERR, __func__); }); + + /** + * After dispatching a termination operation, no new operations should + * be routed through this actor anymore. + */ + mControllerChild = nullptr; + + // Update here, since Evaluation failures directly call ShutdownInternal + UpdateRunning(-1, mHandlesFetch == Enabled ? -1 : 0); + + return promise; +} + +nsresult ServiceWorkerPrivate::ExecServiceWorkerOp( + ServiceWorkerOpArgs&& aArgs, + std::function<void(ServiceWorkerOpResult&&)>&& aSuccessCallback, + std::function<void()>&& aFailureCallback) { + AssertIsOnMainThread(); + MOZ_ASSERT( + aArgs.type() != + ServiceWorkerOpArgs::TParentToChildServiceWorkerFetchEventOpArgs, + "FetchEvent operations should be sent through FetchEventOp(Proxy) " + "actors!"); + MOZ_ASSERT(aSuccessCallback); + + nsresult rv = SpawnWorkerIfNeeded(); + + if (NS_WARN_IF(NS_FAILED(rv))) { + aFailureCallback(); + return rv; + } + + MOZ_ASSERT(mControllerChild); + + RefPtr<ServiceWorkerPrivate> self = this; + RefPtr<RAIIActorPtrHolder> holder = mControllerChild; + RefPtr<KeepAliveToken> token = + aArgs.type() == ServiceWorkerOpArgs::TServiceWorkerTerminateWorkerOpArgs + ? nullptr + : CreateEventKeepAliveToken(); + + /** + * NOTE: moving `aArgs` won't do anything until IPDL `SendMethod()` methods + * can accept rvalue references rather than just const references. + */ + mControllerChild->get()->SendExecServiceWorkerOp(aArgs)->Then( + GetCurrentSerialEventTarget(), __func__, + [self = std::move(self), holder = std::move(holder), + token = std::move(token), onSuccess = std::move(aSuccessCallback), + onFailure = std::move(aFailureCallback)]( + PRemoteWorkerControllerChild::ExecServiceWorkerOpPromise:: + ResolveOrRejectValue&& aResult) { + if (NS_WARN_IF(aResult.IsReject())) { + onFailure(); + return; + } + + onSuccess(std::move(aResult.ResolveValue())); + }); + + return NS_OK; +} + +} // namespace mozilla::dom |