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"
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"
22 #include "nsImportModule.h"
23 #include "nsIXULRuntime.h"
25 #include "nsThreadUtils.h"
26 #include "RemoteWorkerServiceParent.h"
28 mozilla::LazyLogModule
gRemoteWorkerManagerLog("RemoteWorkerManager");
34 MOZ_LOG(gRemoteWorkerManagerLog, mozilla::LogLevel::Verbose, fmt)
44 // Raw pointer because this object is kept alive by RemoteWorkerServiceParent
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())) {
64 nsCOMPtr
<nsIPrincipal
> principal
= principalOrErr
.unwrap();
66 aContentParent
->TransmitBlobURLsForPrincipal(principal
);
69 aContentParent
->TransmitPermissionsForPrincipal(principal
));
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
);
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
);
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
;
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
);
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()) {
170 const auto& principalInfo
= aData
.principalInfo();
172 auto* contentChild
= ContentChild::GetSingleton();
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())) {
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
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"));
201 return MatchRemoteType(remoteType
.unwrap(), contentChild
->GetRemoteType());
205 already_AddRefed
<RemoteWorkerManager
> RemoteWorkerManager::GetOrCreate() {
206 AssertIsInMainProcess();
207 AssertIsOnBackgroundThread();
209 if (!sRemoteWorkerManager
) {
210 sRemoteWorkerManager
= new RemoteWorkerManager();
213 RefPtr
<RemoteWorkerManager
> rwm
= sRemoteWorkerManager
;
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();
235 if (!BackgroundParent::IsOtherProcessActor(aActor
->Manager())) {
236 MOZ_ASSERT(!mParentActor
);
237 mParentActor
= aActor
;
238 MOZ_ASSERT(mPendings
.IsEmpty());
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()) {
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
);
262 unlaunched
.AppendElement(std::move(p
));
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()) {
277 LOG(("RegisterActor - mPendings length: %zu", mPendings
.Length()));
281 void RemoteWorkerManager::UnregisterActor(RemoteWorkerServiceParent
* aActor
) {
282 AssertIsInMainProcess();
283 AssertIsOnBackgroundThread();
286 if (aActor
== mParentActor
) {
287 mParentActor
= nullptr;
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
305 // If this is the first time we have a pending launching, we must keep alive
307 if (mPendings
.IsEmpty()) {
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
);
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
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
,
361 MOZ_ALWAYS_SUCCEEDS(SchedulerGroup::Dispatch(r
.forget()));
364 RefPtr
<RemoteWorkerParent
> workerActor
= MakeAndAddRef
<RemoteWorkerParent
>();
365 if (!aTargetActor
->Manager()->SendPRemoteWorkerConstructor(workerActor
,
367 AsyncCreationFailed(aController
);
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
;
398 // Start from the actor with the given processId instead of starting from
400 for (auto j
= length
- 1; j
> 0; j
--) {
401 if (mChildActors
[j
]->OtherPid() == *aProcessId
) {
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
)) {
423 i
= (i
+ 1) % length
;
428 * When selecting a target actor for a given remote worker, we have to consider
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();
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
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
);
485 workerRemoteType
, IsServiceWorker(aData
) ? Nothing() : Some(aProcessId
));
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
);
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
);
511 // If e10s is off, use the parent process.
512 if (!BrowserTabsRemoteAutostart()) {
513 MOZ_ASSERT(mParentActor
);
517 // We shouldn't have to worry about content-principal parent-process workers.
518 MOZ_ASSERT(aProcessId
!= base::GetCurrentProcId());
520 if (mChildActors
.IsEmpty()) {
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());
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
)) {
565 ("LaunchNewContentProcess: Cancel pending with "
566 "workerRemoteType=%s",
567 workerRemoteType
.get()));
568 pending
.mController
->CreationFailed();
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 {
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
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);
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());
627 } // namespace mozilla