Bug 1861467 - [wpt-sync] Update web-platform-tests to eedf737ce39c512d0ca3471f988972e...
[gecko.git] / dom / workers / remoteworkers / RemoteWorkerManager.cpp
blobfefa91d3849e0b78a47c9ae850a4870e3de67eae
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "RemoteWorkerManager.h"
9 #include <utility>
11 #include "mozilla/SchedulerGroup.h"
12 #include "mozilla/ScopeExit.h"
13 #include "mozilla/dom/ContentChild.h" // ContentChild::GetSingleton
14 #include "mozilla/dom/ProcessIsolation.h"
15 #include "mozilla/dom/RemoteWorkerController.h"
16 #include "mozilla/dom/RemoteWorkerParent.h"
17 #include "mozilla/ipc/BackgroundParent.h"
18 #include "mozilla/ipc/BackgroundUtils.h"
19 #include "mozilla/ipc/PBackgroundParent.h"
20 #include "mozilla/StaticPrefs_extensions.h"
21 #include "nsCOMPtr.h"
22 #include "nsImportModule.h"
23 #include "nsIXULRuntime.h"
24 #include "nsTArray.h"
25 #include "nsThreadUtils.h"
26 #include "RemoteWorkerServiceParent.h"
28 mozilla::LazyLogModule gRemoteWorkerManagerLog("RemoteWorkerManager");
30 #ifdef LOG
31 # undef LOG
32 #endif
33 #define LOG(fmt) \
34 MOZ_LOG(gRemoteWorkerManagerLog, mozilla::LogLevel::Verbose, fmt)
36 namespace mozilla {
38 using namespace ipc;
40 namespace dom {
42 namespace {
44 // Raw pointer because this object is kept alive by RemoteWorkerServiceParent
45 // actors.
46 RemoteWorkerManager* sRemoteWorkerManager;
48 bool IsServiceWorker(const RemoteWorkerData& aData) {
49 return aData.serviceWorkerData().type() ==
50 OptionalServiceWorkerData::TServiceWorkerData;
53 void TransmitPermissionsAndBlobURLsForPrincipalInfo(
54 ContentParent* aContentParent, const PrincipalInfo& aPrincipalInfo) {
55 AssertIsOnMainThread();
56 MOZ_ASSERT(aContentParent);
58 auto principalOrErr = PrincipalInfoToPrincipal(aPrincipalInfo);
60 if (NS_WARN_IF(principalOrErr.isErr())) {
61 return;
64 nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
66 aContentParent->TransmitBlobURLsForPrincipal(principal);
68 MOZ_ALWAYS_SUCCEEDS(
69 aContentParent->TransmitPermissionsForPrincipal(principal));
72 } // namespace
74 // static
75 bool RemoteWorkerManager::MatchRemoteType(const nsACString& processRemoteType,
76 const nsACString& workerRemoteType) {
77 LOG(("MatchRemoteType [processRemoteType=%s, workerRemoteType=%s]",
78 PromiseFlatCString(processRemoteType).get(),
79 PromiseFlatCString(workerRemoteType).get()));
81 // Respecting COOP and COEP requires processing headers in the parent
82 // process in order to choose an appropriate content process, but the
83 // workers' ScriptLoader processes headers in content processes. An
84 // intermediary step that provides security guarantees is to simply never
85 // allow SharedWorkers and ServiceWorkers to exist in a COOP+COEP process.
86 // The ultimate goal is to allow these worker types to be put in such
87 // processes based on their script response headers.
88 // https://bugzilla.mozilla.org/show_bug.cgi?id=1595206
90 // RemoteWorkerManager::GetRemoteType should not select this remoteType
91 // and so workerRemoteType is not expected to be set to a coop+coep
92 // remoteType and here we can just assert that it is not happening.
93 MOZ_ASSERT(!IsWebCoopCoepRemoteType(workerRemoteType));
95 return processRemoteType.Equals(workerRemoteType);
98 // static
99 Result<nsCString, nsresult> RemoteWorkerManager::GetRemoteType(
100 const nsCOMPtr<nsIPrincipal>& aPrincipal, WorkerKind aWorkerKind) {
101 AssertIsOnMainThread();
103 MOZ_ASSERT_IF(aWorkerKind == WorkerKind::WorkerKindService,
104 aPrincipal->GetIsContentPrincipal());
106 // If E10S is fully disabled, there are no decisions to be made, and we need
107 // to finish the load in the parent process.
108 if (!BrowserTabsRemoteAutostart()) {
109 LOG(("GetRemoteType: Loading in parent process as e10s is disabled"));
110 return NOT_REMOTE_TYPE;
113 nsCString preferredRemoteType = DEFAULT_REMOTE_TYPE;
114 if (aWorkerKind == WorkerKind::WorkerKindShared) {
115 if (auto* contentChild = ContentChild::GetSingleton()) {
116 // For a shared worker set the preferred remote type to the content
117 // child process remote type.
118 preferredRemoteType = contentChild->GetRemoteType();
119 } else if (aPrincipal->IsSystemPrincipal()) {
120 preferredRemoteType = NOT_REMOTE_TYPE;
124 auto result = IsolationOptionsForWorker(
125 aPrincipal, aWorkerKind, preferredRemoteType, FissionAutostart());
126 if (NS_WARN_IF(result.isErr())) {
127 LOG(("GetRemoteType Abort: IsolationOptionsForWorker failed"));
128 return Err(NS_ERROR_DOM_ABORT_ERR);
130 auto options = result.unwrap();
132 if (MOZ_LOG_TEST(gRemoteWorkerManagerLog, LogLevel::Verbose)) {
133 nsCString principalOrigin;
134 aPrincipal->GetOrigin(principalOrigin);
136 LOG(
137 ("GetRemoteType workerType=%s, principal=%s, "
138 "preferredRemoteType=%s, selectedRemoteType=%s",
139 aWorkerKind == WorkerKind::WorkerKindService ? "service" : "shared",
140 principalOrigin.get(), preferredRemoteType.get(),
141 options.mRemoteType.get()));
144 return options.mRemoteType;
147 // static
148 bool RemoteWorkerManager::HasExtensionPrincipal(const RemoteWorkerData& aData) {
149 auto principalInfo = aData.principalInfo();
150 return principalInfo.type() == PrincipalInfo::TContentPrincipalInfo &&
151 // This helper method is also called from the background thread and so
152 // we can't check if the principal does have an addonPolicy object
153 // associated and we have to resort to check the url scheme instead.
154 StringBeginsWith(principalInfo.get_ContentPrincipalInfo().spec(),
155 "moz-extension://"_ns);
158 // static
159 bool RemoteWorkerManager::IsRemoteTypeAllowed(const RemoteWorkerData& aData) {
160 AssertIsOnMainThread();
162 // If Gecko is running in single process mode, there is no child process
163 // to select and we have to just consider it valid (if it should haven't
164 // been launched it should have been already prevented before reaching
165 // a RemoteWorkerChild instance).
166 if (!BrowserTabsRemoteAutostart()) {
167 return true;
170 const auto& principalInfo = aData.principalInfo();
172 auto* contentChild = ContentChild::GetSingleton();
173 if (!contentChild) {
174 // If e10s isn't disabled, only workers related to the system principal
175 // should be allowed to run in the parent process, and extension principals
176 // if extensions.webextensions.remote is false.
177 return principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo ||
178 (!StaticPrefs::extensions_webextensions_remote() &&
179 aData.remoteType().Equals(NOT_REMOTE_TYPE) &&
180 HasExtensionPrincipal(aData));
183 auto principalOrErr = PrincipalInfoToPrincipal(principalInfo);
184 if (NS_WARN_IF(principalOrErr.isErr())) {
185 return false;
187 nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
189 // Recompute the remoteType based on the principal, to double-check that it
190 // has not been tempered to select a different child process than the one
191 // expected.
192 bool isServiceWorker = aData.serviceWorkerData().type() ==
193 OptionalServiceWorkerData::TServiceWorkerData;
194 auto remoteType = GetRemoteType(
195 principal, isServiceWorker ? WorkerKindService : WorkerKindShared);
196 if (NS_WARN_IF(remoteType.isErr())) {
197 LOG(("IsRemoteTypeAllowed: Error to retrieve remote type"));
198 return false;
201 return MatchRemoteType(remoteType.unwrap(), contentChild->GetRemoteType());
204 /* static */
205 already_AddRefed<RemoteWorkerManager> RemoteWorkerManager::GetOrCreate() {
206 AssertIsInMainProcess();
207 AssertIsOnBackgroundThread();
209 if (!sRemoteWorkerManager) {
210 sRemoteWorkerManager = new RemoteWorkerManager();
213 RefPtr<RemoteWorkerManager> rwm = sRemoteWorkerManager;
214 return rwm.forget();
217 RemoteWorkerManager::RemoteWorkerManager() : mParentActor(nullptr) {
218 AssertIsInMainProcess();
219 AssertIsOnBackgroundThread();
220 MOZ_ASSERT(!sRemoteWorkerManager);
223 RemoteWorkerManager::~RemoteWorkerManager() {
224 AssertIsInMainProcess();
225 AssertIsOnBackgroundThread();
226 MOZ_ASSERT(sRemoteWorkerManager == this);
227 sRemoteWorkerManager = nullptr;
230 void RemoteWorkerManager::RegisterActor(RemoteWorkerServiceParent* aActor) {
231 AssertIsInMainProcess();
232 AssertIsOnBackgroundThread();
233 MOZ_ASSERT(aActor);
235 if (!BackgroundParent::IsOtherProcessActor(aActor->Manager())) {
236 MOZ_ASSERT(!mParentActor);
237 mParentActor = aActor;
238 MOZ_ASSERT(mPendings.IsEmpty());
239 return;
242 MOZ_ASSERT(!mChildActors.Contains(aActor));
243 mChildActors.AppendElement(aActor);
245 if (!mPendings.IsEmpty()) {
246 const auto& processRemoteType = aActor->GetRemoteType();
247 nsTArray<Pending> unlaunched;
249 // Flush pending launching.
250 for (Pending& p : mPendings) {
251 if (p.mController->IsTerminated()) {
252 continue;
255 const auto& workerRemoteType = p.mData.remoteType();
257 if (MatchRemoteType(processRemoteType, workerRemoteType)) {
258 LOG(("RegisterActor - Launch Pending, workerRemoteType=%s",
259 workerRemoteType.get()));
260 LaunchInternal(p.mController, aActor, p.mData);
261 } else {
262 unlaunched.AppendElement(std::move(p));
263 continue;
267 std::swap(mPendings, unlaunched);
269 // AddRef is called when the first Pending object is added to mPendings, so
270 // the balancing Release is called when the last Pending object is removed.
271 // RemoteWorkerServiceParents will hold strong references to
272 // RemoteWorkerManager.
273 if (mPendings.IsEmpty()) {
274 Release();
277 LOG(("RegisterActor - mPendings length: %zu", mPendings.Length()));
281 void RemoteWorkerManager::UnregisterActor(RemoteWorkerServiceParent* aActor) {
282 AssertIsInMainProcess();
283 AssertIsOnBackgroundThread();
284 MOZ_ASSERT(aActor);
286 if (aActor == mParentActor) {
287 mParentActor = nullptr;
288 } else {
289 MOZ_ASSERT(mChildActors.Contains(aActor));
290 mChildActors.RemoveElement(aActor);
294 void RemoteWorkerManager::Launch(RemoteWorkerController* aController,
295 const RemoteWorkerData& aData,
296 base::ProcessId aProcessId) {
297 AssertIsInMainProcess();
298 AssertIsOnBackgroundThread();
300 RemoteWorkerServiceParent* targetActor = SelectTargetActor(aData, aProcessId);
302 // If there is not an available actor, let's store the data, and let's spawn a
303 // new process.
304 if (!targetActor) {
305 // If this is the first time we have a pending launching, we must keep alive
306 // the manager.
307 if (mPendings.IsEmpty()) {
308 AddRef();
311 Pending* pending = mPendings.AppendElement();
312 pending->mController = aController;
313 pending->mData = aData;
315 // Launching is async, so we cannot check for failures right here.
316 LaunchNewContentProcess(aData);
317 return;
321 * If a target actor for the remote worker has been selected, the actor has
322 * already been registered with the corresponding `ContentParent` and we
323 * should not increment the `mRemoteWorkerActorData`'s `mCount` again (see
324 * `SelectTargetActorForServiceWorker()` /
325 * `SelectTargetActorForSharedWorker()`).
327 LaunchInternal(aController, targetActor, aData, true);
330 void RemoteWorkerManager::LaunchInternal(
331 RemoteWorkerController* aController,
332 RemoteWorkerServiceParent* aTargetActor, const RemoteWorkerData& aData,
333 bool aRemoteWorkerAlreadyRegistered) {
334 AssertIsInMainProcess();
335 AssertIsOnBackgroundThread();
336 MOZ_ASSERT(aController);
337 MOZ_ASSERT(aTargetActor);
338 MOZ_ASSERT(aTargetActor == mParentActor ||
339 mChildActors.Contains(aTargetActor));
341 // We need to send permissions to content processes, but not if we're spawning
342 // the worker here in the parent process.
343 if (aTargetActor != mParentActor) {
344 RefPtr<ThreadsafeContentParentHandle> contentHandle =
345 BackgroundParent::GetContentParentHandle(aTargetActor->Manager());
347 // This won't cause any race conditions because the content process
348 // should wait for the permissions to be received before executing the
349 // Service Worker.
350 nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
351 __func__, [contentHandle = std::move(contentHandle),
352 principalInfo = aData.principalInfo()] {
353 AssertIsOnMainThread();
354 if (RefPtr<ContentParent> contentParent =
355 contentHandle->GetContentParent()) {
356 TransmitPermissionsAndBlobURLsForPrincipalInfo(contentParent,
357 principalInfo);
361 MOZ_ALWAYS_SUCCEEDS(SchedulerGroup::Dispatch(r.forget()));
364 RefPtr<RemoteWorkerParent> workerActor = MakeAndAddRef<RemoteWorkerParent>();
365 if (!aTargetActor->Manager()->SendPRemoteWorkerConstructor(workerActor,
366 aData)) {
367 AsyncCreationFailed(aController);
368 return;
371 workerActor->Initialize(aRemoteWorkerAlreadyRegistered);
373 // This makes the link better the 2 actors.
374 aController->SetWorkerActor(workerActor);
375 workerActor->SetController(aController);
378 void RemoteWorkerManager::AsyncCreationFailed(
379 RemoteWorkerController* aController) {
380 RefPtr<RemoteWorkerController> controller = aController;
381 nsCOMPtr<nsIRunnable> r =
382 NS_NewRunnableFunction("RemoteWorkerManager::AsyncCreationFailed",
383 [controller]() { controller->CreationFailed(); });
385 NS_DispatchToCurrentThread(r.forget());
388 template <typename Callback>
389 void RemoteWorkerManager::ForEachActor(
390 Callback&& aCallback, const nsACString& aRemoteType,
391 Maybe<base::ProcessId> aProcessId) const {
392 AssertIsOnBackgroundThread();
394 const auto length = mChildActors.Length();
396 auto end = static_cast<uint32_t>(rand()) % length;
397 if (aProcessId) {
398 // Start from the actor with the given processId instead of starting from
399 // a random index.
400 for (auto j = length - 1; j > 0; j--) {
401 if (mChildActors[j]->OtherPid() == *aProcessId) {
402 end = j;
403 break;
408 uint32_t i = end;
410 do {
411 MOZ_ASSERT(i < mChildActors.Length());
412 RemoteWorkerServiceParent* actor = mChildActors[i];
414 if (MatchRemoteType(actor->GetRemoteType(), aRemoteType)) {
415 ThreadsafeContentParentHandle* contentHandle =
416 BackgroundParent::GetContentParentHandle(actor->Manager());
418 if (!aCallback(actor, contentHandle)) {
419 break;
423 i = (i + 1) % length;
424 } while (i != end);
428 * When selecting a target actor for a given remote worker, we have to consider
429 * that:
431 * - Service Workers can spawn even when their registering page/script isn't
432 * active (e.g. push notifications), so we don't attempt to spawn the worker
433 * in its registering script's process. We search linearly and choose the
434 * search's starting position randomly.
436 * - When Fission is enabled, Shared Workers may have to be spawned into
437 * different child process from the one where it has been registered from, and
438 * that child process may be going to be marked as dead and shutdown.
440 * Spawning the workers in a random process makes the process selection criteria
441 * a little tricky, as a candidate process may imminently shutdown due to a
442 * remove worker actor unregistering
443 * (see `ContentParent::UnregisterRemoveWorkerActor`).
445 * In `ContentParent::MaybeBeginShutdown` we only dispatch a runnable
446 * to call `ContentParent::ShutDownProcess` if there are no registered remote
447 * worker actors, and we ensure that the check for the number of registered
448 * actors and the dispatching of the runnable are atomic. That happens on the
449 * main thread, so here on the background thread, while
450 * `ContentParent::mRemoteWorkerActorData` is locked, if `mCount` > 0, we can
451 * register a remote worker actor "early" and guarantee that the corresponding
452 * content process will not shutdown.
454 RemoteWorkerServiceParent* RemoteWorkerManager::SelectTargetActorInternal(
455 const RemoteWorkerData& aData, base::ProcessId aProcessId) const {
456 AssertIsOnBackgroundThread();
457 MOZ_ASSERT(!mChildActors.IsEmpty());
459 RemoteWorkerServiceParent* actor = nullptr;
461 const auto& workerRemoteType = aData.remoteType();
463 ForEachActor(
464 [&](RemoteWorkerServiceParent* aActor,
465 ThreadsafeContentParentHandle* aContentHandle) {
466 // Make sure to choose an actor related to a child process that is not
467 // going to shutdown while we are still in the process of launching the
468 // remote worker.
470 // ForEachActor will start from the child actor coming from the child
471 // process with a pid equal to aProcessId if any, otherwise it would
472 // start from a random actor in the mChildActors array, this guarantees
473 // that we will choose that actor if it does also match the remote type.
474 if (aContentHandle->MaybeRegisterRemoteWorkerActor(
475 [&](uint32_t count, bool shutdownStarted) -> bool {
476 return (count || !shutdownStarted) &&
477 (aActor->OtherPid() == aProcessId || !actor);
478 })) {
479 actor = aActor;
480 return false;
482 MOZ_ASSERT(!actor);
483 return true;
485 workerRemoteType, IsServiceWorker(aData) ? Nothing() : Some(aProcessId));
487 return actor;
490 RemoteWorkerServiceParent* RemoteWorkerManager::SelectTargetActor(
491 const RemoteWorkerData& aData, base::ProcessId aProcessId) {
492 AssertIsInMainProcess();
493 AssertIsOnBackgroundThread();
495 // System principal workers should run on the parent process.
496 if (aData.principalInfo().type() == PrincipalInfo::TSystemPrincipalInfo) {
497 MOZ_ASSERT(mParentActor);
498 return mParentActor;
501 // Extension principal workers are allowed to run on the parent process
502 // when "extension.webextensions.remote" pref is false.
503 if (aProcessId == base::GetCurrentProcId() &&
504 aData.remoteType().Equals(NOT_REMOTE_TYPE) &&
505 !StaticPrefs::extensions_webextensions_remote() &&
506 HasExtensionPrincipal(aData)) {
507 MOZ_ASSERT(mParentActor);
508 return mParentActor;
511 // If e10s is off, use the parent process.
512 if (!BrowserTabsRemoteAutostart()) {
513 MOZ_ASSERT(mParentActor);
514 return mParentActor;
517 // We shouldn't have to worry about content-principal parent-process workers.
518 MOZ_ASSERT(aProcessId != base::GetCurrentProcId());
520 if (mChildActors.IsEmpty()) {
521 return nullptr;
524 return SelectTargetActorInternal(aData, aProcessId);
527 void RemoteWorkerManager::LaunchNewContentProcess(
528 const RemoteWorkerData& aData) {
529 AssertIsInMainProcess();
530 AssertIsOnBackgroundThread();
532 nsCOMPtr<nsISerialEventTarget> bgEventTarget = GetCurrentSerialEventTarget();
534 using LaunchPromiseType = ContentParent::LaunchPromise;
535 using CallbackParamType = LaunchPromiseType::ResolveOrRejectValue;
537 // A new content process must be requested on the main thread. On success,
538 // the success callback will also run on the main thread. On failure, however,
539 // the failure callback must be run on the background thread - it uses
540 // RemoteWorkerManager, and RemoteWorkerManager isn't threadsafe, so the
541 // promise callback will just dispatch the "real" failure callback to the
542 // background thread.
543 auto processLaunchCallback = [principalInfo = aData.principalInfo(),
544 bgEventTarget = std::move(bgEventTarget),
545 self = RefPtr<RemoteWorkerManager>(this)](
546 const CallbackParamType& aValue,
547 const nsCString& remoteType) mutable {
548 if (aValue.IsResolve()) {
549 LOG(("LaunchNewContentProcess: successfully got child process"));
551 // The failure callback won't run, and we're on the main thread, so
552 // we need to properly release the thread-unsafe RemoteWorkerManager.
553 NS_ProxyRelease(__func__, bgEventTarget, self.forget());
554 } else {
555 // The "real" failure callback.
556 nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
557 __func__, [self = std::move(self), remoteType] {
558 nsTArray<Pending> uncancelled;
559 auto pendings = std::move(self->mPendings);
561 for (const auto& pending : pendings) {
562 const auto& workerRemoteType = pending.mData.remoteType();
563 if (self->MatchRemoteType(remoteType, workerRemoteType)) {
564 LOG(
565 ("LaunchNewContentProcess: Cancel pending with "
566 "workerRemoteType=%s",
567 workerRemoteType.get()));
568 pending.mController->CreationFailed();
569 } else {
570 uncancelled.AppendElement(pending);
574 std::swap(self->mPendings, uncancelled);
577 bgEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
581 LOG(("LaunchNewContentProcess: remoteType=%s", aData.remoteType().get()));
583 nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
584 __func__, [callback = std::move(processLaunchCallback),
585 workerRemoteType = aData.remoteType()]() mutable {
586 auto remoteType =
587 workerRemoteType.IsEmpty() ? DEFAULT_REMOTE_TYPE : workerRemoteType;
589 RefPtr<LaunchPromiseType> onFinished;
590 if (!AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
591 // Request a process making sure to specify aPreferUsed=true. For a
592 // given remoteType there's a pool size limit. If we pass aPreferUsed
593 // here, then if there's any process in the pool already, we will use
594 // that. If we pass false (which is the default if omitted), then
595 // this call will spawn a new process if the pool isn't at its limit
596 // yet.
598 // (Our intent is never to grow the pool size here. Our logic gets
599 // here because our current logic on PBackground is only aware of
600 // RemoteWorkerServiceParent actors that have registered themselves,
601 // which is fundamentally unaware of processes that will match in the
602 // future when they register. So we absolutely are fine with and want
603 // any existing processes.)
604 onFinished = ContentParent::GetNewOrUsedBrowserProcessAsync(
605 /* aRemoteType = */ remoteType,
606 /* aGroup */ nullptr,
607 hal::ProcessPriority::PROCESS_PRIORITY_FOREGROUND,
608 /* aPreferUsed */ true);
609 } else {
610 // We can find this event still in flight after having been asked to
611 // shutdown. Let's fake a failure to ensure our callback is called
612 // such that we clean up everything properly.
613 onFinished = LaunchPromiseType::CreateAndReject(
614 NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
616 onFinished->Then(GetCurrentSerialEventTarget(), __func__,
617 [callback = std::move(callback),
618 remoteType](const CallbackParamType& aValue) mutable {
619 callback(aValue, remoteType);
623 SchedulerGroup::Dispatch(r.forget());
626 } // namespace dom
627 } // namespace mozilla