diff options
Diffstat (limited to 'dom/workers/remoteworkers/RemoteWorkerManager.cpp')
-rw-r--r-- | dom/workers/remoteworkers/RemoteWorkerManager.cpp | 735 |
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 |