summaryrefslogtreecommitdiffstats
path: root/dom/workers/remoteworkers/RemoteWorkerManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/workers/remoteworkers/RemoteWorkerManager.cpp')
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerManager.cpp735
1 files changed, 735 insertions, 0 deletions
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