summaryrefslogtreecommitdiffstats
path: root/dom/serviceworkers/ServiceWorkerPrivate.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /dom/serviceworkers/ServiceWorkerPrivate.cpp
parentInitial commit. (diff)
downloadfirefox-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.cpp1686
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