summaryrefslogtreecommitdiffstats
path: root/dom/workers/remoteworkers
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/workers/remoteworkers
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/workers/remoteworkers')
-rw-r--r--dom/workers/remoteworkers/PRemoteWorker.ipdl90
-rw-r--r--dom/workers/remoteworkers/PRemoteWorkerController.ipdl49
-rw-r--r--dom/workers/remoteworkers/PRemoteWorkerService.ipdl25
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerChild.cpp1011
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerChild.h237
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerController.cpp573
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerController.h323
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerControllerChild.cpp149
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerControllerChild.h64
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerControllerParent.cpp215
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerControllerParent.h86
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerManager.cpp735
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerManager.h118
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerParent.cpp201
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerParent.h62
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerService.cpp346
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerService.h123
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerServiceChild.cpp16
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerServiceChild.h34
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerServiceParent.cpp34
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerServiceParent.h41
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerTypes.ipdlh123
-rw-r--r--dom/workers/remoteworkers/moz.build45
23 files changed, 4700 insertions, 0 deletions
diff --git a/dom/workers/remoteworkers/PRemoteWorker.ipdl b/dom/workers/remoteworkers/PRemoteWorker.ipdl
new file mode 100644
index 0000000000..4da11c4840
--- /dev/null
+++ b/dom/workers/remoteworkers/PRemoteWorker.ipdl
@@ -0,0 +1,90 @@
+/* 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 protocol PBackground;
+include protocol PFetchEventOpProxy;
+
+include DOMTypes;
+include ServiceWorkerOpArgs;
+include RemoteWorkerTypes;
+
+namespace mozilla {
+namespace dom {
+
+struct RemoteWorkerSuspendOp
+{};
+
+struct RemoteWorkerResumeOp
+{};
+
+struct RemoteWorkerFreezeOp
+{};
+
+struct RemoteWorkerThawOp
+{};
+
+struct RemoteWorkerTerminateOp
+{};
+
+struct RemoteWorkerPortIdentifierOp
+{
+ MessagePortIdentifier portIdentifier;
+};
+
+struct RemoteWorkerAddWindowIDOp
+{
+ uint64_t windowID;
+};
+
+struct RemoteWorkerRemoveWindowIDOp
+{
+ uint64_t windowID;
+};
+
+union RemoteWorkerOp {
+ RemoteWorkerSuspendOp;
+ RemoteWorkerResumeOp;
+ RemoteWorkerFreezeOp;
+ RemoteWorkerThawOp;
+ RemoteWorkerTerminateOp;
+ RemoteWorkerPortIdentifierOp;
+ RemoteWorkerAddWindowIDOp;
+ RemoteWorkerRemoveWindowIDOp;
+};
+
+// This protocol is used to make a remote worker controllable from the parent
+// process. The parent process will receive operations from the
+// PRemoteWorkerController protocol.
+protocol PRemoteWorker
+{
+ manager PBackground;
+
+ manages PFetchEventOpProxy;
+
+parent:
+ async Created(bool aStatus);
+
+ async Error(ErrorValue aValue);
+
+ async NotifyLock(bool aCreated);
+
+ async NotifyWebTransport(bool aCreated);
+
+ async Close();
+
+ async SetServiceWorkerSkipWaitingFlag() returns (bool aOk);
+
+child:
+ async PFetchEventOpProxy(ParentToChildServiceWorkerFetchEventOpArgs aArgs);
+
+ async __delete__();
+
+ async ExecOp(RemoteWorkerOp op);
+
+ async ExecServiceWorkerOp(ServiceWorkerOpArgs aArgs)
+ returns (ServiceWorkerOpResult aResult);
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/remoteworkers/PRemoteWorkerController.ipdl b/dom/workers/remoteworkers/PRemoteWorkerController.ipdl
new file mode 100644
index 0000000000..5c3818a27d
--- /dev/null
+++ b/dom/workers/remoteworkers/PRemoteWorkerController.ipdl
@@ -0,0 +1,49 @@
+/* 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 protocol PBackground;
+include protocol PFetchEventOp;
+
+include RemoteWorkerTypes;
+include ServiceWorkerOpArgs;
+
+namespace mozilla {
+namespace dom {
+
+/**
+ * Proxy protocol used by ServiceWorkerManager whose canonical state exists on
+ * the main thread to control/receive feedback from RemoteWorkers which are
+ * canonically controlled from the PBackground thread. Exclusively for use from
+ * the parent process main thread to the parent process PBackground thread.
+ */
+[ManualDealloc]
+protocol PRemoteWorkerController {
+ manager PBackground;
+
+ manages PFetchEventOp;
+
+ child:
+ async CreationFailed();
+
+ async CreationSucceeded();
+
+ async ErrorReceived(ErrorValue aError);
+
+ async Terminated();
+
+ async SetServiceWorkerSkipWaitingFlag() returns (bool aOk);
+
+ parent:
+ async PFetchEventOp(ParentToParentServiceWorkerFetchEventOpArgs aArgs);
+
+ async __delete__();
+
+ async Shutdown() returns (bool aOk);
+
+ async ExecServiceWorkerOp(ServiceWorkerOpArgs aArgs)
+ returns (ServiceWorkerOpResult aResult);
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/remoteworkers/PRemoteWorkerService.ipdl b/dom/workers/remoteworkers/PRemoteWorkerService.ipdl
new file mode 100644
index 0000000000..6287bb56a1
--- /dev/null
+++ b/dom/workers/remoteworkers/PRemoteWorkerService.ipdl
@@ -0,0 +1,25 @@
+/* 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 protocol PBackground;
+
+include ProtocolTypes;
+include RemoteWorkerTypes;
+
+namespace mozilla {
+namespace dom {
+
+// Simple protocol to register any active RemoteWorkerService running on any
+// process. Initialization/registration is delayed for preallocated processes
+// until the process takes on its final remoteType.
+protocol PRemoteWorkerService
+{
+ manager PBackground;
+
+parent:
+ async __delete__();
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/remoteworkers/RemoteWorkerChild.cpp b/dom/workers/remoteworkers/RemoteWorkerChild.cpp
new file mode 100644
index 0000000000..163fd00fec
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerChild.cpp
@@ -0,0 +1,1011 @@
+/* -*- 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 "RemoteWorkerChild.h"
+
+#include <utility>
+
+#include "MainThreadUtils.h"
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+#include "nsError.h"
+#include "nsIConsoleReportCollector.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIPrincipal.h"
+#include "nsNetUtil.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+
+#include "RemoteWorkerService.h"
+#include "mozilla/ArrayAlgorithm.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/Services.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/FetchEventOpProxyChild.h"
+#include "mozilla/dom/IndexedDatabaseManager.h"
+#include "mozilla/dom/MessagePort.h"
+#include "mozilla/dom/RemoteWorkerManager.h" // RemoteWorkerManager::IsRemoteTypeAllowed
+#include "mozilla/dom/RemoteWorkerTypes.h"
+#include "mozilla/dom/ServiceWorkerDescriptor.h"
+#include "mozilla/dom/ServiceWorkerInterceptController.h"
+#include "mozilla/dom/ServiceWorkerOp.h"
+#include "mozilla/dom/ServiceWorkerRegistrationDescriptor.h"
+#include "mozilla/dom/ServiceWorkerShutdownState.h"
+#include "mozilla/dom/ServiceWorkerUtils.h"
+#include "mozilla/dom/workerinternals/ScriptLoader.h"
+#include "mozilla/dom/WorkerError.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRef.h"
+#include "mozilla/dom/WorkerRunnable.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/net/CookieJarSettings.h"
+#include "mozilla/PermissionManager.h"
+
+mozilla::LazyLogModule gRemoteWorkerChildLog("RemoteWorkerChild");
+
+#ifdef LOG
+# undef LOG
+#endif
+#define LOG(fmt) MOZ_LOG(gRemoteWorkerChildLog, mozilla::LogLevel::Verbose, fmt)
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+
+using workerinternals::ChannelFromScriptURLMainThread;
+
+namespace {
+
+class SharedWorkerInterfaceRequestor final : public nsIInterfaceRequestor {
+ public:
+ NS_DECL_ISUPPORTS
+
+ SharedWorkerInterfaceRequestor() {
+ // This check must match the code nsDocShell::Create.
+ if (XRE_IsParentProcess()) {
+ mSWController = new ServiceWorkerInterceptController();
+ }
+ }
+
+ NS_IMETHOD
+ GetInterface(const nsIID& aIID, void** aSink) override {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mSWController &&
+ aIID.Equals(NS_GET_IID(nsINetworkInterceptController))) {
+ // If asked for the network intercept controller, ask the outer requestor,
+ // which could be the docshell.
+ RefPtr<ServiceWorkerInterceptController> swController = mSWController;
+ swController.forget(aSink);
+ return NS_OK;
+ }
+
+ return NS_NOINTERFACE;
+ }
+
+ private:
+ ~SharedWorkerInterfaceRequestor() = default;
+
+ RefPtr<ServiceWorkerInterceptController> mSWController;
+};
+
+NS_IMPL_ADDREF(SharedWorkerInterfaceRequestor)
+NS_IMPL_RELEASE(SharedWorkerInterfaceRequestor)
+NS_IMPL_QUERY_INTERFACE(SharedWorkerInterfaceRequestor, nsIInterfaceRequestor)
+
+// Normal runnable because AddPortIdentifier() is going to exec JS code.
+class MessagePortIdentifierRunnable final : public WorkerRunnable {
+ public:
+ MessagePortIdentifierRunnable(WorkerPrivate* aWorkerPrivate,
+ RemoteWorkerChild* aActor,
+ const MessagePortIdentifier& aPortIdentifier)
+ : WorkerRunnable(aWorkerPrivate),
+ mActor(aActor),
+ mPortIdentifier(aPortIdentifier) {}
+
+ private:
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
+ mActor->AddPortIdentifier(aCx, aWorkerPrivate, mPortIdentifier);
+ return true;
+ }
+
+ RefPtr<RemoteWorkerChild> mActor;
+ UniqueMessagePortId mPortIdentifier;
+};
+
+} // anonymous namespace
+
+RemoteWorkerChild::RemoteWorkerChild(const RemoteWorkerData& aData)
+ : mState(VariantType<Pending>(), "RemoteWorkerChild::mState"),
+ mServiceKeepAlive(RemoteWorkerService::MaybeGetKeepAlive()),
+ mIsServiceWorker(aData.serviceWorkerData().type() ==
+ OptionalServiceWorkerData::TServiceWorkerData) {
+ MOZ_ASSERT(RemoteWorkerService::Thread()->IsOnCurrentThread());
+}
+
+RemoteWorkerChild::~RemoteWorkerChild() {
+#ifdef DEBUG
+ auto lock = mState.Lock();
+ MOZ_ASSERT(lock->is<Killed>());
+#endif
+}
+
+void RemoteWorkerChild::ActorDestroy(ActorDestroyReason) {
+ auto launcherData = mLauncherData.Access();
+
+ Unused << NS_WARN_IF(!launcherData->mTerminationPromise.IsEmpty());
+ launcherData->mTerminationPromise.RejectIfExists(NS_ERROR_DOM_ABORT_ERR,
+ __func__);
+
+ auto lock = mState.Lock();
+
+ // If the worker hasn't shutdown or begun shutdown, we need to ensure it gets
+ // canceled.
+ if (NS_WARN_IF(!lock->is<Killed>() && !lock->is<Canceled>())) {
+ // In terms of strong references to this RemoteWorkerChild, at this moment:
+ // - IPC is holding a strong reference that will be dropped in the near
+ // future after this method returns.
+ // - If the worker has been started by ExecWorkerOnMainThread, then
+ // WorkerPrivate::mRemoteWorkerController is a strong reference to us.
+ // If the worker has not been started, ExecWorker's runnable lambda will
+ // have a strong reference that will cover the call to
+ // ExecWorkerOnMainThread.
+ // - The WorkerPrivate cancellation and termination callbacks will also
+ // hold strong references, but those callbacks will not outlive the
+ // WorkerPrivate and are not exposed to callers like
+ // mRemoteWorkerController is.
+ //
+ // Note that this call to RequestWorkerCancellation can still race worker
+ // cancellation, in which case the strong reference obtained by
+ // NewRunnableMethod can end up being the last strong reference.
+ // (RequestWorkerCancellation handles the case that the Worker is already
+ // canceled if this happens.)
+ RefPtr<nsIRunnable> runnable =
+ NewRunnableMethod("RequestWorkerCancellation", this,
+ &RemoteWorkerChild::RequestWorkerCancellation);
+ MOZ_ALWAYS_SUCCEEDS(
+ SchedulerGroup::Dispatch(TaskCategory::Other, runnable.forget()));
+ }
+}
+
+void RemoteWorkerChild::ExecWorker(const RemoteWorkerData& aData) {
+#ifdef DEBUG
+ MOZ_ASSERT(GetActorEventTarget()->IsOnCurrentThread());
+ auto launcherData = mLauncherData.Access();
+ MOZ_ASSERT(CanSend());
+#endif
+
+ RefPtr<RemoteWorkerChild> self = this;
+
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ __func__, [self = std::move(self), data = aData]() mutable {
+ nsresult rv = self->ExecWorkerOnMainThread(std::move(data));
+
+ // Creation failure will already have been reported via the method
+ // above internally using ScopeExit.
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+ });
+
+ MOZ_ALWAYS_SUCCEEDS(
+ SchedulerGroup::Dispatch(TaskCategory::Other, r.forget()));
+}
+
+nsresult RemoteWorkerChild::ExecWorkerOnMainThread(RemoteWorkerData&& aData) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Ensure that the IndexedDatabaseManager is initialized so that if any
+ // workers do any IndexedDB calls that all of IDB's prefs/etc. are
+ // initialized.
+ Unused << NS_WARN_IF(!IndexedDatabaseManager::GetOrCreate());
+
+ auto scopeExit =
+ MakeScopeExit([&] { ExceptionalErrorTransitionDuringExecWorker(); });
+
+ // Verify the the RemoteWorker should be really allowed to run in this
+ // process, and fail if it shouldn't (This shouldn't normally happen,
+ // unless the RemoteWorkerData has been tempered in the process it was
+ // sent from).
+ if (!RemoteWorkerManager::IsRemoteTypeAllowed(aData)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ auto principalOrErr = PrincipalInfoToPrincipal(aData.principalInfo());
+ if (NS_WARN_IF(principalOrErr.isErr())) {
+ return principalOrErr.unwrapErr();
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
+
+ auto loadingPrincipalOrErr =
+ PrincipalInfoToPrincipal(aData.loadingPrincipalInfo());
+ if (NS_WARN_IF(loadingPrincipalOrErr.isErr())) {
+ return loadingPrincipalOrErr.unwrapErr();
+ }
+
+ auto partitionedPrincipalOrErr =
+ PrincipalInfoToPrincipal(aData.partitionedPrincipalInfo());
+ if (NS_WARN_IF(partitionedPrincipalOrErr.isErr())) {
+ return partitionedPrincipalOrErr.unwrapErr();
+ }
+
+ WorkerLoadInfo info;
+ info.mBaseURI = DeserializeURI(aData.baseScriptURL());
+ info.mResolvedScriptURI = DeserializeURI(aData.resolvedScriptURL());
+
+ info.mPrincipalInfo = MakeUnique<PrincipalInfo>(aData.principalInfo());
+ info.mPartitionedPrincipalInfo =
+ MakeUnique<PrincipalInfo>(aData.partitionedPrincipalInfo());
+
+ info.mReferrerInfo = aData.referrerInfo();
+ info.mDomain = aData.domain();
+ info.mTrials = aData.originTrials();
+ info.mPrincipal = principal;
+ info.mPartitionedPrincipal = partitionedPrincipalOrErr.unwrap();
+ info.mLoadingPrincipal = loadingPrincipalOrErr.unwrap();
+ info.mStorageAccess = aData.storageAccess();
+ info.mUseRegularPrincipal = aData.useRegularPrincipal();
+ info.mHasStorageAccessPermissionGranted =
+ aData.hasStorageAccessPermissionGranted();
+ info.mIsThirdPartyContextToTopWindow = aData.isThirdPartyContextToTopWindow();
+ info.mOriginAttributes =
+ BasePrincipal::Cast(principal)->OriginAttributesRef();
+ info.mShouldResistFingerprinting = aData.shouldResistFingerprinting();
+ net::CookieJarSettings::Deserialize(aData.cookieJarSettings(),
+ getter_AddRefs(info.mCookieJarSettings));
+ info.mCookieJarSettingsArgs = aData.cookieJarSettings();
+
+ // Default CSP permissions for now. These will be overrided if necessary
+ // based on the script CSP headers during load in ScriptLoader.
+ info.mEvalAllowed = true;
+ info.mReportEvalCSPViolations = false;
+ info.mWasmEvalAllowed = true;
+ info.mReportWasmEvalCSPViolations = false;
+ info.mSecureContext = aData.isSecureContext()
+ ? WorkerLoadInfo::eSecureContext
+ : WorkerLoadInfo::eInsecureContext;
+
+ WorkerPrivate::OverrideLoadInfoLoadGroup(info, info.mLoadingPrincipal);
+
+ RefPtr<SharedWorkerInterfaceRequestor> requestor =
+ new SharedWorkerInterfaceRequestor();
+ info.mInterfaceRequestor->SetOuterRequestor(requestor);
+
+ Maybe<ClientInfo> clientInfo;
+ if (aData.clientInfo().isSome()) {
+ clientInfo.emplace(ClientInfo(aData.clientInfo().ref()));
+ }
+
+ nsresult rv = NS_OK;
+
+ if (clientInfo.isSome()) {
+ Maybe<mozilla::ipc::CSPInfo> cspInfo = clientInfo.ref().GetCspInfo();
+ if (cspInfo.isSome()) {
+ info.mCSP = CSPInfoToCSP(cspInfo.ref(), nullptr);
+ info.mCSPInfo = MakeUnique<CSPInfo>();
+ rv = CSPToCSPInfo(info.mCSP, info.mCSPInfo.get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ }
+
+ rv = info.SetPrincipalsAndCSPOnMainThread(
+ info.mPrincipal, info.mPartitionedPrincipal, info.mLoadGroup, info.mCSP);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsString workerPrivateId;
+
+ if (mIsServiceWorker) {
+ ServiceWorkerData& data = aData.serviceWorkerData().get_ServiceWorkerData();
+
+ MOZ_ASSERT(!data.id().IsEmpty());
+ workerPrivateId = std::move(data.id());
+
+ info.mServiceWorkerCacheName = data.cacheName();
+ info.mServiceWorkerDescriptor.emplace(data.descriptor());
+ info.mServiceWorkerRegistrationDescriptor.emplace(
+ data.registrationDescriptor());
+ info.mLoadFlags = static_cast<nsLoadFlags>(data.loadFlags());
+ } else {
+ // Top level workers' main script use the document charset for the script
+ // uri encoding.
+ rv = ChannelFromScriptURLMainThread(
+ info.mLoadingPrincipal, nullptr /* parent document */, info.mLoadGroup,
+ info.mResolvedScriptURI, aData.type(), aData.credentials(), clientInfo,
+ nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER, info.mCookieJarSettings,
+ info.mReferrerInfo, getter_AddRefs(info.mChannel));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ info.mAgentClusterId = aData.agentClusterId();
+
+ AutoJSAPI jsapi;
+ jsapi.Init();
+
+ ErrorResult error;
+ RefPtr<RemoteWorkerChild> self = this;
+ RefPtr<WorkerPrivate> workerPrivate = WorkerPrivate::Constructor(
+ jsapi.cx(), aData.originalScriptURL(), false,
+ mIsServiceWorker ? WorkerKindService : WorkerKindShared,
+ aData.credentials(), aData.type(), aData.name(), VoidCString(), &info,
+ error, std::move(workerPrivateId),
+ [self](bool aEverRan) {
+ self->OnWorkerCancellationTransitionStateFromPendingOrRunningToCanceled();
+ },
+ // This will be invoked here on the main thread when the worker is already
+ // fully shutdown. This replaces a prior approach where we would
+ // begin to transition when the worker thread would reach the Canceling
+ // state. This lambda ensures that we not only wait for the Killing state
+ // to be reached but that the global shutdown has already occurred.
+ [self]() { self->TransitionStateFromCanceledToKilled(); });
+
+ if (NS_WARN_IF(error.Failed())) {
+ MOZ_ASSERT(!workerPrivate);
+
+ rv = error.StealNSResult();
+ return rv;
+ }
+
+ workerPrivate->SetRemoteWorkerController(this);
+
+ // This wants to run as a normal task sequentially after the top level script
+ // evaluation, so the hybrid target is the correct choice between hybrid and
+ // `ControlEventTarget`.
+ nsCOMPtr<nsISerialEventTarget> workerTarget =
+ workerPrivate->HybridEventTarget();
+
+ nsCOMPtr<nsIRunnable> runnable = NewCancelableRunnableMethod(
+ "InitialzeOnWorker", this, &RemoteWorkerChild::InitializeOnWorker);
+
+ {
+ MOZ_ASSERT(workerPrivate);
+ auto lock = mState.Lock();
+ // We MUST be pending here, so direct access is ok.
+ lock->as<Pending>().mWorkerPrivate = std::move(workerPrivate);
+ }
+
+ if (mIsServiceWorker) {
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ __func__, [workerTarget,
+ initializeWorkerRunnable = std::move(runnable)]() mutable {
+ Unused << NS_WARN_IF(NS_FAILED(
+ workerTarget->Dispatch(initializeWorkerRunnable.forget())));
+ });
+
+ RefPtr<PermissionManager> permissionManager =
+ PermissionManager::GetInstance();
+ if (!permissionManager) {
+ return NS_ERROR_FAILURE;
+ }
+ permissionManager->WhenPermissionsAvailable(principal, r);
+ } else {
+ if (NS_WARN_IF(NS_FAILED(workerTarget->Dispatch(runnable.forget())))) {
+ rv = NS_ERROR_FAILURE;
+ return rv;
+ }
+ }
+
+ scopeExit.release();
+
+ return NS_OK;
+}
+
+void RemoteWorkerChild::RequestWorkerCancellation() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ LOG(("RequestWorkerCancellation[this=%p]", this));
+
+ // We want to ensure that we've requested the worker be canceled. So if the
+ // worker is running, cancel it. We can't do this with the lock held,
+ // however, because our lambdas will want to manipulate the state.
+ RefPtr<WorkerPrivate> cancelWith;
+ {
+ auto lock = mState.Lock();
+ if (lock->is<Pending>()) {
+ cancelWith = lock->as<Pending>().mWorkerPrivate;
+ } else if (lock->is<Running>()) {
+ cancelWith = lock->as<Running>().mWorkerPrivate;
+ }
+ }
+
+ if (cancelWith) {
+ cancelWith->Cancel();
+ }
+}
+
+// This method will be invoked on the worker after the top-level
+// CompileScriptRunnable task has succeeded and as long as the worker has not
+// been closed/canceled. There are edge-cases related to cancellation, but we
+// have our caller ensure that we are only called as long as the worker's state
+// is Running.
+//
+// (https://bugzilla.mozilla.org/show_bug.cgi?id=1800659 will eliminate
+// cancellation, and the documentation around that bug / in design documents
+// helps provide more context about this.)
+void RemoteWorkerChild::InitializeOnWorker() {
+ nsCOMPtr<nsIRunnable> r =
+ NewRunnableMethod("TransitionStateToRunning", this,
+ &RemoteWorkerChild::TransitionStateToRunning);
+ MOZ_ALWAYS_SUCCEEDS(
+ SchedulerGroup::Dispatch(TaskCategory::Other, r.forget()));
+}
+
+RefPtr<GenericNonExclusivePromise> RemoteWorkerChild::GetTerminationPromise() {
+ auto launcherData = mLauncherData.Access();
+ return launcherData->mTerminationPromise.Ensure(__func__);
+}
+
+void RemoteWorkerChild::CreationSucceededOnAnyThread() {
+ CreationSucceededOrFailedOnAnyThread(true);
+}
+
+void RemoteWorkerChild::CreationFailedOnAnyThread() {
+ CreationSucceededOrFailedOnAnyThread(false);
+}
+
+void RemoteWorkerChild::CreationSucceededOrFailedOnAnyThread(
+ bool aDidCreationSucceed) {
+#ifdef DEBUG
+ {
+ auto lock = mState.Lock();
+ MOZ_ASSERT_IF(aDidCreationSucceed, lock->is<Running>());
+ MOZ_ASSERT_IF(!aDidCreationSucceed, lock->is<Killed>());
+ }
+#endif
+
+ RefPtr<RemoteWorkerChild> self = this;
+
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ __func__,
+ [self = std::move(self), didCreationSucceed = aDidCreationSucceed] {
+ auto launcherData = self->mLauncherData.Access();
+
+ if (!self->CanSend() || launcherData->mDidSendCreated) {
+ return;
+ }
+
+ Unused << self->SendCreated(didCreationSucceed);
+ launcherData->mDidSendCreated = true;
+ });
+
+ GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+}
+
+void RemoteWorkerChild::CloseWorkerOnMainThread() {
+ AssertIsOnMainThread();
+
+ LOG(("CloseWorkerOnMainThread[this=%p]", this));
+
+ // We can't hold the state lock while calling WorkerPrivate::Cancel because
+ // the lambda callback will want to touch the state, so save off the
+ // WorkerPrivate so we can cancel it (if we need to cancel it).
+ RefPtr<WorkerPrivate> cancelWith;
+ {
+ auto lock = mState.Lock();
+
+ if (lock->is<Pending>()) {
+ cancelWith = lock->as<Pending>().mWorkerPrivate;
+ // There should be no way for this code to run before we
+ // ExecWorkerOnMainThread runs, which means that either it should have
+ // set a WorkerPrivate on Pending, or its error handling should already
+ // have transitioned us to Canceled and Killing in that order. (It's
+ // also possible that it assigned a WorkerPrivate and subsequently we
+ // transitioned to Running, which would put us in the next branch.)
+ MOZ_DIAGNOSTIC_ASSERT(cancelWith);
+ } else if (lock->is<Running>()) {
+ cancelWith = lock->as<Running>().mWorkerPrivate;
+ }
+ }
+
+ // It's very okay for us to not have a WorkerPrivate here if we've already
+ // canceled the worker or if errors happened.
+ if (cancelWith) {
+ cancelWith->Cancel();
+ }
+}
+
+/**
+ * Error reporting method
+ */
+void RemoteWorkerChild::ErrorPropagation(const ErrorValue& aValue) {
+ MOZ_ASSERT(GetActorEventTarget()->IsOnCurrentThread());
+
+ if (!CanSend()) {
+ return;
+ }
+
+ Unused << SendError(aValue);
+}
+
+void RemoteWorkerChild::ErrorPropagationDispatch(nsresult aError) {
+ MOZ_ASSERT(NS_FAILED(aError));
+
+ RefPtr<RemoteWorkerChild> self = this;
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ "RemoteWorkerChild::ErrorPropagationDispatch",
+ [self = std::move(self), aError]() { self->ErrorPropagation(aError); });
+
+ GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+}
+
+void RemoteWorkerChild::ErrorPropagationOnMainThread(
+ const WorkerErrorReport* aReport, bool aIsErrorEvent) {
+ AssertIsOnMainThread();
+
+ ErrorValue value;
+ if (aIsErrorEvent) {
+ ErrorData data(
+ aReport->mIsWarning, aReport->mLineNumber, aReport->mColumnNumber,
+ aReport->mMessage, aReport->mFilename, aReport->mLine,
+ TransformIntoNewArray(aReport->mNotes, [](const WorkerErrorNote& note) {
+ return ErrorDataNote(note.mLineNumber, note.mColumnNumber,
+ note.mMessage, note.mFilename);
+ }));
+ value = data;
+ } else {
+ value = void_t();
+ }
+
+ RefPtr<RemoteWorkerChild> self = this;
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ "RemoteWorkerChild::ErrorPropagationOnMainThread",
+ [self = std::move(self), value]() { self->ErrorPropagation(value); });
+
+ GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+}
+
+void RemoteWorkerChild::NotifyLock(bool aCreated) {
+ nsCOMPtr<nsIRunnable> r =
+ NS_NewRunnableFunction(__func__, [self = RefPtr(this), aCreated] {
+ if (!self->CanSend()) {
+ return;
+ }
+
+ Unused << self->SendNotifyLock(aCreated);
+ });
+
+ GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+}
+
+void RemoteWorkerChild::NotifyWebTransport(bool aCreated) {
+ nsCOMPtr<nsIRunnable> r =
+ NS_NewRunnableFunction(__func__, [self = RefPtr(this), aCreated] {
+ if (!self->CanSend()) {
+ return;
+ }
+
+ Unused << self->SendNotifyWebTransport(aCreated);
+ });
+
+ GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+}
+
+void RemoteWorkerChild::FlushReportsOnMainThread(
+ nsIConsoleReportCollector* aReporter) {
+ AssertIsOnMainThread();
+
+ bool reportErrorToBrowserConsole = true;
+
+ // Flush the reports.
+ for (uint32_t i = 0, len = mWindowIDs.Length(); i < len; ++i) {
+ aReporter->FlushReportsToConsole(
+ mWindowIDs[i], nsIConsoleReportCollector::ReportAction::Save);
+ reportErrorToBrowserConsole = false;
+ }
+
+ // Finally report to browser console if there is no any window.
+ if (reportErrorToBrowserConsole) {
+ aReporter->FlushReportsToConsole(0);
+ return;
+ }
+
+ aReporter->ClearConsoleReports();
+}
+
+/**
+ * Worker state transition methods
+ */
+RemoteWorkerChild::WorkerPrivateAccessibleState::
+ ~WorkerPrivateAccessibleState() {
+ // We should now only be performing state transitions on the main thread, so
+ // we should assert we're only releasing on the main thread.
+ MOZ_ASSERT(!mWorkerPrivate || NS_IsMainThread());
+ // mWorkerPrivate can be safely released on the main thread.
+ if (!mWorkerPrivate || NS_IsMainThread()) {
+ return;
+ }
+
+ // But as a backstop, do proxy the release to the main thread.
+ NS_ReleaseOnMainThread(
+ "RemoteWorkerChild::WorkerPrivateAccessibleState::mWorkerPrivate",
+ mWorkerPrivate.forget());
+}
+
+void RemoteWorkerChild::
+ OnWorkerCancellationTransitionStateFromPendingOrRunningToCanceled() {
+ auto lock = mState.Lock();
+
+ LOG(("TransitionStateFromPendingOrRunningToCanceled[this=%p]", this));
+
+ if (lock->is<Pending>()) {
+ TransitionStateFromPendingToCanceled(lock.ref());
+ } else if (lock->is<Running>()) {
+ *lock = VariantType<Canceled>();
+ } else {
+ MOZ_ASSERT(false, "State should have been Pending or Running");
+ }
+}
+
+void RemoteWorkerChild::TransitionStateFromPendingToCanceled(State& aState) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aState.is<Pending>());
+ LOG(("TransitionStateFromPendingToCanceled[this=%p]", this));
+
+ CancelAllPendingOps(aState);
+
+ aState = VariantType<Canceled>();
+}
+
+void RemoteWorkerChild::TransitionStateFromCanceledToKilled() {
+ AssertIsOnMainThread();
+
+ LOG(("TransitionStateFromCanceledToKilled[this=%p]", this));
+
+ auto lock = mState.Lock();
+ MOZ_ASSERT(lock->is<Canceled>());
+
+ *lock = VariantType<Killed>();
+
+ RefPtr<RemoteWorkerChild> self = this;
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(__func__, [self]() {
+ auto launcherData = self->mLauncherData.Access();
+
+ // (We maintain the historical ordering of resolving this promise prior to
+ // calling SendClose, however the previous code used 2 separate dispatches
+ // to this thread for the resolve and SendClose, and there inherently
+ // would be a race between the runnables resulting from the resolved
+ // promise and the promise containing the call to SendClose. Now it's
+ // entirely clear that our call to SendClose will effectively run before
+ // any of the resolved promises are able to do anything.)
+ launcherData->mTerminationPromise.ResolveIfExists(true, __func__);
+
+ if (self->CanSend()) {
+ Unused << self->SendClose();
+ }
+ });
+
+ GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+}
+
+void RemoteWorkerChild::TransitionStateToRunning() {
+ AssertIsOnMainThread();
+
+ LOG(("TransitionStateToRunning[this=%p]", this));
+
+ nsTArray<RefPtr<Op>> pendingOps;
+
+ {
+ auto lock = mState.Lock();
+
+ // Because this is an async notification sent from the worker to the main
+ // thread, it's very possible that we've already decided on the main thread
+ // to transition to the Canceled state, in which case there is nothing for
+ // us to do here.
+ if (!lock->is<Pending>()) {
+ LOG(("State is already not pending in TransitionStateToRunning[this=%p]!",
+ this));
+ return;
+ }
+
+ RefPtr<WorkerPrivate> workerPrivate =
+ std::move(lock->as<Pending>().mWorkerPrivate);
+ pendingOps = std::move(lock->as<Pending>().mPendingOps);
+
+ // Move the worker private into place to avoid gratuitous ref churn; prior
+ // comments here suggest the Variant can't accept a move.
+ *lock = VariantType<Running>();
+ lock->as<Running>().mWorkerPrivate = std::move(workerPrivate);
+ }
+
+ CreationSucceededOnAnyThread();
+
+ RefPtr<RemoteWorkerChild> self = this;
+ for (auto& op : pendingOps) {
+ op->StartOnMainThread(self);
+ }
+}
+
+void RemoteWorkerChild::ExceptionalErrorTransitionDuringExecWorker() {
+ AssertIsOnMainThread();
+
+ LOG(("ExceptionalErrorTransitionDuringExecWorker[this=%p]", this));
+
+ // This method is called synchronously by ExecWorkerOnMainThread in the event
+ // of any error. Because we only transition to Running on the main thread
+ // as the result of a notification from the worker, we know our state will be
+ // Pending, but mWorkerPrivate may or may not be null, as we may not have
+ // gotten to spawning the worker.
+ //
+ // In the event the worker exists, we need to Cancel() it. We must do this
+ // without the lock held because our call to Cancel() will invoke the
+ // cancellation callback we created which will call TransitionStateToCanceled,
+ // and we can't be holding the lock when that happens.
+
+ RefPtr<WorkerPrivate> cancelWith;
+
+ {
+ auto lock = mState.Lock();
+
+ MOZ_ASSERT(lock->is<Pending>());
+ if (lock->is<Pending>()) {
+ cancelWith = lock->as<Pending>().mWorkerPrivate;
+ if (!cancelWith) {
+ // The worker wasn't actually created, so we should synthetically
+ // transition to canceled and onward. Since we have the lock,
+ // perform the transition now for clarity, but we'll handle the rest of
+ // this case after dropping the lock.
+ TransitionStateFromPendingToCanceled(lock.ref());
+ }
+ }
+ }
+
+ if (cancelWith) {
+ cancelWith->Cancel();
+ } else {
+ TransitionStateFromCanceledToKilled();
+ CreationFailedOnAnyThread();
+ }
+}
+
+/**
+ * Operation execution classes/methods
+ */
+class RemoteWorkerChild::SharedWorkerOp : public RemoteWorkerChild::Op {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedWorkerOp, override)
+
+ explicit SharedWorkerOp(RemoteWorkerOp&& aOp) : mOp(std::move(aOp)) {}
+
+ bool MaybeStart(RemoteWorkerChild* aOwner,
+ RemoteWorkerChild::State& aState) override {
+ MOZ_ASSERT(!mStarted);
+ MOZ_ASSERT(aOwner);
+ // Thread: We are on the Worker Launcher thread.
+
+ // Return false, indicating we should queue this op if our current state is
+ // pending and this isn't a termination op (which should skip the line).
+ if (aState.is<Pending>() && !IsTerminationOp()) {
+ return false;
+ }
+
+ // If the worker is already shutting down (which should be unexpected
+ // because we should be told new operations after a termination op), just
+ // return true to indicate the op should be discarded.
+ if (aState.is<Canceled>() || aState.is<Killed>()) {
+#ifdef DEBUG
+ mStarted = true;
+#endif
+
+ return true;
+ }
+
+ MOZ_ASSERT(aState.is<Running>() || IsTerminationOp());
+
+ RefPtr<SharedWorkerOp> self = this;
+ RefPtr<RemoteWorkerChild> owner = aOwner;
+
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ __func__, [self = std::move(self), owner = std::move(owner)]() mutable {
+ {
+ auto lock = owner->mState.Lock();
+
+ if (NS_WARN_IF(lock->is<Canceled>() || lock->is<Killed>())) {
+ self->Cancel();
+ return;
+ }
+ }
+
+ self->StartOnMainThread(owner);
+ });
+
+ MOZ_ALWAYS_SUCCEEDS(
+ SchedulerGroup::Dispatch(TaskCategory::Other, r.forget()));
+
+#ifdef DEBUG
+ mStarted = true;
+#endif
+
+ return true;
+ }
+
+ void StartOnMainThread(RefPtr<RemoteWorkerChild>& aOwner) final {
+ using Running = RemoteWorkerChild::Running;
+
+ AssertIsOnMainThread();
+
+ if (IsTerminationOp()) {
+ aOwner->CloseWorkerOnMainThread();
+ return;
+ }
+
+ auto lock = aOwner->mState.Lock();
+ MOZ_ASSERT(lock->is<Running>());
+ if (!lock->is<Running>()) {
+ aOwner->ErrorPropagationDispatch(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ RefPtr<WorkerPrivate> workerPrivate = lock->as<Running>().mWorkerPrivate;
+
+ MOZ_ASSERT(workerPrivate);
+
+ if (mOp.type() == RemoteWorkerOp::TRemoteWorkerSuspendOp) {
+ workerPrivate->ParentWindowPaused();
+ } else if (mOp.type() == RemoteWorkerOp::TRemoteWorkerResumeOp) {
+ workerPrivate->ParentWindowResumed();
+ } else if (mOp.type() == RemoteWorkerOp::TRemoteWorkerFreezeOp) {
+ workerPrivate->Freeze(nullptr);
+ } else if (mOp.type() == RemoteWorkerOp::TRemoteWorkerThawOp) {
+ workerPrivate->Thaw(nullptr);
+ } else if (mOp.type() == RemoteWorkerOp::TRemoteWorkerPortIdentifierOp) {
+ RefPtr<MessagePortIdentifierRunnable> r =
+ new MessagePortIdentifierRunnable(
+ workerPrivate, aOwner,
+ mOp.get_RemoteWorkerPortIdentifierOp().portIdentifier());
+
+ if (NS_WARN_IF(!r->Dispatch())) {
+ aOwner->ErrorPropagationDispatch(NS_ERROR_FAILURE);
+ }
+ } else if (mOp.type() == RemoteWorkerOp::TRemoteWorkerAddWindowIDOp) {
+ aOwner->mWindowIDs.AppendElement(
+ mOp.get_RemoteWorkerAddWindowIDOp().windowID());
+ } else if (mOp.type() == RemoteWorkerOp::TRemoteWorkerRemoveWindowIDOp) {
+ aOwner->mWindowIDs.RemoveElement(
+ mOp.get_RemoteWorkerRemoveWindowIDOp().windowID());
+ } else {
+ MOZ_CRASH("Unknown RemoteWorkerOp type!");
+ }
+ }
+
+ void Cancel() override {
+#ifdef DEBUG
+ mStarted = true;
+#endif
+ }
+
+ private:
+ ~SharedWorkerOp() { MOZ_ASSERT(mStarted); }
+
+ bool IsTerminationOp() const {
+ return mOp.type() == RemoteWorkerOp::TRemoteWorkerTerminateOp;
+ }
+
+ RemoteWorkerOp mOp;
+
+#ifdef DEBUG
+ bool mStarted = false;
+#endif
+};
+
+void RemoteWorkerChild::AddPortIdentifier(
+ JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+ UniqueMessagePortId& aPortIdentifier) {
+ if (NS_WARN_IF(!aWorkerPrivate->ConnectMessagePort(aCx, aPortIdentifier))) {
+ ErrorPropagationDispatch(NS_ERROR_FAILURE);
+ }
+}
+
+void RemoteWorkerChild::CancelAllPendingOps(State& aState) {
+ MOZ_ASSERT(aState.is<Pending>());
+
+ auto pendingOps = std::move(aState.as<Pending>().mPendingOps);
+
+ for (auto& op : pendingOps) {
+ op->Cancel();
+ }
+}
+
+void RemoteWorkerChild::MaybeStartOp(RefPtr<Op>&& aOp) {
+ MOZ_ASSERT(aOp);
+
+ auto lock = mState.Lock();
+
+ if (!aOp->MaybeStart(this, lock.ref())) {
+ // Maybestart returns false only if we are <Pending>.
+ lock->as<Pending>().mPendingOps.AppendElement(std::move(aOp));
+ }
+}
+
+IPCResult RemoteWorkerChild::RecvExecOp(RemoteWorkerOp&& aOp) {
+ MOZ_ASSERT(!mIsServiceWorker);
+
+ MaybeStartOp(new SharedWorkerOp(std::move(aOp)));
+
+ return IPC_OK();
+}
+
+IPCResult RemoteWorkerChild::RecvExecServiceWorkerOp(
+ ServiceWorkerOpArgs&& aArgs, ExecServiceWorkerOpResolver&& aResolve) {
+ MOZ_ASSERT(mIsServiceWorker);
+ MOZ_ASSERT(
+ aArgs.type() !=
+ ServiceWorkerOpArgs::TParentToChildServiceWorkerFetchEventOpArgs,
+ "FetchEvent operations should be sent via PFetchEventOp(Proxy) actors!");
+
+ MaybeReportServiceWorkerShutdownProgress(aArgs);
+
+ MaybeStartOp(ServiceWorkerOp::Create(std::move(aArgs), std::move(aResolve)));
+
+ return IPC_OK();
+}
+
+RefPtr<GenericPromise>
+RemoteWorkerChild::MaybeSendSetServiceWorkerSkipWaitingFlag() {
+ RefPtr<GenericPromise::Private> promise =
+ new GenericPromise::Private(__func__);
+
+ RefPtr<RemoteWorkerChild> self = this;
+
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(__func__, [self = std::move(
+ self),
+ promise] {
+ if (!self->CanSend()) {
+ promise->Reject(NS_ERROR_DOM_ABORT_ERR, __func__);
+ return;
+ }
+
+ self->SendSetServiceWorkerSkipWaitingFlag()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [promise](
+ const SetServiceWorkerSkipWaitingFlagPromise::ResolveOrRejectValue&
+ aResult) {
+ if (NS_WARN_IF(aResult.IsReject())) {
+ promise->Reject(NS_ERROR_DOM_ABORT_ERR, __func__);
+ return;
+ }
+
+ promise->Resolve(aResult.ResolveValue(), __func__);
+ });
+ });
+
+ GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+
+ return promise;
+}
+
+/**
+ * PFetchEventOpProxy methods
+ */
+already_AddRefed<PFetchEventOpProxyChild>
+RemoteWorkerChild::AllocPFetchEventOpProxyChild(
+ const ParentToChildServiceWorkerFetchEventOpArgs& aArgs) {
+ return RefPtr{new FetchEventOpProxyChild()}.forget();
+}
+
+IPCResult RemoteWorkerChild::RecvPFetchEventOpProxyConstructor(
+ PFetchEventOpProxyChild* aActor,
+ const ParentToChildServiceWorkerFetchEventOpArgs& aArgs) {
+ MOZ_ASSERT(aActor);
+
+ (static_cast<FetchEventOpProxyChild*>(aActor))->Initialize(aArgs);
+
+ return IPC_OK();
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/remoteworkers/RemoteWorkerChild.h b/dom/workers/remoteworkers/RemoteWorkerChild.h
new file mode 100644
index 0000000000..78fc43246e
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerChild.h
@@ -0,0 +1,237 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_RemoteWorkerChild_h
+#define mozilla_dom_RemoteWorkerChild_h
+
+#include "nsCOMPtr.h"
+#include "nsISupportsImpl.h"
+#include "nsTArray.h"
+
+#include "mozilla/DataMutex.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ThreadBound.h"
+#include "mozilla/dom/PRemoteWorkerChild.h"
+#include "mozilla/dom/ServiceWorkerOpArgs.h"
+
+class nsISerialEventTarget;
+class nsIConsoleReportCollector;
+
+namespace mozilla::dom {
+
+class ErrorValue;
+class FetchEventOpProxyChild;
+class RemoteWorkerData;
+class RemoteWorkerServiceKeepAlive;
+class ServiceWorkerOp;
+class UniqueMessagePortId;
+class WeakWorkerRef;
+class WorkerErrorReport;
+class WorkerPrivate;
+
+/**
+ * Background-managed "Worker Launcher"-thread-resident created via the
+ * RemoteWorkerManager to actually spawn the worker. Currently, the worker will
+ * be spawned from the main thread due to nsIPrincipal not being able to be
+ * created on background threads and other ownership invariants, most of which
+ * can be relaxed in the future.
+ */
+class RemoteWorkerChild final : public PRemoteWorkerChild {
+ friend class FetchEventOpProxyChild;
+ friend class PRemoteWorkerChild;
+ friend class ServiceWorkerOp;
+
+ ~RemoteWorkerChild();
+
+ public:
+ // Note that all IPC-using methods must only be invoked on the
+ // RemoteWorkerService thread which the inherited
+ // IProtocol::GetActorEventTarget() will return for us.
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RemoteWorkerChild, final)
+
+ explicit RemoteWorkerChild(const RemoteWorkerData& aData);
+
+ void ExecWorker(const RemoteWorkerData& aData);
+
+ void ErrorPropagationOnMainThread(const WorkerErrorReport* aReport,
+ bool aIsErrorEvent);
+
+ void NotifyLock(bool aCreated);
+
+ void NotifyWebTransport(bool aCreated);
+
+ void FlushReportsOnMainThread(nsIConsoleReportCollector* aReporter);
+
+ void AddPortIdentifier(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+ UniqueMessagePortId& aPortIdentifier);
+
+ RefPtr<GenericNonExclusivePromise> GetTerminationPromise();
+
+ RefPtr<GenericPromise> MaybeSendSetServiceWorkerSkipWaitingFlag();
+
+ const nsTArray<uint64_t>& WindowIDs() const { return mWindowIDs; }
+
+ private:
+ class InitializeWorkerRunnable;
+
+ class Op;
+ class SharedWorkerOp;
+
+ struct WorkerPrivateAccessibleState {
+ ~WorkerPrivateAccessibleState();
+ RefPtr<WorkerPrivate> mWorkerPrivate;
+ };
+
+ // Initial state, mWorkerPrivate is initially null but will be initialized on
+ // the main thread by ExecWorkerOnMainThread when the WorkerPrivate is
+ // created. The state will transition to Running or Canceled, also from the
+ // main thread.
+ struct Pending : WorkerPrivateAccessibleState {
+ nsTArray<RefPtr<Op>> mPendingOps;
+ };
+
+ // Running, with the state transition happening on the main thread as a result
+ // of the worker successfully processing our initialization runnable,
+ // indicating that top-level script execution successfully completed. Because
+ // all of our state transitions happen on the main thread and are posed in
+ // terms of the main thread's perspective of the worker's state, it's very
+ // possible for us to skip directly from Pending to Canceled because we decide
+ // to cancel/terminate the worker prior to it finishing script loading or
+ // reporting back to us.
+ struct Running : WorkerPrivateAccessibleState {};
+
+ // Cancel() has been called on the WorkerPrivate on the main thread by a
+ // TerminationOp, top-level script evaluation has failed and canceled the
+ // worker, or in the case of a SharedWorker, close() has been called on
+ // the global scope by content code and the worker has advanced to the
+ // Canceling state. (Dedicated Workers can also self close, but they will
+ // never be RemoteWorkers. Although a SharedWorker can own DedicatedWorkers.)
+ // Browser shutdown will result in a TerminationOp thanks to use of a shutdown
+ // blocker in the parent, so the RuntimeService shouldn't get involved, but we
+ // would also handle that case acceptably too.
+ //
+ // Because worker self-closing is still handled by dispatching a runnable to
+ // the main thread to effectively call WorkerPrivate::Cancel(), there isn't
+ // a race between a worker deciding to self-close and our termination ops.
+ //
+ // In this state, we have dropped the reference to the WorkerPrivate and will
+ // no longer be dispatching runnables to the worker. We wait in this state
+ // until the termination lambda is invoked letting us know that the worker has
+ // entirely shutdown and we can advanced to the Killed state.
+ struct Canceled {};
+
+ // The worker termination lambda has been invoked and we know the Worker is
+ // entirely shutdown. (Inherently it is possible for us to advance to this
+ // state while the nsThread for the worker is still in the process of
+ // shutting down, but no more worker code will run on it.)
+ //
+ // This name is chosen to match the Worker's own state model.
+ struct Killed {};
+
+ using State = Variant<Pending, Running, Canceled, Killed>;
+
+ // The state of the WorkerPrivate as perceived by the owner on the main
+ // thread. All state transitions now happen on the main thread, but the
+ // Worker Launcher thread will consult the state and will directly append ops
+ // to the Pending queue
+ DataMutex<State> mState;
+
+ const RefPtr<RemoteWorkerServiceKeepAlive> mServiceKeepAlive;
+
+ class Op {
+ public:
+ NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
+
+ virtual ~Op() = default;
+
+ virtual bool MaybeStart(RemoteWorkerChild* aOwner, State& aState) = 0;
+
+ virtual void StartOnMainThread(RefPtr<RemoteWorkerChild>& aOwner) = 0;
+
+ virtual void Cancel() = 0;
+ };
+
+ void ActorDestroy(ActorDestroyReason) override;
+
+ mozilla::ipc::IPCResult RecvExecOp(RemoteWorkerOp&& aOp);
+
+ mozilla::ipc::IPCResult RecvExecServiceWorkerOp(
+ ServiceWorkerOpArgs&& aArgs, ExecServiceWorkerOpResolver&& aResolve);
+
+ already_AddRefed<PFetchEventOpProxyChild> AllocPFetchEventOpProxyChild(
+ const ParentToChildServiceWorkerFetchEventOpArgs& aArgs);
+
+ mozilla::ipc::IPCResult RecvPFetchEventOpProxyConstructor(
+ PFetchEventOpProxyChild* aActor,
+ const ParentToChildServiceWorkerFetchEventOpArgs& aArgs) override;
+
+ nsresult ExecWorkerOnMainThread(RemoteWorkerData&& aData);
+
+ void ExceptionalErrorTransitionDuringExecWorker();
+
+ void RequestWorkerCancellation();
+
+ void InitializeOnWorker();
+
+ void CreationSucceededOnAnyThread();
+
+ void CreationFailedOnAnyThread();
+
+ void CreationSucceededOrFailedOnAnyThread(bool aDidCreationSucceed);
+
+ // Cancels the worker if it has been started and ensures that we transition
+ // to the Terminated state once the worker has been terminated or we have
+ // ensured that it will never start.
+ void CloseWorkerOnMainThread();
+
+ void ErrorPropagation(const ErrorValue& aValue);
+
+ void ErrorPropagationDispatch(nsresult aError);
+
+ // When the WorkerPrivate Cancellation lambda is invoked, it's possible that
+ // we have not yet advanced to running from pending, so we could be in either
+ // state. This method is expected to be called by the Workers' cancellation
+ // lambda and will obtain the lock and call the
+ // TransitionStateFromPendingToCanceled if appropriate. Otherwise it will
+ // directly move from the running state to the canceled state which does not
+ // require additional cleanup.
+ void OnWorkerCancellationTransitionStateFromPendingOrRunningToCanceled();
+ // A helper used by the above method by the worker cancellation lambda if the
+ // the worker hasn't started running, or in exceptional cases where we bail
+ // out of the ExecWorker method early. The caller must be holding the lock
+ // (in order to pass in the state).
+ void TransitionStateFromPendingToCanceled(State& aState);
+ void TransitionStateFromCanceledToKilled();
+
+ void TransitionStateToRunning();
+
+ void TransitionStateToTerminated();
+
+ void TransitionStateToTerminated(State& aState);
+
+ void CancelAllPendingOps(State& aState);
+
+ void MaybeStartOp(RefPtr<Op>&& aOp);
+
+ const bool mIsServiceWorker;
+
+ // Touched on main-thread only.
+ nsTArray<uint64_t> mWindowIDs;
+
+ struct LauncherBoundData {
+ MozPromiseHolder<GenericNonExclusivePromise> mTerminationPromise;
+ // Flag to ensure we report creation at most once. This could be cleaned up
+ // further.
+ bool mDidSendCreated = false;
+ };
+
+ ThreadBound<LauncherBoundData> mLauncherData;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_RemoteWorkerChild_h
diff --git a/dom/workers/remoteworkers/RemoteWorkerController.cpp b/dom/workers/remoteworkers/RemoteWorkerController.cpp
new file mode 100644
index 0000000000..b0d56aa33d
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerController.cpp
@@ -0,0 +1,573 @@
+/* -*- 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 "RemoteWorkerController.h"
+
+#include <utility>
+
+#include "nsDebug.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/RemoteLazyInputStreamStorage.h"
+#include "mozilla/dom/FetchEventOpParent.h"
+#include "mozilla/dom/FetchEventOpProxyParent.h"
+#include "mozilla/dom/MessagePortParent.h"
+#include "mozilla/dom/RemoteWorkerTypes.h"
+#include "mozilla/dom/ServiceWorkerCloneData.h"
+#include "mozilla/dom/ServiceWorkerShutdownState.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "RemoteWorkerControllerParent.h"
+#include "RemoteWorkerManager.h"
+#include "RemoteWorkerParent.h"
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+
+/* static */
+already_AddRefed<RemoteWorkerController> RemoteWorkerController::Create(
+ const RemoteWorkerData& aData, RemoteWorkerObserver* aObserver,
+ base::ProcessId aProcessId) {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aObserver);
+
+ RefPtr<RemoteWorkerController> controller =
+ new RemoteWorkerController(aData, aObserver);
+
+ RefPtr<RemoteWorkerManager> manager = RemoteWorkerManager::GetOrCreate();
+ MOZ_ASSERT(manager);
+
+ // XXX: We do not check for failure here, should we?
+ manager->Launch(controller, aData, aProcessId);
+
+ return controller.forget();
+}
+
+RemoteWorkerController::RemoteWorkerController(const RemoteWorkerData& aData,
+ RemoteWorkerObserver* aObserver)
+ : mObserver(aObserver),
+ mState(ePending),
+ mIsServiceWorker(aData.serviceWorkerData().type() ==
+ OptionalServiceWorkerData::TServiceWorkerData) {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+}
+
+RemoteWorkerController::~RemoteWorkerController() {
+ AssertIsOnBackgroundThread();
+ MOZ_DIAGNOSTIC_ASSERT(mPendingOps.IsEmpty());
+}
+
+void RemoteWorkerController::SetWorkerActor(RemoteWorkerParent* aActor) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mActor);
+ MOZ_ASSERT(aActor);
+
+ mActor = aActor;
+}
+
+void RemoteWorkerController::NoteDeadWorkerActor() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mActor);
+
+ // The actor has been destroyed without a proper close() notification. Let's
+ // inform the observer.
+ if (mState == eReady) {
+ mObserver->Terminated();
+ }
+
+ mActor = nullptr;
+
+ Shutdown();
+}
+
+void RemoteWorkerController::CreationFailed() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(mState == ePending || mState == eTerminated);
+
+ if (mState == eTerminated) {
+ MOZ_ASSERT(!mActor);
+ MOZ_ASSERT(mPendingOps.IsEmpty());
+ // Nothing to do.
+ return;
+ }
+
+ NoteDeadWorker();
+
+ mObserver->CreationFailed();
+}
+
+void RemoteWorkerController::CreationSucceeded() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mState == ePending || mState == eTerminated);
+
+ if (mState == eTerminated) {
+ MOZ_ASSERT(!mActor);
+ MOZ_ASSERT(mPendingOps.IsEmpty());
+ // Nothing to do.
+ return;
+ }
+
+ MOZ_ASSERT(mActor);
+ mState = eReady;
+
+ mObserver->CreationSucceeded();
+
+ auto pendingOps = std::move(mPendingOps);
+
+ for (auto& op : pendingOps) {
+ DebugOnly<bool> started = op->MaybeStart(this);
+ MOZ_ASSERT(started);
+ }
+}
+
+void RemoteWorkerController::ErrorPropagation(const ErrorValue& aValue) {
+ AssertIsOnBackgroundThread();
+
+ mObserver->ErrorReceived(aValue);
+}
+
+void RemoteWorkerController::NotifyLock(bool aCreated) {
+ AssertIsOnBackgroundThread();
+
+ mObserver->LockNotified(aCreated);
+}
+
+void RemoteWorkerController::NotifyWebTransport(bool aCreated) {
+ AssertIsOnBackgroundThread();
+
+ mObserver->WebTransportNotified(aCreated);
+}
+
+void RemoteWorkerController::WorkerTerminated() {
+ AssertIsOnBackgroundThread();
+
+ NoteDeadWorker();
+
+ mObserver->Terminated();
+}
+
+void RemoteWorkerController::CancelAllPendingOps() {
+ AssertIsOnBackgroundThread();
+
+ auto pendingOps = std::move(mPendingOps);
+
+ for (auto& op : pendingOps) {
+ op->Cancel();
+ }
+}
+
+void RemoteWorkerController::Shutdown() {
+ AssertIsOnBackgroundThread();
+ Unused << NS_WARN_IF(mIsServiceWorker && !mPendingOps.IsEmpty());
+
+ if (mState == eTerminated) {
+ MOZ_ASSERT(mPendingOps.IsEmpty());
+ return;
+ }
+
+ mState = eTerminated;
+
+ CancelAllPendingOps();
+
+ if (!mActor) {
+ return;
+ }
+
+ mActor->SetController(nullptr);
+
+ /**
+ * The "non-remote-side" of the Service Worker will have ensured that the
+ * remote worker is terminated before calling `Shutdown().`
+ */
+ if (mIsServiceWorker) {
+ mActor->MaybeSendDelete();
+ } else {
+ Unused << mActor->SendExecOp(RemoteWorkerTerminateOp());
+ }
+
+ mActor = nullptr;
+}
+
+void RemoteWorkerController::NoteDeadWorker() {
+ AssertIsOnBackgroundThread();
+
+ CancelAllPendingOps();
+
+ /**
+ * The "non-remote-side" of the Service Worker will initiate `Shutdown()`
+ * once it's notified that all dispatched operations have either completed
+ * or canceled. That is, it'll explicitly call `Shutdown()` later.
+ */
+ if (!mIsServiceWorker) {
+ Shutdown();
+ }
+}
+
+template <typename... Args>
+void RemoteWorkerController::MaybeStartSharedWorkerOp(Args&&... aArgs) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mIsServiceWorker);
+
+ UniquePtr<PendingSharedWorkerOp> op =
+ MakeUnique<PendingSharedWorkerOp>(std::forward<Args>(aArgs)...);
+
+ if (!op->MaybeStart(this)) {
+ mPendingOps.AppendElement(std::move(op));
+ }
+}
+
+void RemoteWorkerController::AddWindowID(uint64_t aWindowID) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aWindowID);
+
+ MaybeStartSharedWorkerOp(PendingSharedWorkerOp::eAddWindowID, aWindowID);
+}
+
+void RemoteWorkerController::RemoveWindowID(uint64_t aWindowID) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aWindowID);
+
+ MaybeStartSharedWorkerOp(PendingSharedWorkerOp::eRemoveWindowID, aWindowID);
+}
+
+void RemoteWorkerController::AddPortIdentifier(
+ const MessagePortIdentifier& aPortIdentifier) {
+ AssertIsOnBackgroundThread();
+
+ MaybeStartSharedWorkerOp(aPortIdentifier);
+}
+
+void RemoteWorkerController::Terminate() {
+ AssertIsOnBackgroundThread();
+
+ MaybeStartSharedWorkerOp(PendingSharedWorkerOp::eTerminate);
+}
+
+void RemoteWorkerController::Suspend() {
+ AssertIsOnBackgroundThread();
+
+ MaybeStartSharedWorkerOp(PendingSharedWorkerOp::eSuspend);
+}
+
+void RemoteWorkerController::Resume() {
+ AssertIsOnBackgroundThread();
+
+ MaybeStartSharedWorkerOp(PendingSharedWorkerOp::eResume);
+}
+
+void RemoteWorkerController::Freeze() {
+ AssertIsOnBackgroundThread();
+
+ MaybeStartSharedWorkerOp(PendingSharedWorkerOp::eFreeze);
+}
+
+void RemoteWorkerController::Thaw() {
+ AssertIsOnBackgroundThread();
+
+ MaybeStartSharedWorkerOp(PendingSharedWorkerOp::eThaw);
+}
+
+RefPtr<ServiceWorkerOpPromise> RemoteWorkerController::ExecServiceWorkerOp(
+ ServiceWorkerOpArgs&& aArgs) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mIsServiceWorker);
+
+ RefPtr<ServiceWorkerOpPromise::Private> promise =
+ new ServiceWorkerOpPromise::Private(__func__);
+
+ UniquePtr<PendingServiceWorkerOp> op =
+ MakeUnique<PendingServiceWorkerOp>(std::move(aArgs), promise);
+
+ if (!op->MaybeStart(this)) {
+ mPendingOps.AppendElement(std::move(op));
+ }
+
+ return promise;
+}
+
+RefPtr<ServiceWorkerFetchEventOpPromise>
+RemoteWorkerController::ExecServiceWorkerFetchEventOp(
+ const ParentToParentServiceWorkerFetchEventOpArgs& aArgs,
+ RefPtr<FetchEventOpParent> aReal) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mIsServiceWorker);
+
+ RefPtr<ServiceWorkerFetchEventOpPromise::Private> promise =
+ new ServiceWorkerFetchEventOpPromise::Private(__func__);
+
+ UniquePtr<PendingSWFetchEventOp> op =
+ MakeUnique<PendingSWFetchEventOp>(aArgs, promise, std::move(aReal));
+
+ if (!op->MaybeStart(this)) {
+ mPendingOps.AppendElement(std::move(op));
+ }
+
+ return promise;
+}
+
+RefPtr<GenericPromise> RemoteWorkerController::SetServiceWorkerSkipWaitingFlag()
+ const {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mObserver);
+
+ RefPtr<GenericPromise::Private> promise =
+ new GenericPromise::Private(__func__);
+
+ static_cast<RemoteWorkerControllerParent*>(mObserver.get())
+ ->MaybeSendSetServiceWorkerSkipWaitingFlag(
+ [promise](bool aOk) { promise->Resolve(aOk, __func__); });
+
+ return promise;
+}
+
+bool RemoteWorkerController::IsTerminated() const {
+ return mState == eTerminated;
+}
+
+RemoteWorkerController::PendingSharedWorkerOp::PendingSharedWorkerOp(
+ Type aType, uint64_t aWindowID)
+ : mType(aType), mWindowID(aWindowID) {
+ AssertIsOnBackgroundThread();
+}
+
+RemoteWorkerController::PendingSharedWorkerOp::PendingSharedWorkerOp(
+ const MessagePortIdentifier& aPortIdentifier)
+ : mType(ePortIdentifier), mPortIdentifier(aPortIdentifier) {
+ AssertIsOnBackgroundThread();
+}
+
+RemoteWorkerController::PendingSharedWorkerOp::~PendingSharedWorkerOp() {
+ AssertIsOnBackgroundThread();
+ MOZ_DIAGNOSTIC_ASSERT(mCompleted);
+}
+
+bool RemoteWorkerController::PendingSharedWorkerOp::MaybeStart(
+ RemoteWorkerController* const aOwner) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mCompleted);
+ MOZ_ASSERT(aOwner);
+
+ if (aOwner->mState == RemoteWorkerController::eTerminated) {
+ Cancel();
+ return true;
+ }
+
+ if (aOwner->mState == RemoteWorkerController::ePending &&
+ mType != eTerminate) {
+ return false;
+ }
+
+ switch (mType) {
+ case eTerminate:
+ aOwner->Shutdown();
+ break;
+ case eSuspend:
+ Unused << aOwner->mActor->SendExecOp(RemoteWorkerSuspendOp());
+ break;
+ case eResume:
+ Unused << aOwner->mActor->SendExecOp(RemoteWorkerResumeOp());
+ break;
+ case eFreeze:
+ Unused << aOwner->mActor->SendExecOp(RemoteWorkerFreezeOp());
+ break;
+ case eThaw:
+ Unused << aOwner->mActor->SendExecOp(RemoteWorkerThawOp());
+ break;
+ case ePortIdentifier:
+ Unused << aOwner->mActor->SendExecOp(
+ RemoteWorkerPortIdentifierOp(mPortIdentifier));
+ break;
+ case eAddWindowID:
+ Unused << aOwner->mActor->SendExecOp(
+ RemoteWorkerAddWindowIDOp(mWindowID));
+ break;
+ case eRemoveWindowID:
+ Unused << aOwner->mActor->SendExecOp(
+ RemoteWorkerRemoveWindowIDOp(mWindowID));
+ break;
+ default:
+ MOZ_CRASH("Unknown op.");
+ }
+
+ mCompleted = true;
+
+ return true;
+}
+
+void RemoteWorkerController::PendingSharedWorkerOp::Cancel() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mCompleted);
+
+ // We don't want to leak the port if the operation has not been processed.
+ if (mType == ePortIdentifier) {
+ MessagePortParent::ForceClose(mPortIdentifier.uuid(),
+ mPortIdentifier.destinationUuid(),
+ mPortIdentifier.sequenceId());
+ }
+
+ mCompleted = true;
+}
+
+RemoteWorkerController::PendingServiceWorkerOp::PendingServiceWorkerOp(
+ ServiceWorkerOpArgs&& aArgs,
+ RefPtr<ServiceWorkerOpPromise::Private> aPromise)
+ : mArgs(std::move(aArgs)), mPromise(std::move(aPromise)) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mPromise);
+}
+
+RemoteWorkerController::PendingServiceWorkerOp::~PendingServiceWorkerOp() {
+ AssertIsOnBackgroundThread();
+ MOZ_DIAGNOSTIC_ASSERT(!mPromise);
+}
+
+bool RemoteWorkerController::PendingServiceWorkerOp::MaybeStart(
+ RemoteWorkerController* const aOwner) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mPromise);
+ MOZ_ASSERT(aOwner);
+
+ if (NS_WARN_IF(aOwner->mState == RemoteWorkerController::eTerminated)) {
+ mPromise->Reject(NS_ERROR_DOM_ABORT_ERR, __func__);
+ mPromise = nullptr;
+ return true;
+ }
+
+ // The target content process must still be starting up.
+ if (!aOwner->mActor) {
+ // We can avoid starting the worker at all if we know it should be
+ // terminated.
+ MOZ_ASSERT(aOwner->mState == RemoteWorkerController::ePending);
+ if (mArgs.type() ==
+ ServiceWorkerOpArgs::TServiceWorkerTerminateWorkerOpArgs) {
+ aOwner->CancelAllPendingOps();
+ Cancel();
+
+ aOwner->mState = RemoteWorkerController::eTerminated;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Allow termination operations to pass through while pending because the
+ * remote Service Worker can be terminated while still starting up.
+ */
+ if (aOwner->mState == RemoteWorkerController::ePending &&
+ mArgs.type() !=
+ ServiceWorkerOpArgs::TServiceWorkerTerminateWorkerOpArgs) {
+ return false;
+ }
+
+ MaybeReportServiceWorkerShutdownProgress(mArgs);
+
+ aOwner->mActor->SendExecServiceWorkerOp(mArgs)->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [promise = std::move(mPromise)](
+ PRemoteWorkerParent::ExecServiceWorkerOpPromise::
+ ResolveOrRejectValue&& aResult) {
+ if (NS_WARN_IF(aResult.IsReject())) {
+ promise->Reject(NS_ERROR_DOM_ABORT_ERR, __func__);
+ return;
+ }
+
+ promise->Resolve(std::move(aResult.ResolveValue()), __func__);
+ });
+
+ return true;
+}
+
+void RemoteWorkerController::PendingServiceWorkerOp::Cancel() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mPromise);
+
+ mPromise->Reject(NS_ERROR_DOM_ABORT_ERR, __func__);
+ mPromise = nullptr;
+}
+
+RemoteWorkerController::PendingSWFetchEventOp::PendingSWFetchEventOp(
+ const ParentToParentServiceWorkerFetchEventOpArgs& aArgs,
+ RefPtr<ServiceWorkerFetchEventOpPromise::Private> aPromise,
+ RefPtr<FetchEventOpParent>&& aReal)
+ : mArgs(aArgs), mPromise(std::move(aPromise)), mReal(aReal) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mPromise);
+
+ // If there is a TParentToParentStream in the request body, we need to
+ // save it to our stream.
+ IPCInternalRequest& req = mArgs.common().internalRequest();
+ if (req.body().isSome() &&
+ req.body().ref().type() == BodyStreamVariant::TParentToParentStream) {
+ nsCOMPtr<nsIInputStream> stream;
+ auto streamLength = req.bodySize();
+ const auto& uuid = req.body().ref().get_ParentToParentStream().uuid();
+
+ auto storage = RemoteLazyInputStreamStorage::Get().unwrapOr(nullptr);
+ MOZ_DIAGNOSTIC_ASSERT(storage);
+ storage->GetStream(uuid, 0, streamLength, getter_AddRefs(mBodyStream));
+ storage->ForgetStream(uuid);
+
+ MOZ_DIAGNOSTIC_ASSERT(mBodyStream);
+
+ req.body() = Nothing();
+ }
+}
+
+RemoteWorkerController::PendingSWFetchEventOp::~PendingSWFetchEventOp() {
+ AssertIsOnBackgroundThread();
+ MOZ_DIAGNOSTIC_ASSERT(!mPromise);
+}
+
+bool RemoteWorkerController::PendingSWFetchEventOp::MaybeStart(
+ RemoteWorkerController* const aOwner) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mPromise);
+ MOZ_ASSERT(aOwner);
+
+ if (NS_WARN_IF(aOwner->mState == RemoteWorkerController::eTerminated)) {
+ mPromise->Reject(NS_ERROR_DOM_ABORT_ERR, __func__);
+ mPromise = nullptr;
+ // Because the worker has transitioned to terminated, this operation is moot
+ // and so we should return true because there's no need to queue it.
+ return true;
+ }
+
+ // The target content process must still be starting up.
+ if (!aOwner->mActor) {
+ MOZ_ASSERT(aOwner->mState == RemoteWorkerController::ePending);
+ return false;
+ }
+
+ // At this point we are handing off responsibility for the promise to the
+ // actor.
+ FetchEventOpProxyParent::Create(aOwner->mActor.get(), std::move(mPromise),
+ mArgs, std::move(mReal),
+ std::move(mBodyStream));
+
+ return true;
+}
+
+void RemoteWorkerController::PendingSWFetchEventOp::Cancel() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mPromise);
+
+ if (mPromise) {
+ mPromise->Reject(NS_ERROR_DOM_ABORT_ERR, __func__);
+ mPromise = nullptr;
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/remoteworkers/RemoteWorkerController.h b/dom/workers/remoteworkers/RemoteWorkerController.h
new file mode 100644
index 0000000000..af53634abd
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerController.h
@@ -0,0 +1,323 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_RemoteWorkerController_h
+#define mozilla_dom_RemoteWorkerController_h
+
+#include "nsISupportsImpl.h"
+#include "nsTArray.h"
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/DOMTypes.h"
+#include "mozilla/dom/ServiceWorkerOpArgs.h"
+#include "mozilla/dom/ServiceWorkerOpPromise.h"
+
+namespace mozilla::dom {
+
+/* Here's a graph about this remote workers are spawned.
+ *
+ * _________________________________ | ________________________________
+ * | | | | |
+ * | Parent process | IPC | Creation of Process X |
+ * | PBackground thread | | | |
+ * | | | | [RemoteWorkerService::Init()] |
+ * | | | | | |
+ * | | | | | (1) |
+ * | [RemoteWorkerManager:: (2) | | | V |
+ * | RegisterActor()]<-------- [new RemoteWorkerServiceChild] |
+ * | | | | |
+ * | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | | |________________________________|
+ * | | |
+ * | new SharedWorker/ServiceWorker | |
+ * | | ^ | IPC
+ * | (3) | (4)| |
+ * | V | | |
+ * | [RemoteWorkerController:: | |
+ * | | Create(data)] | |
+ * | | (5) | |
+ * | V | |
+ * | [RemoteWorkerManager::Launch()] | |
+ * | | | IPC _____________________________
+ * | | (6) | | | |
+ * | | | | Selected content process |
+ * | V | (7) | |
+ * | [SendPRemoteWorkerConstructor()]--------->[new RemoteWorkerChild()] |
+ * | | | | | | |
+ * | | (8) | | | | |
+ * | V | | | V |
+ * | [RemoteWorkerController-> | | | RemoteWorkerChild->Exec() |
+ * | | SetControllerActor()] | | |_____________________________|
+ * | (9) | | IPC
+ * | V | |
+ * | [RemoteWorkerObserver-> | |
+ * | CreationCompleted()] | |
+ * |_________________________________| |
+ * |
+ *
+ * 1. When a new process starts, it creates a RemoteWorkerService singleton.
+ * This service creates a new thread (Worker Launcher) and from there, it
+ * starts a PBackground RemoteWorkerServiceChild actor.
+ * 2. On the parent process, PBackground thread, RemoteWorkerServiceParent
+ * actors are registered into the RemoteWorkerManager service.
+ *
+ * 3. At some point, a SharedWorker or a ServiceWorker must be executed.
+ * RemoteWorkerController::Create() is used to start the launching. This
+ * method must be called on the parent process, on the PBackground thread.
+ * 4. RemoteWorkerController object is immediately returned to the caller. Any
+ * operation done with this controller object will be stored in a queue,
+ * until the launching is correctly executed.
+ * 5. RemoteWorkerManager has the list of active RemoteWorkerServiceParent
+ * actors. From them, it picks one.
+ * In case we don't have any content process to select, a new one is
+ * spawned. If this happens, the operation is suspended until a new
+ * RemoteWorkerServiceParent is registered.
+ * 6. RemoteWorkerServiceParent is used to create a RemoteWorkerParent.
+ * 7. RemoteWorkerChild is created on a selected process and it executes the
+ * WorkerPrivate.
+ * 8. The RemoteWorkerParent actor is passed to the RemoteWorkerController.
+ * 9. RemoteWorkerController now is ready to continue and it called
+ * RemoteWorkerObserver to inform that the operation is completed.
+ * In case there were pending operations, they are now executed.
+ */
+
+class ErrorValue;
+class FetchEventOpParent;
+class RemoteWorkerControllerParent;
+class RemoteWorkerData;
+class RemoteWorkerManager;
+class RemoteWorkerParent;
+
+class RemoteWorkerObserver {
+ public:
+ NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
+
+ virtual void CreationFailed() = 0;
+
+ virtual void CreationSucceeded() = 0;
+
+ virtual void ErrorReceived(const ErrorValue& aValue) = 0;
+
+ virtual void LockNotified(bool aCreated) = 0;
+
+ virtual void WebTransportNotified(bool aCreated) = 0;
+
+ virtual void Terminated() = 0;
+};
+
+/**
+ * PBackground instance created by static RemoteWorkerController::Create that
+ * builds on RemoteWorkerManager. Interface to control the remote worker as well
+ * as receive events via the RemoteWorkerObserver interface that the owner
+ * (SharedWorkerManager in this case) must implement to hear about errors,
+ * termination, and whether the initial spawning succeeded/failed.
+ *
+ * Its methods may be called immediately after creation even though the worker
+ * is created asynchronously; an internal operation queue makes this work.
+ * Communicates with the remote worker via owned RemoteWorkerParent over
+ * PRemoteWorker protocol.
+ */
+class RemoteWorkerController final {
+ friend class RemoteWorkerControllerParent;
+ friend class RemoteWorkerManager;
+ friend class RemoteWorkerParent;
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING(RemoteWorkerController)
+
+ static already_AddRefed<RemoteWorkerController> Create(
+ const RemoteWorkerData& aData, RemoteWorkerObserver* aObserver,
+ base::ProcessId = 0);
+
+ void AddWindowID(uint64_t aWindowID);
+
+ void RemoveWindowID(uint64_t aWindowID);
+
+ void AddPortIdentifier(const MessagePortIdentifier& aPortIdentifier);
+
+ void Terminate();
+
+ void Suspend();
+
+ void Resume();
+
+ void Freeze();
+
+ void Thaw();
+
+ RefPtr<ServiceWorkerOpPromise> ExecServiceWorkerOp(
+ ServiceWorkerOpArgs&& aArgs);
+
+ RefPtr<ServiceWorkerFetchEventOpPromise> ExecServiceWorkerFetchEventOp(
+ const ParentToParentServiceWorkerFetchEventOpArgs& aArgs,
+ RefPtr<FetchEventOpParent> aReal);
+
+ RefPtr<GenericPromise> SetServiceWorkerSkipWaitingFlag() const;
+
+ bool IsTerminated() const;
+
+ void NotifyWebTransport(bool aCreated);
+
+ private:
+ RemoteWorkerController(const RemoteWorkerData& aData,
+ RemoteWorkerObserver* aObserver);
+
+ ~RemoteWorkerController();
+
+ void SetWorkerActor(RemoteWorkerParent* aActor);
+
+ void NoteDeadWorkerActor();
+
+ void ErrorPropagation(const ErrorValue& aValue);
+
+ void NotifyLock(bool aCreated);
+
+ void WorkerTerminated();
+
+ void Shutdown();
+
+ void CreationFailed();
+
+ void CreationSucceeded();
+
+ void CancelAllPendingOps();
+
+ template <typename... Args>
+ void MaybeStartSharedWorkerOp(Args&&... aArgs);
+
+ void NoteDeadWorker();
+
+ RefPtr<RemoteWorkerObserver> mObserver;
+ RefPtr<RemoteWorkerParent> mActor;
+
+ enum {
+ ePending,
+ eReady,
+ eTerminated,
+ } mState;
+
+ const bool mIsServiceWorker;
+
+ /**
+ * `PendingOp` is responsible for encapsulating logic for starting and
+ * canceling pending remote worker operations, as this logic may vary
+ * depending on the type of the remote worker and the type of the operation.
+ */
+ class PendingOp {
+ public:
+ PendingOp() = default;
+
+ PendingOp(const PendingOp&) = delete;
+
+ PendingOp& operator=(const PendingOp&) = delete;
+
+ virtual ~PendingOp() = default;
+
+ /**
+ * Returns `true` if execution has started or the operation is moot and
+ * doesn't need to be queued, `false` if execution hasn't started and the
+ * operation should be queued. In general, operations should only return
+ * false when a remote worker is first starting up. Operations may also
+ * somewhat non-intuitively return true without doing anything if the worker
+ * has already been told to shutdown.
+ *
+ * Starting execution may depend the state of `aOwner.`
+ */
+ virtual bool MaybeStart(RemoteWorkerController* const aOwner) = 0;
+
+ /**
+ * Invoked if the operation will never have MaybeStart() called again
+ * because the RemoteWorkerController has terminated (or will never start).
+ * This should be used by PendingOps to clean up any resources they own and
+ * may also be called internally by their MaybeStart() methods if they
+ * determine the worker has been terminated. This should be idempotent.
+ */
+ virtual void Cancel() = 0;
+ };
+
+ class PendingSharedWorkerOp final : public PendingOp {
+ public:
+ enum Type {
+ eTerminate,
+ eSuspend,
+ eResume,
+ eFreeze,
+ eThaw,
+ ePortIdentifier,
+ eAddWindowID,
+ eRemoveWindowID,
+ };
+
+ explicit PendingSharedWorkerOp(Type aType, uint64_t aWindowID = 0);
+
+ explicit PendingSharedWorkerOp(
+ const MessagePortIdentifier& aPortIdentifier);
+
+ ~PendingSharedWorkerOp();
+
+ bool MaybeStart(RemoteWorkerController* const aOwner) override;
+
+ void Cancel() override;
+
+ private:
+ const Type mType;
+ const MessagePortIdentifier mPortIdentifier;
+ const uint64_t mWindowID = 0;
+ bool mCompleted = false;
+ };
+
+ class PendingServiceWorkerOp final : public PendingOp {
+ public:
+ PendingServiceWorkerOp(ServiceWorkerOpArgs&& aArgs,
+ RefPtr<ServiceWorkerOpPromise::Private> aPromise);
+
+ ~PendingServiceWorkerOp();
+
+ bool MaybeStart(RemoteWorkerController* const aOwner) override;
+
+ void Cancel() override;
+
+ private:
+ ServiceWorkerOpArgs mArgs;
+ RefPtr<ServiceWorkerOpPromise::Private> mPromise;
+ };
+
+ /**
+ * Custom pending op type to deal with the complexities of FetchEvents having
+ * their own actor.
+ *
+ * FetchEvent Ops have their own actor type because their lifecycle is more
+ * complex than IPDL's async return value mechanism allows. Additionally,
+ * its IPC struct potentially has to serialize RemoteLazyStreams which
+ * requires us to hold an nsIInputStream when at rest and serialize it when
+ * eventually sending.
+ */
+ class PendingSWFetchEventOp final : public PendingOp {
+ public:
+ PendingSWFetchEventOp(
+ const ParentToParentServiceWorkerFetchEventOpArgs& aArgs,
+ RefPtr<ServiceWorkerFetchEventOpPromise::Private> aPromise,
+ RefPtr<FetchEventOpParent>&& aReal);
+
+ ~PendingSWFetchEventOp();
+
+ bool MaybeStart(RemoteWorkerController* const aOwner) override;
+
+ void Cancel() override;
+
+ private:
+ ParentToParentServiceWorkerFetchEventOpArgs mArgs;
+ RefPtr<ServiceWorkerFetchEventOpPromise::Private> mPromise;
+ RefPtr<FetchEventOpParent> mReal;
+ nsCOMPtr<nsIInputStream> mBodyStream;
+ };
+
+ nsTArray<UniquePtr<PendingOp>> mPendingOps;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_RemoteWorkerController_h
diff --git a/dom/workers/remoteworkers/RemoteWorkerControllerChild.cpp b/dom/workers/remoteworkers/RemoteWorkerControllerChild.cpp
new file mode 100644
index 0000000000..1b7f159fa5
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerControllerChild.cpp
@@ -0,0 +1,149 @@
+/* -*- 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 "RemoteWorkerControllerChild.h"
+
+#include <utility>
+
+#include "MainThreadUtils.h"
+#include "nsError.h"
+#include "nsThreadUtils.h"
+
+#include "ServiceWorkerPrivate.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/PFetchEventOpChild.h"
+
+namespace mozilla {
+
+using ipc::IPCResult;
+
+namespace dom {
+
+RemoteWorkerControllerChild::RemoteWorkerControllerChild(
+ RefPtr<RemoteWorkerObserver> aObserver)
+ : mObserver(std::move(aObserver)) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mObserver);
+}
+
+PFetchEventOpChild* RemoteWorkerControllerChild::AllocPFetchEventOpChild(
+ const ParentToParentServiceWorkerFetchEventOpArgs& aArgs) {
+ MOZ_CRASH("PFetchEventOpChild actors must be manually constructed!");
+ return nullptr;
+}
+
+bool RemoteWorkerControllerChild::DeallocPFetchEventOpChild(
+ PFetchEventOpChild* aActor) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aActor);
+
+ delete aActor;
+ return true;
+}
+
+void RemoteWorkerControllerChild::ActorDestroy(ActorDestroyReason aReason) {
+ AssertIsOnMainThread();
+
+ mIPCActive = false;
+
+ if (NS_WARN_IF(mObserver)) {
+ mObserver->ErrorReceived(NS_ERROR_DOM_ABORT_ERR);
+ }
+}
+
+IPCResult RemoteWorkerControllerChild::RecvCreationFailed() {
+ AssertIsOnMainThread();
+
+ if (mObserver) {
+ mObserver->CreationFailed();
+ }
+
+ return IPC_OK();
+}
+
+IPCResult RemoteWorkerControllerChild::RecvCreationSucceeded() {
+ AssertIsOnMainThread();
+
+ if (mObserver) {
+ mObserver->CreationSucceeded();
+ }
+
+ return IPC_OK();
+}
+
+IPCResult RemoteWorkerControllerChild::RecvErrorReceived(
+ const ErrorValue& aError) {
+ AssertIsOnMainThread();
+
+ if (mObserver) {
+ mObserver->ErrorReceived(aError);
+ }
+
+ return IPC_OK();
+}
+
+IPCResult RemoteWorkerControllerChild::RecvTerminated() {
+ AssertIsOnMainThread();
+
+ if (mObserver) {
+ mObserver->Terminated();
+ }
+
+ return IPC_OK();
+}
+
+IPCResult RemoteWorkerControllerChild::RecvSetServiceWorkerSkipWaitingFlag(
+ SetServiceWorkerSkipWaitingFlagResolver&& aResolve) {
+ AssertIsOnMainThread();
+
+ if (mObserver) {
+ static_cast<ServiceWorkerPrivate*>(mObserver.get())
+ ->SetSkipWaitingFlag()
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [resolve = std::move(aResolve)](
+ const GenericPromise::ResolveOrRejectValue& aResult) {
+ resolve(aResult.IsResolve() ? aResult.ResolveValue() : false);
+ });
+
+ return IPC_OK();
+ }
+
+ aResolve(false);
+
+ return IPC_OK();
+}
+
+void RemoteWorkerControllerChild::RevokeObserver(
+ RemoteWorkerObserver* aObserver) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aObserver);
+ MOZ_ASSERT(aObserver == mObserver);
+
+ mObserver = nullptr;
+}
+
+void RemoteWorkerControllerChild::MaybeSendDelete() {
+ AssertIsOnMainThread();
+
+ if (!mIPCActive) {
+ return;
+ }
+
+ RefPtr<RemoteWorkerControllerChild> self = this;
+
+ SendShutdown()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self = std::move(self)](const ShutdownPromise::ResolveOrRejectValue&) {
+ if (self->mIPCActive) {
+ Unused << self->Send__delete__(self);
+ }
+ });
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/remoteworkers/RemoteWorkerControllerChild.h b/dom/workers/remoteworkers/RemoteWorkerControllerChild.h
new file mode 100644
index 0000000000..049d67ad56
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerControllerChild.h
@@ -0,0 +1,64 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_remoteworkercontrollerchild_h__
+#define mozilla_dom_remoteworkercontrollerchild_h__
+
+#include "nsISupportsImpl.h"
+
+#include "RemoteWorkerController.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/PRemoteWorkerControllerChild.h"
+
+namespace mozilla::dom {
+
+/**
+ * Parent-process main-thread proxy used by ServiceWorkerManager to control
+ * RemoteWorkerController instances on the parent-process PBackground thread.
+ */
+class RemoteWorkerControllerChild final : public PRemoteWorkerControllerChild {
+ friend class PRemoteWorkerControllerChild;
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING(RemoteWorkerControllerChild)
+
+ explicit RemoteWorkerControllerChild(RefPtr<RemoteWorkerObserver> aObserver);
+
+ void Initialize();
+
+ void RevokeObserver(RemoteWorkerObserver* aObserver);
+
+ void MaybeSendDelete();
+
+ private:
+ ~RemoteWorkerControllerChild() = default;
+
+ PFetchEventOpChild* AllocPFetchEventOpChild(
+ const ParentToParentServiceWorkerFetchEventOpArgs& aArgs);
+
+ bool DeallocPFetchEventOpChild(PFetchEventOpChild* aActor);
+
+ void ActorDestroy(ActorDestroyReason aReason) override;
+
+ mozilla::ipc::IPCResult RecvCreationFailed();
+
+ mozilla::ipc::IPCResult RecvCreationSucceeded();
+
+ mozilla::ipc::IPCResult RecvErrorReceived(const ErrorValue& aError);
+
+ mozilla::ipc::IPCResult RecvTerminated();
+
+ mozilla::ipc::IPCResult RecvSetServiceWorkerSkipWaitingFlag(
+ SetServiceWorkerSkipWaitingFlagResolver&& aResolve);
+
+ RefPtr<RemoteWorkerObserver> mObserver;
+
+ bool mIPCActive = true;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_remoteworkercontrollerchild_h__
diff --git a/dom/workers/remoteworkers/RemoteWorkerControllerParent.cpp b/dom/workers/remoteworkers/RemoteWorkerControllerParent.cpp
new file mode 100644
index 0000000000..7ac901f655
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerControllerParent.cpp
@@ -0,0 +1,215 @@
+/* -*- 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 "RemoteWorkerControllerParent.h"
+
+#include <utility>
+
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+#include "nsError.h"
+#include "nsThreadUtils.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/FetchEventOpParent.h"
+#include "mozilla/dom/RemoteWorkerParent.h"
+#include "mozilla/dom/ServiceWorkerOpPromise.h"
+#include "mozilla/ipc/BackgroundParent.h"
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+
+RemoteWorkerControllerParent::RemoteWorkerControllerParent(
+ const RemoteWorkerData& aRemoteWorkerData)
+ : mRemoteWorkerController(RemoteWorkerController::Create(
+ aRemoteWorkerData, this, 0 /* random process ID */)) {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mRemoteWorkerController);
+}
+
+RefPtr<RemoteWorkerParent> RemoteWorkerControllerParent::GetRemoteWorkerParent()
+ const {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mRemoteWorkerController);
+
+ return mRemoteWorkerController->mActor;
+}
+
+void RemoteWorkerControllerParent::MaybeSendSetServiceWorkerSkipWaitingFlag(
+ std::function<void(bool)>&& aCallback) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aCallback);
+
+ if (!mIPCActive) {
+ aCallback(false);
+ return;
+ }
+
+ SendSetServiceWorkerSkipWaitingFlag()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [callback = std::move(aCallback)](
+ const SetServiceWorkerSkipWaitingFlagPromise::ResolveOrRejectValue&
+ aResult) {
+ callback(aResult.IsResolve() ? aResult.ResolveValue() : false);
+ });
+}
+
+RemoteWorkerControllerParent::~RemoteWorkerControllerParent() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mIPCActive);
+ MOZ_ASSERT(!mRemoteWorkerController);
+}
+
+PFetchEventOpParent* RemoteWorkerControllerParent::AllocPFetchEventOpParent(
+ const ParentToParentServiceWorkerFetchEventOpArgs& aArgs) {
+ AssertIsOnBackgroundThread();
+
+ RefPtr<FetchEventOpParent> actor = new FetchEventOpParent();
+ return actor.forget().take();
+}
+
+IPCResult RemoteWorkerControllerParent::RecvPFetchEventOpConstructor(
+ PFetchEventOpParent* aActor,
+ const ParentToParentServiceWorkerFetchEventOpArgs& aArgs) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ RefPtr<FetchEventOpParent> realFetchOp =
+ static_cast<FetchEventOpParent*>(aActor);
+ mRemoteWorkerController->ExecServiceWorkerFetchEventOp(aArgs, realFetchOp)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [fetchOp = std::move(realFetchOp)](
+ ServiceWorkerFetchEventOpPromise::ResolveOrRejectValue&&
+ aResult) {
+ if (NS_WARN_IF(aResult.IsReject())) {
+ MOZ_ASSERT(NS_FAILED(aResult.RejectValue()));
+ Unused << fetchOp->Send__delete__(fetchOp, aResult.RejectValue());
+ return;
+ }
+
+ Unused << fetchOp->Send__delete__(fetchOp, aResult.ResolveValue());
+ });
+
+ return IPC_OK();
+}
+
+bool RemoteWorkerControllerParent::DeallocPFetchEventOpParent(
+ PFetchEventOpParent* aActor) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ RefPtr<FetchEventOpParent> actor =
+ dont_AddRef(static_cast<FetchEventOpParent*>(aActor));
+ return true;
+}
+
+IPCResult RemoteWorkerControllerParent::RecvExecServiceWorkerOp(
+ ServiceWorkerOpArgs&& aArgs, ExecServiceWorkerOpResolver&& aResolve) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mIPCActive);
+ MOZ_ASSERT(mRemoteWorkerController);
+
+ mRemoteWorkerController->ExecServiceWorkerOp(std::move(aArgs))
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [resolve = std::move(aResolve)](
+ ServiceWorkerOpPromise::ResolveOrRejectValue&& aResult) {
+ if (NS_WARN_IF(aResult.IsReject())) {
+ MOZ_ASSERT(NS_FAILED(aResult.RejectValue()));
+ resolve(aResult.RejectValue());
+ return;
+ }
+
+ resolve(aResult.ResolveValue());
+ });
+
+ return IPC_OK();
+}
+
+IPCResult RemoteWorkerControllerParent::RecvShutdown(
+ ShutdownResolver&& aResolve) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mIPCActive);
+ MOZ_ASSERT(mRemoteWorkerController);
+
+ mIPCActive = false;
+
+ mRemoteWorkerController->Shutdown();
+ mRemoteWorkerController = nullptr;
+
+ aResolve(true);
+
+ return IPC_OK();
+}
+
+IPCResult RemoteWorkerControllerParent::Recv__delete__() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mIPCActive);
+ MOZ_ASSERT(!mRemoteWorkerController);
+
+ return IPC_OK();
+}
+
+void RemoteWorkerControllerParent::ActorDestroy(ActorDestroyReason aReason) {
+ AssertIsOnBackgroundThread();
+
+ if (NS_WARN_IF(mIPCActive)) {
+ mIPCActive = false;
+ }
+
+ if (NS_WARN_IF(mRemoteWorkerController)) {
+ mRemoteWorkerController->Shutdown();
+ mRemoteWorkerController = nullptr;
+ }
+}
+
+void RemoteWorkerControllerParent::CreationFailed() {
+ AssertIsOnBackgroundThread();
+
+ if (!mIPCActive) {
+ return;
+ }
+
+ Unused << SendCreationFailed();
+}
+
+void RemoteWorkerControllerParent::CreationSucceeded() {
+ AssertIsOnBackgroundThread();
+
+ if (!mIPCActive) {
+ return;
+ }
+
+ Unused << SendCreationSucceeded();
+}
+
+void RemoteWorkerControllerParent::ErrorReceived(const ErrorValue& aValue) {
+ AssertIsOnBackgroundThread();
+
+ if (!mIPCActive) {
+ return;
+ }
+
+ Unused << SendErrorReceived(aValue);
+}
+
+void RemoteWorkerControllerParent::Terminated() {
+ AssertIsOnBackgroundThread();
+
+ if (!mIPCActive) {
+ return;
+ }
+
+ Unused << SendTerminated();
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/remoteworkers/RemoteWorkerControllerParent.h b/dom/workers/remoteworkers/RemoteWorkerControllerParent.h
new file mode 100644
index 0000000000..618fbf9506
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerControllerParent.h
@@ -0,0 +1,86 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_remoteworkercontrollerparent_h__
+#define mozilla_dom_remoteworkercontrollerparent_h__
+
+#include <functional>
+
+#include "nsISupportsImpl.h"
+
+#include "RemoteWorkerController.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/PRemoteWorkerControllerParent.h"
+
+namespace mozilla::dom {
+
+/**
+ * PBackground-resident proxy used by ServiceWorkerManager because canonical
+ * ServiceWorkerManager state exists on the parent process main thread but the
+ * RemoteWorkerController API is used from the parent process PBackground
+ * thread.
+ */
+class RemoteWorkerControllerParent final : public PRemoteWorkerControllerParent,
+ public RemoteWorkerObserver {
+ friend class PRemoteWorkerControllerParent;
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING(RemoteWorkerControllerParent, override)
+
+ explicit RemoteWorkerControllerParent(
+ const RemoteWorkerData& aRemoteWorkerData);
+
+ // Returns the corresponding RemoteWorkerParent (if any).
+ RefPtr<RemoteWorkerParent> GetRemoteWorkerParent() const;
+
+ void MaybeSendSetServiceWorkerSkipWaitingFlag(
+ std::function<void(bool)>&& aCallback);
+
+ private:
+ ~RemoteWorkerControllerParent();
+
+ PFetchEventOpParent* AllocPFetchEventOpParent(
+ const ParentToParentServiceWorkerFetchEventOpArgs& aArgs);
+
+ mozilla::ipc::IPCResult RecvPFetchEventOpConstructor(
+ PFetchEventOpParent* aActor,
+ const ParentToParentServiceWorkerFetchEventOpArgs& aArgs) override;
+
+ bool DeallocPFetchEventOpParent(PFetchEventOpParent* aActor);
+
+ mozilla::ipc::IPCResult RecvExecServiceWorkerOp(
+ ServiceWorkerOpArgs&& aArgs, ExecServiceWorkerOpResolver&& aResolve);
+
+ mozilla::ipc::IPCResult RecvShutdown(ShutdownResolver&& aResolve);
+
+ mozilla::ipc::IPCResult Recv__delete__() override;
+
+ void ActorDestroy(ActorDestroyReason aReason) override;
+
+ void CreationFailed() override;
+
+ void CreationSucceeded() override;
+
+ void ErrorReceived(const ErrorValue& aValue) override;
+
+ void LockNotified(bool aCreated) final {
+ // no-op for service workers
+ }
+
+ void WebTransportNotified(bool aCreated) final {
+ // no-op for service workers
+ }
+
+ void Terminated() override;
+
+ RefPtr<RemoteWorkerController> mRemoteWorkerController;
+
+ bool mIPCActive = true;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_remoteworkercontrollerparent_h__
diff --git a/dom/workers/remoteworkers/RemoteWorkerManager.cpp b/dom/workers/remoteworkers/RemoteWorkerManager.cpp
new file mode 100644
index 0000000000..9d9189b38f
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerManager.cpp
@@ -0,0 +1,735 @@
+/* -*- 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 "RemoteWorkerManager.h"
+
+#include <utility>
+
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/dom/ContentChild.h" // ContentChild::GetSingleton
+#include "mozilla/dom/RemoteWorkerController.h"
+#include "mozilla/dom/RemoteWorkerParent.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/ipc/PBackgroundParent.h"
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+# include "mozilla/dom/DOMException.h"
+# include "mozilla/CycleCollectedJSContext.h"
+# include "mozilla/Sprintf.h" // SprintfLiteral
+# include "nsIXPConnect.h" // nsIXPConnectWrappedJS
+#endif
+#include "mozilla/StaticPrefs_extensions.h"
+#include "nsCOMPtr.h"
+#include "nsIE10SUtils.h"
+#include "nsImportModule.h"
+#include "nsIXULRuntime.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+#include "RemoteWorkerServiceParent.h"
+
+mozilla::LazyLogModule gRemoteWorkerManagerLog("RemoteWorkerManager");
+
+#ifdef LOG
+# undef LOG
+#endif
+#define LOG(fmt) \
+ MOZ_LOG(gRemoteWorkerManagerLog, mozilla::LogLevel::Verbose, fmt)
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+
+namespace {
+
+// Raw pointer because this object is kept alive by RemoteWorkerServiceParent
+// actors.
+RemoteWorkerManager* sRemoteWorkerManager;
+
+bool IsServiceWorker(const RemoteWorkerData& aData) {
+ return aData.serviceWorkerData().type() ==
+ OptionalServiceWorkerData::TServiceWorkerData;
+}
+
+void TransmitPermissionsAndBlobURLsForPrincipalInfo(
+ ContentParent* aContentParent, const PrincipalInfo& aPrincipalInfo) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aContentParent);
+
+ auto principalOrErr = PrincipalInfoToPrincipal(aPrincipalInfo);
+
+ if (NS_WARN_IF(principalOrErr.isErr())) {
+ return;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
+
+ aContentParent->TransmitBlobURLsForPrincipal(principal);
+
+ MOZ_ALWAYS_SUCCEEDS(
+ aContentParent->TransmitPermissionsForPrincipal(principal));
+}
+
+} // namespace
+
+// static
+bool RemoteWorkerManager::MatchRemoteType(const nsACString& processRemoteType,
+ const nsACString& workerRemoteType) {
+ LOG(("MatchRemoteType [processRemoteType=%s, workerRemoteType=%s]",
+ PromiseFlatCString(processRemoteType).get(),
+ PromiseFlatCString(workerRemoteType).get()));
+
+ // Respecting COOP and COEP requires processing headers in the parent
+ // process in order to choose an appropriate content process, but the
+ // workers' ScriptLoader processes headers in content processes. An
+ // intermediary step that provides security guarantees is to simply never
+ // allow SharedWorkers and ServiceWorkers to exist in a COOP+COEP process.
+ // The ultimate goal is to allow these worker types to be put in such
+ // processes based on their script response headers.
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1595206
+ //
+ // RemoteWorkerManager::GetRemoteType should not select this remoteType
+ // and so workerRemoteType is not expected to be set to a coop+coep
+ // remoteType and here we can just assert that it is not happening.
+ MOZ_ASSERT(!IsWebCoopCoepRemoteType(workerRemoteType));
+
+ return processRemoteType.Equals(workerRemoteType);
+}
+
+// static
+Result<nsCString, nsresult> RemoteWorkerManager::GetRemoteType(
+ const nsCOMPtr<nsIPrincipal>& aPrincipal, WorkerKind aWorkerKind) {
+ AssertIsOnMainThread();
+
+ MOZ_ASSERT_IF(aWorkerKind == WorkerKind::WorkerKindService,
+ aPrincipal->GetIsContentPrincipal());
+
+ nsCOMPtr<nsIE10SUtils> e10sUtils = do_ImportESModule(
+ "resource://gre/modules/E10SUtils.sys.mjs", "E10SUtils", fallible);
+ if (NS_WARN_IF(!e10sUtils)) {
+ LOG(("GetRemoteType Abort: could not import E10SUtils"));
+ return Err(NS_ERROR_DOM_ABORT_ERR);
+ }
+
+ nsCString preferredRemoteType = DEFAULT_REMOTE_TYPE;
+ if (aWorkerKind == WorkerKind::WorkerKindShared) {
+ if (auto* contentChild = ContentChild::GetSingleton()) {
+ // For a shared worker set the preferred remote type to the content
+ // child process remote type.
+ preferredRemoteType = contentChild->GetRemoteType();
+ } else if (aPrincipal->IsSystemPrincipal()) {
+ preferredRemoteType = NOT_REMOTE_TYPE;
+ }
+ }
+
+ nsIE10SUtils::RemoteWorkerType workerType;
+
+ switch (aWorkerKind) {
+ case WorkerKind::WorkerKindService:
+ workerType = nsIE10SUtils::REMOTE_WORKER_TYPE_SERVICE;
+ break;
+ case WorkerKind::WorkerKindShared:
+ workerType = nsIE10SUtils::REMOTE_WORKER_TYPE_SHARED;
+ break;
+ default:
+ // This method isn't expected to be called for worker types that
+ // aren't remote workers (currently Service and Shared workers).
+ LOG(("GetRemoteType Error on unexpected worker type"));
+ MOZ_DIAGNOSTIC_ASSERT(false, "Unexpected worker type");
+ return Err(NS_ERROR_DOM_ABORT_ERR);
+ }
+
+ // Here we do not have access to the window and so we can't use its
+ // useRemoteTabs and useRemoteSubframes flags (for the service
+ // worker there may not even be a window associated to the worker
+ // yet), and so we have to use the prefs instead.
+ bool isMultiprocess = BrowserTabsRemoteAutostart();
+ bool isFission = FissionAutostart();
+
+ nsCString remoteType = NOT_REMOTE_TYPE;
+
+ nsresult rv = e10sUtils->GetRemoteTypeForWorkerPrincipal(
+ aPrincipal, workerType, isMultiprocess, isFission, preferredRemoteType,
+ remoteType);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG(
+ ("GetRemoteType Abort: E10SUtils.getRemoteTypeForWorkerPrincipal "
+ "exception"));
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ nsCString principalTypeOrScheme;
+ if (aPrincipal->IsSystemPrincipal()) {
+ principalTypeOrScheme = "system"_ns;
+ } else if (aPrincipal->GetIsExpandedPrincipal()) {
+ principalTypeOrScheme = "expanded"_ns;
+ } else if (aPrincipal->GetIsNullPrincipal()) {
+ principalTypeOrScheme = "null"_ns;
+ } else {
+ nsCOMPtr<nsIURI> uri = aPrincipal->GetURI();
+ nsresult rv2 = uri->GetScheme(principalTypeOrScheme);
+ if (NS_FAILED(rv2)) {
+ principalTypeOrScheme = "content"_ns;
+ }
+ }
+
+ nsCString processRemoteType = "parent"_ns;
+ if (auto* contentChild = ContentChild::GetSingleton()) {
+ // RemoteTypePrefix make sure that we are not going to include
+ // the full origin that may be part of the current remote type.
+ processRemoteType = RemoteTypePrefix(contentChild->GetRemoteType());
+ }
+
+ // Convert the error code into an error name.
+ nsAutoCString errorName;
+ GetErrorName(rv, errorName);
+
+ // Try to retrieve the line number from the exception.
+ nsAutoCString errorFilename("(unknown)"_ns);
+ uint32_t jsmErrorLineNumber = 0;
+
+ if (auto* context = CycleCollectedJSContext::Get()) {
+ if (RefPtr<Exception> exn = context->GetPendingException()) {
+ nsAutoString filename(u"(unknown)"_ns);
+
+ if (rv == NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS) {
+ // When the failure is a Javascript Error, the line number retrieved
+ // from the Exception instance isn't going to be the E10SUtils.sys.mjs
+ // line that originated the failure, and so we fallback to retrieve it
+ // from the nsIScriptError.
+ nsCOMPtr<nsIScriptError> scriptError =
+ do_QueryInterface(exn->GetData());
+ if (scriptError) {
+ scriptError->GetLineNumber(&jsmErrorLineNumber);
+ scriptError->GetSourceName(filename);
+ }
+ } else {
+ nsCOMPtr<nsIXPConnectWrappedJS> wrapped =
+ do_QueryInterface(e10sUtils);
+ dom::AutoJSAPI jsapi;
+ if (jsapi.Init(wrapped->GetJSObjectGlobal())) {
+ auto* cx = jsapi.cx();
+ jsmErrorLineNumber = exn->LineNumber(cx);
+ exn->GetFilename(cx, filename);
+ }
+ }
+
+ errorFilename = NS_ConvertUTF16toUTF8(filename);
+ }
+ }
+
+ char buf[1024];
+ SprintfLiteral(
+ buf,
+ "workerType=%s, principal=%s, preferredRemoteType=%s, "
+ "processRemoteType=%s, errorName=%s, errorLocation=%s:%d",
+ aWorkerKind == WorkerKind::WorkerKindService ? "service" : "shared",
+ principalTypeOrScheme.get(),
+ PromiseFlatCString(RemoteTypePrefix(preferredRemoteType)).get(),
+ processRemoteType.get(), errorName.get(), errorFilename.get(),
+ jsmErrorLineNumber);
+ MOZ_CRASH_UNSAFE_PRINTF(
+ "E10SUtils.getRemoteTypeForWorkerPrincipal did throw: %s", buf);
+#endif
+ return Err(NS_ERROR_DOM_ABORT_ERR);
+ }
+
+ if (MOZ_LOG_TEST(gRemoteWorkerManagerLog, LogLevel::Verbose)) {
+ nsCString principalOrigin;
+ aPrincipal->GetOrigin(principalOrigin);
+
+ LOG(
+ ("GetRemoteType workerType=%s, principal=%s, "
+ "preferredRemoteType=%s, selectedRemoteType=%s",
+ aWorkerKind == WorkerKind::WorkerKindService ? "service" : "shared",
+ principalOrigin.get(), preferredRemoteType.get(), remoteType.get()));
+ }
+
+ return remoteType;
+}
+
+// static
+bool RemoteWorkerManager::HasExtensionPrincipal(const RemoteWorkerData& aData) {
+ auto principalInfo = aData.principalInfo();
+ return principalInfo.type() == PrincipalInfo::TContentPrincipalInfo &&
+ // This helper method is also called from the background thread and so
+ // we can't check if the principal does have an addonPolicy object
+ // associated and we have to resort to check the url scheme instead.
+ StringBeginsWith(principalInfo.get_ContentPrincipalInfo().spec(),
+ "moz-extension://"_ns);
+}
+
+// static
+bool RemoteWorkerManager::IsRemoteTypeAllowed(const RemoteWorkerData& aData) {
+ AssertIsOnMainThread();
+
+ // If Gecko is running in single process mode, there is no child process
+ // to select and we have to just consider it valid (if it should haven't
+ // been launched it should have been already prevented before reaching
+ // a RemoteWorkerChild instance).
+ if (!BrowserTabsRemoteAutostart()) {
+ return true;
+ }
+
+ const auto& principalInfo = aData.principalInfo();
+
+ auto* contentChild = ContentChild::GetSingleton();
+ if (!contentChild) {
+ // If e10s isn't disabled, only workers related to the system principal
+ // should be allowed to run in the parent process, and extension principals
+ // if extensions.webextensions.remote is false.
+ return principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo ||
+ (!StaticPrefs::extensions_webextensions_remote() &&
+ aData.remoteType().Equals(NOT_REMOTE_TYPE) &&
+ HasExtensionPrincipal(aData));
+ }
+
+ auto principalOrErr = PrincipalInfoToPrincipal(principalInfo);
+ if (NS_WARN_IF(principalOrErr.isErr())) {
+ return false;
+ }
+ nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
+
+ // Recompute the remoteType based on the principal, to double-check that it
+ // has not been tempered to select a different child process than the one
+ // expected.
+ bool isServiceWorker = aData.serviceWorkerData().type() ==
+ OptionalServiceWorkerData::TServiceWorkerData;
+ auto remoteType = GetRemoteType(
+ principal, isServiceWorker ? WorkerKindService : WorkerKindShared);
+ if (NS_WARN_IF(remoteType.isErr())) {
+ LOG(("IsRemoteTypeAllowed: Error to retrieve remote type"));
+ return false;
+ }
+
+ return MatchRemoteType(remoteType.unwrap(), contentChild->GetRemoteType());
+}
+
+/* static */
+already_AddRefed<RemoteWorkerManager> RemoteWorkerManager::GetOrCreate() {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+
+ if (!sRemoteWorkerManager) {
+ sRemoteWorkerManager = new RemoteWorkerManager();
+ }
+
+ RefPtr<RemoteWorkerManager> rwm = sRemoteWorkerManager;
+ return rwm.forget();
+}
+
+RemoteWorkerManager::RemoteWorkerManager() : mParentActor(nullptr) {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!sRemoteWorkerManager);
+}
+
+RemoteWorkerManager::~RemoteWorkerManager() {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(sRemoteWorkerManager == this);
+ sRemoteWorkerManager = nullptr;
+}
+
+void RemoteWorkerManager::RegisterActor(RemoteWorkerServiceParent* aActor) {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ if (!BackgroundParent::IsOtherProcessActor(aActor->Manager())) {
+ MOZ_ASSERT(!mParentActor);
+ mParentActor = aActor;
+ MOZ_ASSERT(mPendings.IsEmpty());
+ return;
+ }
+
+ MOZ_ASSERT(!mChildActors.Contains(aActor));
+ mChildActors.AppendElement(aActor);
+
+ if (!mPendings.IsEmpty()) {
+ const auto& processRemoteType = aActor->GetRemoteType();
+ nsTArray<Pending> unlaunched;
+
+ // Flush pending launching.
+ for (Pending& p : mPendings) {
+ if (p.mController->IsTerminated()) {
+ continue;
+ }
+
+ const auto& workerRemoteType = p.mData.remoteType();
+
+ if (MatchRemoteType(processRemoteType, workerRemoteType)) {
+ LOG(("RegisterActor - Launch Pending, workerRemoteType=%s",
+ workerRemoteType.get()));
+ LaunchInternal(p.mController, aActor, p.mData);
+ } else {
+ unlaunched.AppendElement(std::move(p));
+ continue;
+ }
+ }
+
+ std::swap(mPendings, unlaunched);
+
+ // AddRef is called when the first Pending object is added to mPendings, so
+ // the balancing Release is called when the last Pending object is removed.
+ // RemoteWorkerServiceParents will hold strong references to
+ // RemoteWorkerManager.
+ if (mPendings.IsEmpty()) {
+ Release();
+ }
+
+ LOG(("RegisterActor - mPendings length: %zu", mPendings.Length()));
+ }
+}
+
+void RemoteWorkerManager::UnregisterActor(RemoteWorkerServiceParent* aActor) {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ if (aActor == mParentActor) {
+ mParentActor = nullptr;
+ } else {
+ MOZ_ASSERT(mChildActors.Contains(aActor));
+ mChildActors.RemoveElement(aActor);
+ }
+}
+
+void RemoteWorkerManager::Launch(RemoteWorkerController* aController,
+ const RemoteWorkerData& aData,
+ base::ProcessId aProcessId) {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+
+ RemoteWorkerServiceParent* targetActor = SelectTargetActor(aData, aProcessId);
+
+ // If there is not an available actor, let's store the data, and let's spawn a
+ // new process.
+ if (!targetActor) {
+ // If this is the first time we have a pending launching, we must keep alive
+ // the manager.
+ if (mPendings.IsEmpty()) {
+ AddRef();
+ }
+
+ Pending* pending = mPendings.AppendElement();
+ pending->mController = aController;
+ pending->mData = aData;
+
+ // Launching is async, so we cannot check for failures right here.
+ LaunchNewContentProcess(aData);
+ return;
+ }
+
+ /**
+ * If a target actor for the remote worker has been selected, the actor has
+ * already been registered with the corresponding `ContentParent` and we
+ * should not increment the `mRemoteWorkerActorData`'s `mCount` again (see
+ * `SelectTargetActorForServiceWorker()` /
+ * `SelectTargetActorForSharedWorker()`).
+ */
+ LaunchInternal(aController, targetActor, aData, true);
+}
+
+void RemoteWorkerManager::LaunchInternal(
+ RemoteWorkerController* aController,
+ RemoteWorkerServiceParent* aTargetActor, const RemoteWorkerData& aData,
+ bool aRemoteWorkerAlreadyRegistered) {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aController);
+ MOZ_ASSERT(aTargetActor);
+ MOZ_ASSERT(aTargetActor == mParentActor ||
+ mChildActors.Contains(aTargetActor));
+
+ // We need to send permissions to content processes, but not if we're spawning
+ // the worker here in the parent process.
+ if (aTargetActor != mParentActor) {
+ RefPtr<ThreadsafeContentParentHandle> contentHandle =
+ BackgroundParent::GetContentParentHandle(aTargetActor->Manager());
+
+ // This won't cause any race conditions because the content process
+ // should wait for the permissions to be received before executing the
+ // Service Worker.
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ __func__, [contentHandle = std::move(contentHandle),
+ principalInfo = aData.principalInfo()] {
+ AssertIsOnMainThread();
+ if (RefPtr<ContentParent> contentParent =
+ contentHandle->GetContentParent()) {
+ TransmitPermissionsAndBlobURLsForPrincipalInfo(contentParent,
+ principalInfo);
+ }
+ });
+
+ MOZ_ALWAYS_SUCCEEDS(
+ SchedulerGroup::Dispatch(TaskCategory::Other, r.forget()));
+ }
+
+ RefPtr<RemoteWorkerParent> workerActor = MakeAndAddRef<RemoteWorkerParent>();
+ if (!aTargetActor->Manager()->SendPRemoteWorkerConstructor(workerActor,
+ aData)) {
+ AsyncCreationFailed(aController);
+ return;
+ }
+
+ workerActor->Initialize(aRemoteWorkerAlreadyRegistered);
+
+ // This makes the link better the 2 actors.
+ aController->SetWorkerActor(workerActor);
+ workerActor->SetController(aController);
+}
+
+void RemoteWorkerManager::AsyncCreationFailed(
+ RemoteWorkerController* aController) {
+ RefPtr<RemoteWorkerController> controller = aController;
+ nsCOMPtr<nsIRunnable> r =
+ NS_NewRunnableFunction("RemoteWorkerManager::AsyncCreationFailed",
+ [controller]() { controller->CreationFailed(); });
+
+ NS_DispatchToCurrentThread(r.forget());
+}
+
+template <typename Callback>
+void RemoteWorkerManager::ForEachActor(
+ Callback&& aCallback, const nsACString& aRemoteType,
+ Maybe<base::ProcessId> aProcessId) const {
+ AssertIsOnBackgroundThread();
+
+ const auto length = mChildActors.Length();
+
+ auto end = static_cast<uint32_t>(rand()) % length;
+ if (aProcessId) {
+ // Start from the actor with the given processId instead of starting from
+ // a random index.
+ for (auto j = length - 1; j > 0; j--) {
+ if (mChildActors[j]->OtherPid() == *aProcessId) {
+ end = j;
+ break;
+ }
+ }
+ }
+
+ uint32_t i = end;
+
+ do {
+ MOZ_ASSERT(i < mChildActors.Length());
+ RemoteWorkerServiceParent* actor = mChildActors[i];
+
+ if (MatchRemoteType(actor->GetRemoteType(), aRemoteType)) {
+ ThreadsafeContentParentHandle* contentHandle =
+ BackgroundParent::GetContentParentHandle(actor->Manager());
+
+ if (!aCallback(actor, contentHandle)) {
+ break;
+ }
+ }
+
+ i = (i + 1) % length;
+ } while (i != end);
+}
+
+/**
+ * When selecting a target actor for a given remote worker, we have to consider
+ * that:
+ *
+ * - Service Workers can spawn even when their registering page/script isn't
+ * active (e.g. push notifications), so we don't attempt to spawn the worker
+ * in its registering script's process. We search linearly and choose the
+ * search's starting position randomly.
+ *
+ * - When Fission is enabled, Shared Workers may have to be spawned into
+ * different child process from the one where it has been registered from, and
+ * that child process may be going to be marked as dead and shutdown.
+ *
+ * Spawning the workers in a random process makes the process selection criteria
+ * a little tricky, as a candidate process may imminently shutdown due to a
+ * remove worker actor unregistering
+ * (see `ContentParent::UnregisterRemoveWorkerActor`).
+ *
+ * In `ContentParent::MaybeAsyncSendShutDownMessage` we only dispatch a runnable
+ * to call `ContentParent::ShutDownProcess` if there are no registered remote
+ * worker actors, and we ensure that the check for the number of registered
+ * actors and the dispatching of the runnable are atomic. That happens on the
+ * main thread, so here on the background thread, while
+ * `ContentParent::mRemoteWorkerActorData` is locked, if `mCount` > 0, we can
+ * register a remote worker actor "early" and guarantee that the corresponding
+ * content process will not shutdown.
+ */
+RemoteWorkerServiceParent* RemoteWorkerManager::SelectTargetActorInternal(
+ const RemoteWorkerData& aData, base::ProcessId aProcessId) const {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mChildActors.IsEmpty());
+
+ RemoteWorkerServiceParent* actor = nullptr;
+
+ const auto& workerRemoteType = aData.remoteType();
+
+ ForEachActor(
+ [&](RemoteWorkerServiceParent* aActor,
+ ThreadsafeContentParentHandle* aContentHandle) {
+ // Make sure to choose an actor related to a child process that is not
+ // going to shutdown while we are still in the process of launching the
+ // remote worker.
+ //
+ // ForEachActor will start from the child actor coming from the child
+ // process with a pid equal to aProcessId if any, otherwise it would
+ // start from a random actor in the mChildActors array, this guarantees
+ // that we will choose that actor if it does also match the remote type.
+ if (aContentHandle->MaybeRegisterRemoteWorkerActor(
+ [&](uint32_t count, bool shutdownStarted) -> bool {
+ return (count || !shutdownStarted) &&
+ (aActor->OtherPid() == aProcessId || !actor);
+ })) {
+ actor = aActor;
+ return false;
+ }
+ MOZ_ASSERT(!actor);
+ return true;
+ },
+ workerRemoteType, IsServiceWorker(aData) ? Nothing() : Some(aProcessId));
+
+ return actor;
+}
+
+RemoteWorkerServiceParent* RemoteWorkerManager::SelectTargetActor(
+ const RemoteWorkerData& aData, base::ProcessId aProcessId) {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+
+ // System principal workers should run on the parent process.
+ if (aData.principalInfo().type() == PrincipalInfo::TSystemPrincipalInfo) {
+ MOZ_ASSERT(mParentActor);
+ return mParentActor;
+ }
+
+ // Extension principal workers are allowed to run on the parent process
+ // when "extension.webextensions.remote" pref is false.
+ if (aProcessId == base::GetCurrentProcId() &&
+ aData.remoteType().Equals(NOT_REMOTE_TYPE) &&
+ !StaticPrefs::extensions_webextensions_remote() &&
+ HasExtensionPrincipal(aData)) {
+ MOZ_ASSERT(mParentActor);
+ return mParentActor;
+ }
+
+ // If e10s is off, use the parent process.
+ if (!BrowserTabsRemoteAutostart()) {
+ MOZ_ASSERT(mParentActor);
+ return mParentActor;
+ }
+
+ // We shouldn't have to worry about content-principal parent-process workers.
+ MOZ_ASSERT(aProcessId != base::GetCurrentProcId());
+
+ if (mChildActors.IsEmpty()) {
+ return nullptr;
+ }
+
+ return SelectTargetActorInternal(aData, aProcessId);
+}
+
+void RemoteWorkerManager::LaunchNewContentProcess(
+ const RemoteWorkerData& aData) {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+
+ nsCOMPtr<nsISerialEventTarget> bgEventTarget = GetCurrentSerialEventTarget();
+
+ using LaunchPromiseType = ContentParent::LaunchPromise;
+ using CallbackParamType = LaunchPromiseType::ResolveOrRejectValue;
+
+ // A new content process must be requested on the main thread. On success,
+ // the success callback will also run on the main thread. On failure, however,
+ // the failure callback must be run on the background thread - it uses
+ // RemoteWorkerManager, and RemoteWorkerManager isn't threadsafe, so the
+ // promise callback will just dispatch the "real" failure callback to the
+ // background thread.
+ auto processLaunchCallback = [principalInfo = aData.principalInfo(),
+ bgEventTarget = std::move(bgEventTarget),
+ self = RefPtr<RemoteWorkerManager>(this)](
+ const CallbackParamType& aValue,
+ const nsCString& remoteType) mutable {
+ if (aValue.IsResolve()) {
+ LOG(("LaunchNewContentProcess: successfully got child process"));
+
+ // The failure callback won't run, and we're on the main thread, so
+ // we need to properly release the thread-unsafe RemoteWorkerManager.
+ NS_ProxyRelease(__func__, bgEventTarget, self.forget());
+ } else {
+ // The "real" failure callback.
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ __func__, [self = std::move(self), remoteType] {
+ nsTArray<Pending> uncancelled;
+ auto pendings = std::move(self->mPendings);
+
+ for (const auto& pending : pendings) {
+ const auto& workerRemoteType = pending.mData.remoteType();
+ if (self->MatchRemoteType(remoteType, workerRemoteType)) {
+ LOG(
+ ("LaunchNewContentProcess: Cancel pending with "
+ "workerRemoteType=%s",
+ workerRemoteType.get()));
+ pending.mController->CreationFailed();
+ } else {
+ uncancelled.AppendElement(pending);
+ }
+ }
+
+ std::swap(self->mPendings, uncancelled);
+ });
+
+ bgEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+ }
+ };
+
+ LOG(("LaunchNewContentProcess: remoteType=%s", aData.remoteType().get()));
+
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ __func__, [callback = std::move(processLaunchCallback),
+ workerRemoteType = aData.remoteType()]() mutable {
+ auto remoteType =
+ workerRemoteType.IsEmpty() ? DEFAULT_REMOTE_TYPE : workerRemoteType;
+
+ RefPtr<LaunchPromiseType> onFinished;
+ if (!AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
+ // Request a process making sure to specify aPreferUsed=true. For a
+ // given remoteType there's a pool size limit. If we pass aPreferUsed
+ // here, then if there's any process in the pool already, we will use
+ // that. If we pass false (which is the default if omitted), then
+ // this call will spawn a new process if the pool isn't at its limit
+ // yet.
+ //
+ // (Our intent is never to grow the pool size here. Our logic gets
+ // here because our current logic on PBackground is only aware of
+ // RemoteWorkerServiceParent actors that have registered themselves,
+ // which is fundamentally unaware of processes that will match in the
+ // future when they register. So we absolutely are fine with and want
+ // any existing processes.)
+ onFinished = ContentParent::GetNewOrUsedBrowserProcessAsync(
+ /* aRemoteType = */ remoteType,
+ /* aGroup */ nullptr,
+ hal::ProcessPriority::PROCESS_PRIORITY_FOREGROUND,
+ /* aPreferUsed */ true);
+ } else {
+ // We can find this event still in flight after having been asked to
+ // shutdown. Let's fake a failure to ensure our callback is called
+ // such that we clean up everything properly.
+ onFinished = LaunchPromiseType::CreateAndReject(
+ NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
+ }
+ onFinished->Then(GetCurrentSerialEventTarget(), __func__,
+ [callback = std::move(callback),
+ remoteType](const CallbackParamType& aValue) mutable {
+ callback(aValue, remoteType);
+ });
+ });
+
+ SchedulerGroup::Dispatch(TaskCategory::Other, r.forget());
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/remoteworkers/RemoteWorkerManager.h b/dom/workers/remoteworkers/RemoteWorkerManager.h
new file mode 100644
index 0000000000..5ff11ee6e6
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerManager.h
@@ -0,0 +1,118 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_RemoteWorkerManager_h
+#define mozilla_dom_RemoteWorkerManager_h
+
+#include "base/process.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/RemoteWorkerTypes.h"
+#include "mozilla/dom/WorkerPrivate.h" // WorkerKind enum
+#include "nsISupportsImpl.h"
+#include "nsTArray.h"
+
+namespace mozilla::dom {
+
+class RemoteWorkerController;
+class RemoteWorkerServiceParent;
+
+/**
+ * PBackground instance that keeps tracks of RemoteWorkerServiceParent actors
+ * (1 per process, including the main process) and pending
+ * RemoteWorkerController requests to spawn remote workers if the spawn request
+ * can't be immediately fulfilled. Decides which RemoteWorkerServerParent to use
+ * internally via SelectTargetActor in order to select a BackgroundParent
+ * manager on which to create a RemoteWorkerParent.
+ */
+class RemoteWorkerManager final {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(RemoteWorkerManager)
+
+ static already_AddRefed<RemoteWorkerManager> GetOrCreate();
+
+ void RegisterActor(RemoteWorkerServiceParent* aActor);
+
+ void UnregisterActor(RemoteWorkerServiceParent* aActor);
+
+ void Launch(RemoteWorkerController* aController,
+ const RemoteWorkerData& aData, base::ProcessId aProcessId);
+
+ static bool MatchRemoteType(const nsACString& processRemoteType,
+ const nsACString& workerRemoteType);
+
+ /**
+ * Get the child process RemoteType where a RemoteWorker should be
+ * launched.
+ */
+ static Result<nsCString, nsresult> GetRemoteType(
+ const nsCOMPtr<nsIPrincipal>& aPrincipal, WorkerKind aWorkerKind);
+
+ /**
+ * Verify if a remote worker should be allowed to run in the current
+ * child process remoteType.
+ */
+ static bool IsRemoteTypeAllowed(const RemoteWorkerData& aData);
+
+ static bool HasExtensionPrincipal(const RemoteWorkerData& aData);
+
+ private:
+ RemoteWorkerManager();
+ ~RemoteWorkerManager();
+
+ RemoteWorkerServiceParent* SelectTargetActor(const RemoteWorkerData& aData,
+ base::ProcessId aProcessId);
+
+ RemoteWorkerServiceParent* SelectTargetActorInternal(
+ const RemoteWorkerData& aData, base::ProcessId aProcessId) const;
+
+ void LaunchInternal(RemoteWorkerController* aController,
+ RemoteWorkerServiceParent* aTargetActor,
+ const RemoteWorkerData& aData,
+ bool aRemoteWorkerAlreadyRegistered = false);
+
+ void LaunchNewContentProcess(const RemoteWorkerData& aData);
+
+ void AsyncCreationFailed(RemoteWorkerController* aController);
+
+ // Iterate through all RemoteWorkerServiceParent actors with the given
+ // remoteType, starting from the actor related to a child process with pid
+ // aProcessId if needed and available or from a random index otherwise (as if
+ // iterating through a circular array).
+ //
+ // aCallback should be a invokable object with a function signature of
+ // bool (RemoteWorkerServiceParent*, RefPtr<ContentParent>&&)
+ //
+ // aCallback is called with the actor and corresponding ContentParent, should
+ // return false to abort iteration before all actors have been traversed (e.g.
+ // if the desired actor is found), and must not mutate mChildActors (which
+ // shouldn't be an issue because this function is const). aCallback also
+ // doesn't need to worry about proxy-releasing the ContentParent if it isn't
+ // moved out of the parameter.
+ template <typename Callback>
+ void ForEachActor(Callback&& aCallback, const nsACString& aRemoteType,
+ Maybe<base::ProcessId> aProcessId = Nothing()) const;
+
+ // The list of existing RemoteWorkerServiceParent actors for child processes.
+ // Raw pointers because RemoteWorkerServiceParent actors unregister themselves
+ // when destroyed.
+ // XXX For Fission, where we could have a lot of child actors, should we maybe
+ // instead keep either a hash table (PID->actor) or perhaps store the actors
+ // in order, sorted by PID, to avoid linear lookup times?
+ nsTArray<RemoteWorkerServiceParent*> mChildActors;
+ RemoteWorkerServiceParent* mParentActor;
+
+ struct Pending {
+ RefPtr<RemoteWorkerController> mController;
+ RemoteWorkerData mData;
+ };
+
+ nsTArray<Pending> mPendings;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_RemoteWorkerManager_h
diff --git a/dom/workers/remoteworkers/RemoteWorkerParent.cpp b/dom/workers/remoteworkers/RemoteWorkerParent.cpp
new file mode 100644
index 0000000000..ee6c8b436e
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerParent.cpp
@@ -0,0 +1,201 @@
+/* -*- 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 "RemoteWorkerParent.h"
+#include "RemoteWorkerController.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/PFetchEventOpProxyParent.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/Unused.h"
+#include "nsProxyRelease.h"
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+
+namespace {
+
+class UnregisterActorRunnable final : public Runnable {
+ public:
+ explicit UnregisterActorRunnable(
+ already_AddRefed<ThreadsafeContentParentHandle> aParent)
+ : Runnable("UnregisterActorRunnable"), mContentHandle(aParent) {
+ AssertIsOnBackgroundThread();
+ }
+
+ NS_IMETHOD
+ Run() override {
+ AssertIsOnMainThread();
+ if (RefPtr<ContentParent> contentParent =
+ mContentHandle->GetContentParent()) {
+ contentParent->UnregisterRemoveWorkerActor();
+ }
+
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<ThreadsafeContentParentHandle> mContentHandle;
+};
+
+} // namespace
+
+RemoteWorkerParent::RemoteWorkerParent() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(XRE_IsParentProcess());
+}
+
+RemoteWorkerParent::~RemoteWorkerParent() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(XRE_IsParentProcess());
+}
+
+void RemoteWorkerParent::Initialize(bool aAlreadyRegistered) {
+ RefPtr<ThreadsafeContentParentHandle> parent =
+ BackgroundParent::GetContentParentHandle(Manager());
+
+ // Parent is null if the child actor runs on the parent process.
+ if (parent) {
+ if (!aAlreadyRegistered) {
+ parent->RegisterRemoteWorkerActor();
+ }
+
+ NS_ReleaseOnMainThread("RemoteWorkerParent::Initialize ContentParent",
+ parent.forget());
+ }
+}
+
+already_AddRefed<PFetchEventOpProxyParent>
+RemoteWorkerParent::AllocPFetchEventOpProxyParent(
+ const ParentToChildServiceWorkerFetchEventOpArgs& aArgs) {
+ MOZ_CRASH("PFetchEventOpProxyParent actors must be manually constructed!");
+ return nullptr;
+}
+
+void RemoteWorkerParent::ActorDestroy(IProtocol::ActorDestroyReason) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ RefPtr<ThreadsafeContentParentHandle> parent =
+ BackgroundParent::GetContentParentHandle(Manager());
+
+ // Parent is null if the child actor runs on the parent process.
+ if (parent) {
+ RefPtr<UnregisterActorRunnable> r =
+ new UnregisterActorRunnable(parent.forget());
+
+ SchedulerGroup::Dispatch(TaskCategory::Other, r.forget());
+ }
+
+ if (mController) {
+ mController->NoteDeadWorkerActor();
+ mController = nullptr;
+ }
+}
+
+IPCResult RemoteWorkerParent::RecvCreated(const bool& aStatus) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (!mController) {
+ return IPC_OK();
+ }
+
+ if (aStatus) {
+ mController->CreationSucceeded();
+ } else {
+ mController->CreationFailed();
+ }
+
+ return IPC_OK();
+}
+
+IPCResult RemoteWorkerParent::RecvError(const ErrorValue& aValue) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (mController) {
+ mController->ErrorPropagation(aValue);
+ }
+
+ return IPC_OK();
+}
+
+IPCResult RemoteWorkerParent::RecvNotifyLock(const bool& aCreated) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (mController) {
+ mController->NotifyLock(aCreated);
+ }
+
+ return IPC_OK();
+}
+
+IPCResult RemoteWorkerParent::RecvNotifyWebTransport(const bool& aCreated) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (mController) {
+ mController->NotifyWebTransport(aCreated);
+ }
+
+ return IPC_OK();
+}
+
+void RemoteWorkerParent::MaybeSendDelete() {
+ if (mDeleteSent) {
+ return;
+ }
+
+ // For some reason, if the following two lines are swapped, ASan says there's
+ // a UAF...
+ mDeleteSent = true;
+ Unused << Send__delete__(this);
+}
+
+IPCResult RemoteWorkerParent::RecvClose() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (mController) {
+ mController->WorkerTerminated();
+ }
+
+ MaybeSendDelete();
+
+ return IPC_OK();
+}
+
+void RemoteWorkerParent::SetController(RemoteWorkerController* aController) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ mController = aController;
+}
+
+IPCResult RemoteWorkerParent::RecvSetServiceWorkerSkipWaitingFlag(
+ SetServiceWorkerSkipWaitingFlagResolver&& aResolve) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (mController) {
+ mController->SetServiceWorkerSkipWaitingFlag()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [resolve = aResolve](bool /* unused */) { resolve(true); },
+ [resolve = aResolve](nsresult /* unused */) { resolve(false); });
+ } else {
+ aResolve(false);
+ }
+
+ return IPC_OK();
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/remoteworkers/RemoteWorkerParent.h b/dom/workers/remoteworkers/RemoteWorkerParent.h
new file mode 100644
index 0000000000..811206eb91
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerParent.h
@@ -0,0 +1,62 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_RemoteWorkerParent_h
+#define mozilla_dom_RemoteWorkerParent_h
+
+#include "mozilla/dom/PRemoteWorkerParent.h"
+
+namespace mozilla::dom {
+
+class RemoteWorkerController;
+
+/**
+ * PBackground-managed parent actor that is mutually associated with a single
+ * RemoteWorkerController. Relays error/close events to the controller and in
+ * turns is told life-cycle events.
+ */
+class RemoteWorkerParent final : public PRemoteWorkerParent {
+ friend class PRemoteWorkerParent;
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING(RemoteWorkerParent, override);
+
+ RemoteWorkerParent();
+
+ void Initialize(bool aAlreadyRegistered = false);
+
+ void SetController(RemoteWorkerController* aController);
+
+ void MaybeSendDelete();
+
+ private:
+ ~RemoteWorkerParent();
+
+ already_AddRefed<PFetchEventOpProxyParent> AllocPFetchEventOpProxyParent(
+ const ParentToChildServiceWorkerFetchEventOpArgs& aArgs);
+
+ void ActorDestroy(mozilla::ipc::IProtocol::ActorDestroyReason) override;
+
+ mozilla::ipc::IPCResult RecvError(const ErrorValue& aValue);
+
+ mozilla::ipc::IPCResult RecvNotifyLock(const bool& aCreated);
+
+ mozilla::ipc::IPCResult RecvNotifyWebTransport(const bool& aCreated);
+
+ mozilla::ipc::IPCResult RecvClose();
+
+ mozilla::ipc::IPCResult RecvCreated(const bool& aStatus);
+
+ mozilla::ipc::IPCResult RecvSetServiceWorkerSkipWaitingFlag(
+ SetServiceWorkerSkipWaitingFlagResolver&& aResolve);
+
+ bool mDeleteSent = false;
+ RefPtr<RemoteWorkerController> mController;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_RemoteWorkerParent_h
diff --git a/dom/workers/remoteworkers/RemoteWorkerService.cpp b/dom/workers/remoteworkers/RemoteWorkerService.cpp
new file mode 100644
index 0000000000..b62c551c22
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerService.cpp
@@ -0,0 +1,346 @@
+/* -*- 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 "RemoteWorkerService.h"
+
+#include "mozilla/dom/PRemoteWorkerParent.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/ipc/PBackgroundParent.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/Services.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPtr.h"
+#include "nsIObserverService.h"
+#include "nsIThread.h"
+#include "nsThreadUtils.h"
+#include "nsXPCOMPrivate.h"
+#include "RemoteWorkerController.h"
+#include "RemoteWorkerServiceChild.h"
+#include "RemoteWorkerServiceParent.h"
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+
+namespace {
+
+StaticMutex sRemoteWorkerServiceMutex;
+StaticRefPtr<RemoteWorkerService> sRemoteWorkerService;
+
+} // namespace
+
+/**
+ * Block shutdown until the RemoteWorkers have shutdown so that we do not try
+ * and shutdown the RemoteWorkerService "Worker Launcher" thread until they have
+ * cleanly shutdown.
+ *
+ * Note that this shutdown blocker is not used to initiate shutdown of any of
+ * the workers directly; their shutdown is initiated from PBackground in the
+ * parent process. The shutdown blocker just exists to avoid races around
+ * shutting down the worker launcher thread after all of the workers have
+ * shutdown and torn down their actors.
+ *
+ * Currently, it should be the case that the ContentParent should want to keep
+ * the content processes alive until the RemoteWorkers have all reported their
+ * shutdown over IPC (on the "Worker Launcher" thread). So for an orderly
+ * content process shutdown that is waiting for there to no longer be a reason
+ * to keep the content process alive, this blocker should only hang around for
+ * a brief period of time, helping smooth out lifecycle edge cases.
+ *
+ * In the event the content process is trying to shutdown while the
+ * RemoteWorkers think they should still be alive, it's possible that this
+ * blocker could expose the relevant logic error in the parent process if no
+ * attempt is made to shutdown the RemoteWorker.
+ *
+ * ## Major Implementation Note: This is not actually an nsIAsyncShutdownClient
+ *
+ * Until https://bugzilla.mozilla.org/show_bug.cgi?id=1760855 provides us with a
+ * non-JS implementation of nsIAsyncShutdownService, this implementation
+ * actually uses event loop spinning. The patch on
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1775784 that changed us to use
+ * this hack can be reverted when the time is right.
+ *
+ * Event loop spinning is handled by `RemoteWorkerService::Observe` and it calls
+ * our exposed `ShouldBlockShutdown()` to know when to stop spinning.
+ */
+class RemoteWorkerServiceShutdownBlocker final {
+ ~RemoteWorkerServiceShutdownBlocker() = default;
+
+ public:
+ explicit RemoteWorkerServiceShutdownBlocker(RemoteWorkerService* aService)
+ : mService(aService), mBlockShutdown(true) {}
+
+ void RemoteWorkersAllGoneAllowShutdown() {
+ mService->FinishShutdown();
+ mService = nullptr;
+
+ mBlockShutdown = false;
+ }
+
+ bool ShouldBlockShutdown() { return mBlockShutdown; }
+
+ NS_INLINE_DECL_REFCOUNTING(RemoteWorkerServiceShutdownBlocker);
+
+ RefPtr<RemoteWorkerService> mService;
+ bool mBlockShutdown;
+};
+
+RemoteWorkerServiceKeepAlive::RemoteWorkerServiceKeepAlive(
+ RemoteWorkerServiceShutdownBlocker* aBlocker)
+ : mBlocker(aBlocker) {
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+RemoteWorkerServiceKeepAlive::~RemoteWorkerServiceKeepAlive() {
+ // Dispatch a runnable to the main thread to tell the Shutdown Blocker to
+ // remove itself and notify the RemoteWorkerService it can finish its
+ // shutdown. We dispatch this to the main thread even if we are already on
+ // the main thread.
+ nsCOMPtr<nsIRunnable> r =
+ NS_NewRunnableFunction(__func__, [blocker = std::move(mBlocker)] {
+ blocker->RemoteWorkersAllGoneAllowShutdown();
+ });
+ MOZ_ALWAYS_SUCCEEDS(
+ SchedulerGroup::Dispatch(TaskCategory::Other, r.forget()));
+}
+
+/* static */
+void RemoteWorkerService::Initialize() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ StaticMutexAutoLock lock(sRemoteWorkerServiceMutex);
+ MOZ_ASSERT(!sRemoteWorkerService);
+
+ RefPtr<RemoteWorkerService> service = new RemoteWorkerService();
+
+ // ## Content Process Initialization Case
+ //
+ // We are being told to initialize now that we know what our remote type is.
+ // Now is a fine time to call InitializeOnMainThread.
+ if (!XRE_IsParentProcess()) {
+ nsresult rv = service->InitializeOnMainThread();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ sRemoteWorkerService = service;
+ return;
+ }
+ // ## Parent Process Initialization Case
+ //
+ // Otherwise we are in the parent process and were invoked by
+ // nsLayoutStatics::Initialize. We wait until profile-after-change to kick
+ // off the Worker Launcher thread and have it connect to PBackground. This is
+ // an appropriate time for remote worker APIs to come online, especially
+ // because the PRemoteWorkerService mechanism needs processes to eagerly
+ // register themselves with PBackground since the design explicitly intends to
+ // avoid blocking on the main threads. (Disclaimer: Currently, things block
+ // on the main thread.)
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (NS_WARN_IF(!obs)) {
+ return;
+ }
+
+ nsresult rv = obs->AddObserver(service, "profile-after-change", false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ sRemoteWorkerService = service;
+}
+
+/* static */
+nsIThread* RemoteWorkerService::Thread() {
+ StaticMutexAutoLock lock(sRemoteWorkerServiceMutex);
+ MOZ_ASSERT(sRemoteWorkerService);
+ MOZ_ASSERT(sRemoteWorkerService->mThread);
+ return sRemoteWorkerService->mThread;
+}
+
+/* static */
+already_AddRefed<RemoteWorkerServiceKeepAlive>
+RemoteWorkerService::MaybeGetKeepAlive() {
+ StaticMutexAutoLock lock(sRemoteWorkerServiceMutex);
+ // In normal operation no one should be calling this without a service
+ // existing, so assert, but we'll also handle this being null as it is a
+ // plausible shutdown race.
+ MOZ_ASSERT(sRemoteWorkerService);
+ if (!sRemoteWorkerService) {
+ return nullptr;
+ }
+
+ // Note that this value can be null, but this all handles that.
+ auto lockedKeepAlive = sRemoteWorkerService->mKeepAlive.Lock();
+ RefPtr<RemoteWorkerServiceKeepAlive> extraRef = *lockedKeepAlive;
+ return extraRef.forget();
+}
+
+nsresult RemoteWorkerService::InitializeOnMainThread() {
+ // I would like to call this thread "DOM Remote Worker Launcher", but the max
+ // length is 16 chars.
+ nsresult rv = NS_NewNamedThread("Worker Launcher", getter_AddRefs(mThread));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (NS_WARN_IF(!obs)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mShutdownBlocker = new RemoteWorkerServiceShutdownBlocker(this);
+
+ {
+ RefPtr<RemoteWorkerServiceKeepAlive> keepAlive =
+ new RemoteWorkerServiceKeepAlive(mShutdownBlocker);
+
+ auto lockedKeepAlive = mKeepAlive.Lock();
+ *lockedKeepAlive = std::move(keepAlive);
+ }
+
+ RefPtr<RemoteWorkerService> self = this;
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ "InitializeThread", [self]() { self->InitializeOnTargetThread(); });
+
+ rv = mThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+RemoteWorkerService::RemoteWorkerService()
+ : mKeepAlive(nullptr, "RemoteWorkerService::mKeepAlive") {
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+RemoteWorkerService::~RemoteWorkerService() = default;
+
+void RemoteWorkerService::InitializeOnTargetThread() {
+ MOZ_ASSERT(mThread);
+ MOZ_ASSERT(mThread->IsOnCurrentThread());
+
+ PBackgroundChild* backgroundActor =
+ BackgroundChild::GetOrCreateForCurrentThread();
+ if (NS_WARN_IF(!backgroundActor)) {
+ return;
+ }
+
+ RefPtr<RemoteWorkerServiceChild> serviceActor =
+ MakeAndAddRef<RemoteWorkerServiceChild>();
+ if (NS_WARN_IF(!backgroundActor->SendPRemoteWorkerServiceConstructor(
+ serviceActor))) {
+ return;
+ }
+
+ // Now we are ready!
+ mActor = serviceActor;
+}
+
+void RemoteWorkerService::CloseActorOnTargetThread() {
+ MOZ_ASSERT(mThread);
+ MOZ_ASSERT(mThread->IsOnCurrentThread());
+
+ // If mActor is nullptr it means that initialization failed.
+ if (mActor) {
+ // Here we need to shutdown the IPC protocol.
+ mActor->Send__delete__(mActor);
+ mActor = nullptr;
+ }
+}
+
+NS_IMETHODIMP
+RemoteWorkerService::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ MOZ_ASSERT(mThread);
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ }
+
+ // Note that nsObserverList::NotifyObservers will hold a strong reference to
+ // our instance throughout the entire duration of this call, so it is not
+ // necessary for us to hold a kungFuDeathGrip here.
+
+ // Drop our keep-alive. This could immediately result in our blocker saying
+ // it's okay for us to shutdown. SpinEventLoopUntil checks the predicate
+ // before spinning, so in the ideal case we will not spin the loop at all.
+ BeginShutdown();
+
+ MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
+ "RemoteWorkerService::Observe"_ns,
+ [&]() { return !mShutdownBlocker->ShouldBlockShutdown(); }));
+
+ mShutdownBlocker = nullptr;
+
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(!strcmp(aTopic, "profile-after-change"));
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, "profile-after-change");
+ }
+
+ return InitializeOnMainThread();
+}
+
+void RemoteWorkerService::BeginShutdown() {
+ // Drop our keepalive reference which may allow near-immediate removal of the
+ // blocker.
+ auto lockedKeepAlive = mKeepAlive.Lock();
+ *lockedKeepAlive = nullptr;
+}
+
+void RemoteWorkerService::FinishShutdown() {
+ // Clear the singleton before spinning the event loop when shutting down the
+ // thread so that MaybeGetKeepAlive() can assert if there are any late calls
+ // and to better reflect the actual state.
+ //
+ // Our caller, the RemoteWorkerServiceShutdownBlocker, will continue to hold a
+ // strong reference to us until we return from this call, so there are no
+ // lifecycle implications to dropping this reference.
+ {
+ StaticMutexAutoLock lock(sRemoteWorkerServiceMutex);
+ sRemoteWorkerService = nullptr;
+ }
+
+ RefPtr<RemoteWorkerService> self = this;
+ nsCOMPtr<nsIRunnable> r =
+ NS_NewRunnableFunction("RemoteWorkerService::CloseActorOnTargetThread",
+ [self]() { self->CloseActorOnTargetThread(); });
+
+ mThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+
+ // We've posted a shutdown message; now shutdown the thread. This will spin
+ // a nested event loop waiting for the thread to process all pending events
+ // (including the just dispatched CloseActorOnTargetThread which will close
+ // the actor), ensuring to block main thread shutdown long enough to avoid
+ // races.
+ mThread->Shutdown();
+ mThread = nullptr;
+}
+
+NS_IMPL_ISUPPORTS(RemoteWorkerService, nsIObserver)
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/remoteworkers/RemoteWorkerService.h b/dom/workers/remoteworkers/RemoteWorkerService.h
new file mode 100644
index 0000000000..9e05e3958b
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerService.h
@@ -0,0 +1,123 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_RemoteWorkerService_h
+#define mozilla_dom_RemoteWorkerService_h
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/DataMutex.h"
+#include "nsCOMPtr.h"
+#include "nsIObserver.h"
+#include "nsISupportsImpl.h"
+
+class nsIThread;
+
+namespace mozilla::dom {
+
+class RemoteWorkerService;
+class RemoteWorkerServiceChild;
+class RemoteWorkerServiceShutdownBlocker;
+
+/**
+ * Refcounted lifecycle helper; when its refcount goes to zero its destructor
+ * will call RemoteWorkerService::Shutdown() which will remove the shutdown
+ * blocker and shutdown the "Worker Launcher" thread.
+ *
+ * The RemoteWorkerService itself will hold a reference to this singleton which
+ * it will use to hand out additional refcounts to RemoteWorkerChild instances.
+ * When the shutdown blocker is notified that it's time to shutdown, the
+ * RemoteWorkerService's reference will be dropped.
+ */
+class RemoteWorkerServiceKeepAlive {
+ public:
+ explicit RemoteWorkerServiceKeepAlive(
+ RemoteWorkerServiceShutdownBlocker* aBlocker);
+
+ private:
+ ~RemoteWorkerServiceKeepAlive();
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RemoteWorkerServiceKeepAlive);
+
+ RefPtr<RemoteWorkerServiceShutdownBlocker> mBlocker;
+};
+
+/**
+ * Every process has a RemoteWorkerService which does the actual spawning of
+ * RemoteWorkerChild instances. The RemoteWorkerService creates a "Worker
+ * Launcher" thread at initialization on which it creates a
+ * RemoteWorkerServiceChild to service spawn requests. The thread is exposed as
+ * RemoteWorkerService::Thread(). A new/distinct thread is used because we
+ * (eventually) don't want to deal with main-thread contention, content
+ * processes have no equivalent of a PBackground thread, and actors are bound to
+ * specific threads.
+ *
+ * (Disclaimer: currently most RemoteWorkerOps need to happen on the main thread
+ * because the main-thread ends up as the owner of the worker and all
+ * manipulation of the worker must happen from the owning thread.)
+ */
+class RemoteWorkerService final : public nsIObserver {
+ friend class RemoteWorkerServiceShutdownBlocker;
+ friend class RemoteWorkerServiceKeepAlive;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ // To be called when a process is initialized on main-thread.
+ static void Initialize();
+
+ static nsIThread* Thread();
+
+ // Called by RemoteWorkerChild instances on the "Worker Launcher" thread at
+ // their creation to assist in tracking when it's safe to shutdown the
+ // RemoteWorkerService and "Worker Launcher" thread. This method will return
+ // a null pointer if the RemoteWorkerService has already begun shutdown.
+ //
+ // This is somewhat awkwardly a static method because the RemoteWorkerChild
+ // instances are not managed by RemoteWorkerServiceChild, but instead are
+ // managed by PBackground(Child). So we either need to find the
+ // RemoteWorkerService via the hidden singleton or by having the
+ // RemoteWorkerChild use PBackgroundChild::ManagedPRemoteWorkerServiceChild()
+ // to locate the instance. We are choosing to use the singleton because we
+ // already need to acquire a mutex in the call regardless and the upcoming
+ // refactorings may want to start using new toplevel protocols and this will
+ // avoid requiring a change when that happens.
+ static already_AddRefed<RemoteWorkerServiceKeepAlive> MaybeGetKeepAlive();
+
+ private:
+ RemoteWorkerService();
+ ~RemoteWorkerService();
+
+ nsresult InitializeOnMainThread();
+
+ void InitializeOnTargetThread();
+
+ void CloseActorOnTargetThread();
+
+ // Called by RemoteWorkerServiceShutdownBlocker when it's time to drop the
+ // RemoteWorkerServiceKeepAlive reference.
+ void BeginShutdown();
+
+ // Called by RemoteWorkerServiceShutdownBlocker when the blocker has been
+ // removed and it's safe to shutdown the "Worker Launcher" thread.
+ void FinishShutdown();
+
+ nsCOMPtr<nsIThread> mThread;
+ RefPtr<RemoteWorkerServiceChild> mActor;
+ // The keep-alive is set and cleared on the main thread but we will hand out
+ // additional references to it from the "Worker Launcher" thread, so it's
+ // appropriate to use a mutex. (Alternately we could have used a ThreadBound
+ // and set and cleared on the "Worker Launcher" thread, but that would
+ // involve more moving parts and could have complicated edge cases.)
+ DataMutex<RefPtr<RemoteWorkerServiceKeepAlive>> mKeepAlive;
+ // In order to poll the blocker to know when we can stop spinning the event
+ // loop at shutdown, we retain a reference to the blocker.
+ RefPtr<RemoteWorkerServiceShutdownBlocker> mShutdownBlocker;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_RemoteWorkerService_h
diff --git a/dom/workers/remoteworkers/RemoteWorkerServiceChild.cpp b/dom/workers/remoteworkers/RemoteWorkerServiceChild.cpp
new file mode 100644
index 0000000000..100908064e
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerServiceChild.cpp
@@ -0,0 +1,16 @@
+/* -*- 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 "RemoteWorkerServiceChild.h"
+#include "RemoteWorkerController.h"
+
+namespace mozilla::dom {
+
+RemoteWorkerServiceChild::RemoteWorkerServiceChild() = default;
+
+RemoteWorkerServiceChild::~RemoteWorkerServiceChild() = default;
+
+} // namespace mozilla::dom
diff --git a/dom/workers/remoteworkers/RemoteWorkerServiceChild.h b/dom/workers/remoteworkers/RemoteWorkerServiceChild.h
new file mode 100644
index 0000000000..41ae879b29
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerServiceChild.h
@@ -0,0 +1,34 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_RemoteWorkerServiceChild_h
+#define mozilla_dom_RemoteWorkerServiceChild_h
+
+#include "mozilla/dom/PRemoteWorkerServiceChild.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla::dom {
+
+class RemoteWorkerController;
+class RemoteWorkerData;
+
+/**
+ * "Worker Launcher"-thread child actor created by the RemoteWorkerService to
+ * register itself with the PBackground RemoteWorkerManager in the parent.
+ */
+class RemoteWorkerServiceChild final : public PRemoteWorkerServiceChild {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(RemoteWorkerServiceChild, final)
+
+ RemoteWorkerServiceChild();
+
+ private:
+ ~RemoteWorkerServiceChild();
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_RemoteWorkerServiceChild_h
diff --git a/dom/workers/remoteworkers/RemoteWorkerServiceParent.cpp b/dom/workers/remoteworkers/RemoteWorkerServiceParent.cpp
new file mode 100644
index 0000000000..c7fd5ea6c8
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerServiceParent.cpp
@@ -0,0 +1,34 @@
+/* -*- 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 "RemoteWorkerServiceParent.h"
+#include "RemoteWorkerManager.h"
+#include "mozilla/ipc/BackgroundParent.h"
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+
+RemoteWorkerServiceParent::RemoteWorkerServiceParent()
+ : mManager(RemoteWorkerManager::GetOrCreate()) {}
+
+RemoteWorkerServiceParent::~RemoteWorkerServiceParent() = default;
+
+void RemoteWorkerServiceParent::Initialize(const nsACString& aRemoteType) {
+ AssertIsOnBackgroundThread();
+ mRemoteType = aRemoteType;
+ mManager->RegisterActor(this);
+}
+
+void RemoteWorkerServiceParent::ActorDestroy(IProtocol::ActorDestroyReason) {
+ AssertIsOnBackgroundThread();
+ mManager->UnregisterActor(this);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/remoteworkers/RemoteWorkerServiceParent.h b/dom/workers/remoteworkers/RemoteWorkerServiceParent.h
new file mode 100644
index 0000000000..31ed29a78c
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerServiceParent.h
@@ -0,0 +1,41 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_RemoteWorkerServiceParent_h
+#define mozilla_dom_RemoteWorkerServiceParent_h
+
+#include "mozilla/dom/PRemoteWorkerServiceParent.h"
+#include "mozilla/dom/RemoteType.h"
+
+namespace mozilla::dom {
+
+class RemoteWorkerManager;
+
+/**
+ * PBackground parent actor that registers with the PBackground
+ * RemoteWorkerManager and used to relay spawn requests.
+ */
+class RemoteWorkerServiceParent final : public PRemoteWorkerServiceParent {
+ public:
+ RemoteWorkerServiceParent();
+ NS_INLINE_DECL_REFCOUNTING(RemoteWorkerServiceParent, override);
+
+ void ActorDestroy(mozilla::ipc::IProtocol::ActorDestroyReason) override;
+
+ void Initialize(const nsACString& aRemoteType);
+
+ nsCString GetRemoteType() const { return mRemoteType; }
+
+ private:
+ ~RemoteWorkerServiceParent();
+
+ RefPtr<RemoteWorkerManager> mManager;
+ nsCString mRemoteType = NOT_REMOTE_TYPE;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_RemoteWorkerServiceParent_h
diff --git a/dom/workers/remoteworkers/RemoteWorkerTypes.ipdlh b/dom/workers/remoteworkers/RemoteWorkerTypes.ipdlh
new file mode 100644
index 0000000000..67a23e9aae
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerTypes.ipdlh
@@ -0,0 +1,123 @@
+/* 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 ClientIPCTypes;
+include IPCServiceWorkerDescriptor;
+include IPCServiceWorkerRegistrationDescriptor;
+include PBackgroundSharedTypes;
+include URIParams;
+include DOMTypes;
+include NeckoChannelParams;
+include ProtocolTypes;
+
+include "mozilla/dom/ClientIPCUtils.h";
+include "mozilla/dom/ReferrerInfoUtils.h";
+include "mozilla/dom/WorkerIPCUtils.h";
+
+using struct mozilla::void_t from "mozilla/ipc/IPCCore.h";
+using mozilla::dom::RequestCredentials from "mozilla/dom/RequestBinding.h";
+using mozilla::StorageAccess from "mozilla/StorageAccess.h";
+using mozilla::OriginTrials from "mozilla/OriginTrialsIPCUtils.h";
+using mozilla::dom::WorkerType from "mozilla/dom/WorkerBinding.h";
+
+namespace mozilla {
+namespace dom {
+
+struct ServiceWorkerData {
+ IPCServiceWorkerDescriptor descriptor;
+ IPCServiceWorkerRegistrationDescriptor registrationDescriptor;
+ nsString cacheName;
+ uint32_t loadFlags;
+ nsString id;
+};
+
+union OptionalServiceWorkerData {
+ void_t;
+ ServiceWorkerData;
+};
+
+struct RemoteWorkerData
+{
+ // This should only be used for devtools.
+ nsString originalScriptURL;
+
+ // It is important to pass these as URIParams instead of strings for blob
+ // URLs: they carry an additional bit of state with them (mIsRevoked) that
+ // gives us a chance to use them, even after they've been revoked. Because
+ // we're asynchronously calling into the parent process before potentially
+ // loading the worker, it is important to keep this state. Note that this
+ // isn't a panacea: once the URL has been revoked, it'll give the worker 5
+ // seconds to actually load it; so it's possible to still fail to load the
+ // blob URL if it takes too long to do the round trip.
+ URIParams baseScriptURL;
+ URIParams resolvedScriptURL;
+
+ nsString name;
+ WorkerType type;
+ RequestCredentials credentials;
+
+ PrincipalInfo loadingPrincipalInfo;
+ PrincipalInfo principalInfo;
+ PrincipalInfo partitionedPrincipalInfo;
+
+ bool useRegularPrincipal;
+ bool hasStorageAccessPermissionGranted;
+
+ CookieJarSettingsArgs cookieJarSettings;
+
+ nsCString domain;
+
+ bool isSecureContext;
+
+ IPCClientInfo? clientInfo;
+
+ nullable nsIReferrerInfo referrerInfo;
+
+ StorageAccess storageAccess;
+
+ bool isThirdPartyContextToTopWindow;
+
+ bool shouldResistFingerprinting;
+
+ OriginTrials originTrials;
+
+ OptionalServiceWorkerData serviceWorkerData;
+
+ nsID agentClusterId;
+
+ // Child process remote type where the worker should only run on.
+ nsCString remoteType;
+};
+
+// ErrorData/ErrorDataNote correspond to WorkerErrorReport/WorkerErrorNote
+// which in turn correspond to JSErrorReport/JSErrorNotes which allows JS to
+// report complicated errors such as redeclarations that involve multiple
+// distinct lines. For more generic error-propagation IPC structures, see bug
+// 1357463 on making ErrorResult usable over IPC.
+
+struct ErrorDataNote {
+ uint32_t lineNumber;
+ uint32_t columnNumber;
+ nsString message;
+ nsString filename;
+};
+
+struct ErrorData {
+ bool isWarning;
+ uint32_t lineNumber;
+ uint32_t columnNumber;
+ nsString message;
+ nsString filename;
+ nsString line;
+ ErrorDataNote[] notes;
+};
+
+union ErrorValue {
+ nsresult;
+ ErrorData;
+ void_t;
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/remoteworkers/moz.build b/dom/workers/remoteworkers/moz.build
new file mode 100644
index 0000000000..9983b7dd10
--- /dev/null
+++ b/dom/workers/remoteworkers/moz.build
@@ -0,0 +1,45 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS.mozilla.dom += [
+ "RemoteWorkerChild.h",
+ "RemoteWorkerController.h",
+ "RemoteWorkerControllerChild.h",
+ "RemoteWorkerControllerParent.h",
+ "RemoteWorkerManager.h",
+ "RemoteWorkerParent.h",
+ "RemoteWorkerService.h",
+ "RemoteWorkerServiceChild.h",
+ "RemoteWorkerServiceParent.h",
+]
+
+UNIFIED_SOURCES += [
+ "RemoteWorkerChild.cpp",
+ "RemoteWorkerController.cpp",
+ "RemoteWorkerControllerChild.cpp",
+ "RemoteWorkerControllerParent.cpp",
+ "RemoteWorkerManager.cpp",
+ "RemoteWorkerParent.cpp",
+ "RemoteWorkerService.cpp",
+ "RemoteWorkerServiceChild.cpp",
+ "RemoteWorkerServiceParent.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/dom/serviceworkers",
+ "/xpcom/build",
+]
+
+IPDL_SOURCES += [
+ "PRemoteWorker.ipdl",
+ "PRemoteWorkerController.ipdl",
+ "PRemoteWorkerService.ipdl",
+ "RemoteWorkerTypes.ipdlh",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"