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/. */
6
7 #include "RemoteWorkerManager.h"
8
9 #include <utility>
10
11 #include "mozilla/SchedulerGroup.h"
12 #include "mozilla/ScopeExit.h"
13 #include "mozilla/dom/ContentChild.h" // ContentChild::GetSingleton
14 #include "mozilla/dom/RemoteWorkerController.h"
15 #include "mozilla/dom/RemoteWorkerParent.h"
16 #include "mozilla/ipc/BackgroundParent.h"
17 #include "mozilla/ipc/BackgroundUtils.h"
18 #include "mozilla/ipc/PBackgroundParent.h"
19 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
20 # include "mozilla/dom/DOMException.h"
21 # include "mozilla/CycleCollectedJSContext.h"
22 # include "mozilla/Sprintf.h" // SprintfLiteral
23 # include "nsIXPConnect.h" // nsIXPConnectWrappedJS
24 #endif
25 #include "mozilla/StaticPrefs_extensions.h"
26 #include "nsCOMPtr.h"
27 #include "nsIE10SUtils.h"
28 #include "nsImportModule.h"
29 #include "nsIXULRuntime.h"
30 #include "nsTArray.h"
31 #include "nsThreadUtils.h"
32 #include "RemoteWorkerServiceParent.h"
33
34 mozilla::LazyLogModule gRemoteWorkerManagerLog("RemoteWorkerManager");
35
36 #define LOG(fmt) \
37 MOZ_LOG(gRemoteWorkerManagerLog, mozilla::LogLevel::Verbose, fmt)
38
39 namespace mozilla {
40
41 using namespace ipc;
42
43 namespace dom {
44
45 namespace {
46
47 // Raw pointer because this object is kept alive by RemoteWorkerServiceParent
48 // actors.
49 RemoteWorkerManager* sRemoteWorkerManager;
50
IsServiceWorker(const RemoteWorkerData & aData)51 bool IsServiceWorker(const RemoteWorkerData& aData) {
52 return aData.serviceWorkerData().type() ==
53 OptionalServiceWorkerData::TServiceWorkerData;
54 }
55
TransmitPermissionsAndBlobURLsForPrincipalInfo(ContentParent * aContentParent,const PrincipalInfo & aPrincipalInfo)56 void TransmitPermissionsAndBlobURLsForPrincipalInfo(
57 ContentParent* aContentParent, const PrincipalInfo& aPrincipalInfo) {
58 AssertIsOnMainThread();
59 MOZ_ASSERT(aContentParent);
60
61 auto principalOrErr = PrincipalInfoToPrincipal(aPrincipalInfo);
62
63 if (NS_WARN_IF(principalOrErr.isErr())) {
64 return;
65 }
66
67 nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
68
69 aContentParent->TransmitBlobURLsForPrincipal(principal);
70
71 MOZ_ALWAYS_SUCCEEDS(
72 aContentParent->TransmitPermissionsForPrincipal(principal));
73 }
74
75 } // namespace
76
77 // static
MatchRemoteType(const nsACString & processRemoteType,const nsACString & workerRemoteType)78 bool RemoteWorkerManager::MatchRemoteType(const nsACString& processRemoteType,
79 const nsACString& workerRemoteType) {
80 LOG(("MatchRemoteType [processRemoteType=%s, workerRemoteType=%s]",
81 PromiseFlatCString(processRemoteType).get(),
82 PromiseFlatCString(workerRemoteType).get()));
83
84 // Respecting COOP and COEP requires processing headers in the parent
85 // process in order to choose an appropriate content process, but the
86 // workers' ScriptLoader processes headers in content processes. An
87 // intermediary step that provides security guarantees is to simply never
88 // allow SharedWorkers and ServiceWorkers to exist in a COOP+COEP process.
89 // The ultimate goal is to allow these worker types to be put in such
90 // processes based on their script response headers.
91 // https://bugzilla.mozilla.org/show_bug.cgi?id=1595206
92 //
93 // RemoteWorkerManager::GetRemoteType should not select this remoteType
94 // and so workerRemoteType is not expected to be set to a coop+coep
95 // remoteType and here we can just assert that it is not happening.
96 MOZ_ASSERT(!IsWebCoopCoepRemoteType(workerRemoteType));
97
98 // For similar reasons to the ones related to COOP+COEP processes,
99 // we don't expect workerRemoteType to be set to a large allocation one.
100 MOZ_ASSERT(workerRemoteType != LARGE_ALLOCATION_REMOTE_TYPE);
101
102 return processRemoteType.Equals(workerRemoteType);
103 }
104
105 // static
GetRemoteType(const nsCOMPtr<nsIPrincipal> & aPrincipal,WorkerKind aWorkerKind)106 Result<nsCString, nsresult> RemoteWorkerManager::GetRemoteType(
107 const nsCOMPtr<nsIPrincipal>& aPrincipal, WorkerKind aWorkerKind) {
108 AssertIsOnMainThread();
109
110 MOZ_ASSERT_IF(aWorkerKind == WorkerKind::WorkerKindService,
111 aPrincipal->GetIsContentPrincipal());
112
113 nsCOMPtr<nsIE10SUtils> e10sUtils =
114 do_ImportModule("resource://gre/modules/E10SUtils.jsm", "E10SUtils");
115 if (NS_WARN_IF(!e10sUtils)) {
116 LOG(("GetRemoteType Abort: could not import E10SUtils"));
117 return Err(NS_ERROR_DOM_ABORT_ERR);
118 }
119
120 nsCString preferredRemoteType = DEFAULT_REMOTE_TYPE;
121 if (aWorkerKind == WorkerKind::WorkerKindShared) {
122 if (auto* contentChild = ContentChild::GetSingleton()) {
123 // For a shared worker set the preferred remote type to the content
124 // child process remote type.
125 preferredRemoteType = contentChild->GetRemoteType();
126 } else if (aPrincipal->IsSystemPrincipal()) {
127 preferredRemoteType = NOT_REMOTE_TYPE;
128 }
129 }
130
131 nsIE10SUtils::RemoteWorkerType workerType;
132
133 switch (aWorkerKind) {
134 case WorkerKind::WorkerKindService:
135 workerType = nsIE10SUtils::REMOTE_WORKER_TYPE_SERVICE;
136 break;
137 case WorkerKind::WorkerKindShared:
138 workerType = nsIE10SUtils::REMOTE_WORKER_TYPE_SHARED;
139 break;
140 default:
141 // This method isn't expected to be called for worker types that
142 // aren't remote workers (currently Service and Shared workers).
143 LOG(("GetRemoteType Error on unexpected worker type"));
144 MOZ_DIAGNOSTIC_ASSERT(false, "Unexpected worker type");
145 return Err(NS_ERROR_DOM_ABORT_ERR);
146 }
147
148 // Here we do not have access to the window and so we can't use its
149 // useRemoteTabs and useRemoteSubframes flags (for the service
150 // worker there may not even be a window associated to the worker
151 // yet), and so we have to use the prefs instead.
152 bool isMultiprocess = BrowserTabsRemoteAutostart();
153 bool isFission = FissionAutostart();
154
155 nsCString remoteType = NOT_REMOTE_TYPE;
156
157 nsresult rv = e10sUtils->GetRemoteTypeForWorkerPrincipal(
158 aPrincipal, workerType, isMultiprocess, isFission, preferredRemoteType,
159 remoteType);
160 if (NS_WARN_IF(NS_FAILED(rv))) {
161 LOG(
162 ("GetRemoteType Abort: E10SUtils.getRemoteTypeForWorkerPrincipal "
163 "exception"));
164 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
165 nsCString principalTypeOrScheme;
166 if (aPrincipal->IsSystemPrincipal()) {
167 principalTypeOrScheme = "system"_ns;
168 } else if (aPrincipal->GetIsExpandedPrincipal()) {
169 principalTypeOrScheme = "expanded"_ns;
170 } else if (aPrincipal->GetIsNullPrincipal()) {
171 principalTypeOrScheme = "null"_ns;
172 } else {
173 nsCOMPtr<nsIURI> uri = aPrincipal->GetURI();
174 nsresult rv2 = uri->GetScheme(principalTypeOrScheme);
175 if (NS_FAILED(rv2)) {
176 principalTypeOrScheme = "content"_ns;
177 }
178 }
179
180 nsCString processRemoteType = "parent"_ns;
181 if (auto* contentChild = ContentChild::GetSingleton()) {
182 // RemoteTypePrefix make sure that we are not going to include
183 // the full origin that may be part of the current remote type.
184 processRemoteType = RemoteTypePrefix(contentChild->GetRemoteType());
185 }
186
187 // Convert the error code into an error name.
188 nsAutoCString errorName;
189 GetErrorName(rv, errorName);
190
191 // Try to retrieve the line number from the exception.
192 nsAutoCString errorFilename("(unknown)"_ns);
193 uint32_t jsmErrorLineNumber = 0;
194
195 if (auto* context = CycleCollectedJSContext::Get()) {
196 if (RefPtr<Exception> exn = context->GetPendingException()) {
197 nsAutoString filename(u"(unknown)"_ns);
198
199 if (rv == NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS) {
200 // When the failure is a Javascript Error, the line number retrieved
201 // from the Exception instance isn't going to be the E10SUtils.jsm
202 // line that originated the failure, and so we fallback to retrieve it
203 // from the nsIScriptError.
204 nsCOMPtr<nsIScriptError> scriptError =
205 do_QueryInterface(exn->GetData());
206 if (scriptError) {
207 scriptError->GetLineNumber(&jsmErrorLineNumber);
208 scriptError->GetSourceName(filename);
209 }
210 } else {
211 nsCOMPtr<nsIXPConnectWrappedJS> wrapped =
212 do_QueryInterface(e10sUtils);
213 dom::AutoJSAPI jsapi;
214 if (jsapi.Init(wrapped->GetJSObjectGlobal())) {
215 auto* cx = jsapi.cx();
216 jsmErrorLineNumber = exn->LineNumber(cx);
217 exn->GetFilename(cx, filename);
218 }
219 }
220
221 errorFilename = NS_ConvertUTF16toUTF8(filename);
222 }
223 }
224
225 char buf[1024];
226 SprintfLiteral(
227 buf,
228 "workerType=%s, principal=%s, preferredRemoteType=%s, "
229 "processRemoteType=%s, errorName=%s, errorLocation=%s:%d",
230 aWorkerKind == WorkerKind::WorkerKindService ? "service" : "shared",
231 principalTypeOrScheme.get(),
232 PromiseFlatCString(RemoteTypePrefix(preferredRemoteType)).get(),
233 processRemoteType.get(), errorName.get(), errorFilename.get(),
234 jsmErrorLineNumber);
235 MOZ_CRASH_UNSAFE_PRINTF(
236 "E10SUtils.getRemoteTypeForWorkerPrincipal did throw: %s", buf);
237 #endif
238 return Err(NS_ERROR_DOM_ABORT_ERR);
239 }
240
241 if (MOZ_LOG_TEST(gRemoteWorkerManagerLog, LogLevel::Verbose)) {
242 nsCString principalOrigin;
243 aPrincipal->GetOrigin(principalOrigin);
244
245 LOG(
246 ("GetRemoteType workerType=%s, principal=%s, "
247 "preferredRemoteType=%s, selectedRemoteType=%s",
248 aWorkerKind == WorkerKind::WorkerKindService ? "service" : "shared",
249 principalOrigin.get(), preferredRemoteType.get(), remoteType.get()));
250 }
251
252 return remoteType;
253 }
254
255 // static
HasExtensionPrincipal(const RemoteWorkerData & aData)256 bool RemoteWorkerManager::HasExtensionPrincipal(const RemoteWorkerData& aData) {
257 auto principalInfo = aData.principalInfo();
258 return principalInfo.type() == PrincipalInfo::TContentPrincipalInfo &&
259 // This helper method is also called from the background thread and so
260 // we can't check if the principal does have an addonPolicy object
261 // associated and we have to resort to check the url scheme instead.
262 StringBeginsWith(principalInfo.get_ContentPrincipalInfo().spec(),
263 "moz-extension://"_ns);
264 }
265
266 // static
IsRemoteTypeAllowed(const RemoteWorkerData & aData)267 bool RemoteWorkerManager::IsRemoteTypeAllowed(const RemoteWorkerData& aData) {
268 AssertIsOnMainThread();
269
270 // If Gecko is running in single process mode, there is no child process
271 // to select and we have to just consider it valid (if it should haven't
272 // been launched it should have been already prevented before reaching
273 // a RemoteWorkerChild instance).
274 if (!BrowserTabsRemoteAutostart()) {
275 return true;
276 }
277
278 const auto& principalInfo = aData.principalInfo();
279
280 auto* contentChild = ContentChild::GetSingleton();
281 if (!contentChild) {
282 // If e10s isn't disabled, only workers related to the system principal
283 // should be allowed to run in the parent process, and extension principals
284 // if extensions.webextensions.remote is false.
285 return principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo ||
286 (!StaticPrefs::extensions_webextensions_remote() &&
287 aData.remoteType().Equals(NOT_REMOTE_TYPE) &&
288 HasExtensionPrincipal(aData));
289 }
290
291 auto principalOrErr = PrincipalInfoToPrincipal(principalInfo);
292 if (NS_WARN_IF(principalOrErr.isErr())) {
293 return false;
294 }
295 nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
296
297 // Recompute the remoteType based on the principal, to double-check that it
298 // has not been tempered to select a different child process than the one
299 // expected.
300 bool isServiceWorker = aData.serviceWorkerData().type() ==
301 OptionalServiceWorkerData::TServiceWorkerData;
302 auto remoteType = GetRemoteType(
303 principal, isServiceWorker ? WorkerKindService : WorkerKindShared);
304 if (NS_WARN_IF(remoteType.isErr())) {
305 LOG(("IsRemoteTypeAllowed: Error to retrieve remote type"));
306 return false;
307 }
308
309 return MatchRemoteType(remoteType.unwrap(), contentChild->GetRemoteType());
310 }
311
312 /* static */
GetOrCreate()313 already_AddRefed<RemoteWorkerManager> RemoteWorkerManager::GetOrCreate() {
314 AssertIsInMainProcess();
315 AssertIsOnBackgroundThread();
316
317 if (!sRemoteWorkerManager) {
318 sRemoteWorkerManager = new RemoteWorkerManager();
319 }
320
321 RefPtr<RemoteWorkerManager> rwm = sRemoteWorkerManager;
322 return rwm.forget();
323 }
324
RemoteWorkerManager()325 RemoteWorkerManager::RemoteWorkerManager() : mParentActor(nullptr) {
326 AssertIsInMainProcess();
327 AssertIsOnBackgroundThread();
328 MOZ_ASSERT(!sRemoteWorkerManager);
329 }
330
~RemoteWorkerManager()331 RemoteWorkerManager::~RemoteWorkerManager() {
332 AssertIsInMainProcess();
333 AssertIsOnBackgroundThread();
334 MOZ_ASSERT(sRemoteWorkerManager == this);
335 sRemoteWorkerManager = nullptr;
336 }
337
RegisterActor(RemoteWorkerServiceParent * aActor)338 void RemoteWorkerManager::RegisterActor(RemoteWorkerServiceParent* aActor) {
339 AssertIsInMainProcess();
340 AssertIsOnBackgroundThread();
341 MOZ_ASSERT(aActor);
342
343 if (!BackgroundParent::IsOtherProcessActor(aActor->Manager())) {
344 MOZ_ASSERT(!mParentActor);
345 mParentActor = aActor;
346 MOZ_ASSERT(mPendings.IsEmpty());
347 return;
348 }
349
350 MOZ_ASSERT(!mChildActors.Contains(aActor));
351 mChildActors.AppendElement(aActor);
352
353 if (!mPendings.IsEmpty()) {
354 const auto& processRemoteType = aActor->GetRemoteType();
355 nsTArray<Pending> unlaunched;
356
357 // Flush pending launching.
358 for (Pending& p : mPendings) {
359 if (p.mController->IsTerminated()) {
360 continue;
361 }
362
363 const auto& workerRemoteType = p.mData.remoteType();
364
365 if (MatchRemoteType(processRemoteType, workerRemoteType)) {
366 LOG(("RegisterActor - Launch Pending, workerRemoteType=%s",
367 workerRemoteType.get()));
368 LaunchInternal(p.mController, aActor, p.mData);
369 } else {
370 unlaunched.AppendElement(std::move(p));
371 continue;
372 }
373 }
374
375 std::swap(mPendings, unlaunched);
376
377 // AddRef is called when the first Pending object is added to mPendings, so
378 // the balancing Release is called when the last Pending object is removed.
379 // RemoteWorkerServiceParents will hold strong references to
380 // RemoteWorkerManager.
381 if (mPendings.IsEmpty()) {
382 Release();
383 }
384
385 LOG(("RegisterActor - mPendings length: %zu", mPendings.Length()));
386 }
387 }
388
UnregisterActor(RemoteWorkerServiceParent * aActor)389 void RemoteWorkerManager::UnregisterActor(RemoteWorkerServiceParent* aActor) {
390 AssertIsInMainProcess();
391 AssertIsOnBackgroundThread();
392 MOZ_ASSERT(aActor);
393
394 if (aActor == mParentActor) {
395 mParentActor = nullptr;
396 } else {
397 MOZ_ASSERT(mChildActors.Contains(aActor));
398 mChildActors.RemoveElement(aActor);
399 }
400 }
401
Launch(RemoteWorkerController * aController,const RemoteWorkerData & aData,base::ProcessId aProcessId)402 void RemoteWorkerManager::Launch(RemoteWorkerController* aController,
403 const RemoteWorkerData& aData,
404 base::ProcessId aProcessId) {
405 AssertIsInMainProcess();
406 AssertIsOnBackgroundThread();
407
408 RemoteWorkerServiceParent* targetActor = SelectTargetActor(aData, aProcessId);
409
410 // If there is not an available actor, let's store the data, and let's spawn a
411 // new process.
412 if (!targetActor) {
413 // If this is the first time we have a pending launching, we must keep alive
414 // the manager.
415 if (mPendings.IsEmpty()) {
416 AddRef();
417 }
418
419 Pending* pending = mPendings.AppendElement();
420 pending->mController = aController;
421 pending->mData = aData;
422
423 LaunchNewContentProcess(aData);
424 return;
425 }
426
427 /**
428 * If a target actor for the remote worker has been selected, the actor has
429 * already been registered with the corresponding `ContentParent` and we
430 * should not increment the `mRemoteWorkerActorData`'s `mCount` again (see
431 * `SelectTargetActorForServiceWorker()` /
432 * `SelectTargetActorForSharedWorker()`).
433 */
434 LaunchInternal(aController, targetActor, aData, true);
435 }
436
LaunchInternal(RemoteWorkerController * aController,RemoteWorkerServiceParent * aTargetActor,const RemoteWorkerData & aData,bool aRemoteWorkerAlreadyRegistered)437 void RemoteWorkerManager::LaunchInternal(
438 RemoteWorkerController* aController,
439 RemoteWorkerServiceParent* aTargetActor, const RemoteWorkerData& aData,
440 bool aRemoteWorkerAlreadyRegistered) {
441 AssertIsInMainProcess();
442 AssertIsOnBackgroundThread();
443 MOZ_ASSERT(aController);
444 MOZ_ASSERT(aTargetActor);
445 MOZ_ASSERT(aTargetActor == mParentActor ||
446 mChildActors.Contains(aTargetActor));
447
448 // We need to send permissions to content processes, but not if we're spawning
449 // the worker here in the parent process.
450 if (aTargetActor != mParentActor) {
451 RefPtr<ContentParent> contentParent =
452 BackgroundParent::GetContentParent(aTargetActor->Manager());
453
454 // This won't cause any race conditions because the content process
455 // should wait for the permissions to be received before executing the
456 // Service Worker.
457 nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
458 __func__, [contentParent = std::move(contentParent),
459 principalInfo = aData.principalInfo()] {
460 TransmitPermissionsAndBlobURLsForPrincipalInfo(contentParent,
461 principalInfo);
462 });
463
464 MOZ_ALWAYS_SUCCEEDS(
465 SchedulerGroup::Dispatch(TaskCategory::Other, r.forget()));
466 }
467
468 RemoteWorkerParent* workerActor = static_cast<RemoteWorkerParent*>(
469 aTargetActor->Manager()->SendPRemoteWorkerConstructor(aData));
470 if (NS_WARN_IF(!workerActor)) {
471 AsyncCreationFailed(aController);
472 return;
473 }
474
475 workerActor->Initialize(aRemoteWorkerAlreadyRegistered);
476
477 // This makes the link better the 2 actors.
478 aController->SetWorkerActor(workerActor);
479 workerActor->SetController(aController);
480 }
481
AsyncCreationFailed(RemoteWorkerController * aController)482 void RemoteWorkerManager::AsyncCreationFailed(
483 RemoteWorkerController* aController) {
484 RefPtr<RemoteWorkerController> controller = aController;
485 nsCOMPtr<nsIRunnable> r =
486 NS_NewRunnableFunction("RemoteWorkerManager::AsyncCreationFailed",
487 [controller]() { controller->CreationFailed(); });
488
489 NS_DispatchToCurrentThread(r.forget());
490 }
491
492 template <typename Callback>
ForEachActor(Callback && aCallback,const nsACString & aRemoteType,Maybe<base::ProcessId> aProcessId) const493 void RemoteWorkerManager::ForEachActor(
494 Callback&& aCallback, const nsACString& aRemoteType,
495 Maybe<base::ProcessId> aProcessId) const {
496 AssertIsOnBackgroundThread();
497
498 const auto length = mChildActors.Length();
499
500 auto end = static_cast<uint32_t>(rand()) % length;
501 if (aProcessId) {
502 // Start from the actor with the given processId instead of starting from
503 // a random index.
504 for (auto j = length - 1; j > 0; j--) {
505 if (mChildActors[j]->OtherPid() == *aProcessId) {
506 end = j;
507 break;
508 }
509 }
510 }
511
512 uint32_t i = end;
513
514 nsTArray<RefPtr<ContentParent>> proxyReleaseArray;
515
516 do {
517 MOZ_ASSERT(i < mChildActors.Length());
518 RemoteWorkerServiceParent* actor = mChildActors[i];
519
520 if (MatchRemoteType(actor->GetRemoteType(), aRemoteType)) {
521 RefPtr<ContentParent> contentParent =
522 BackgroundParent::GetContentParent(actor->Manager());
523
524 auto scopeExit = MakeScopeExit(
525 [&]() { proxyReleaseArray.AppendElement(std::move(contentParent)); });
526
527 if (!aCallback(actor, std::move(contentParent))) {
528 break;
529 }
530 }
531
532 i = (i + 1) % length;
533 } while (i != end);
534
535 nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
536 __func__, [proxyReleaseArray = std::move(proxyReleaseArray)] {});
537
538 MOZ_ALWAYS_SUCCEEDS(
539 SchedulerGroup::Dispatch(TaskCategory::Other, r.forget()));
540 }
541
542 /**
543 * When selecting a target actor for a given remote worker, we have to consider
544 * that:
545 *
546 * - Service Workers can spawn even when their registering page/script isn't
547 * active (e.g. push notifications), so we don't attempt to spawn the worker
548 * in its registering script's process. We search linearly and choose the
549 * search's starting position randomly.
550 *
551 * - When Fission is enabled, Shared Workers may have to be spawned into
552 * different child process from the one where it has been registered from, and
553 * that child process may be going to be marked as dead and shutdown.
554 *
555 * Spawning the workers in a random process makes the process selection criteria
556 * a little tricky, as a candidate process may imminently shutdown due to a
557 * remove worker actor unregistering
558 * (see `ContentParent::UnregisterRemoveWorkerActor`).
559 *
560 * In `ContentParent::MaybeAsyncSendShutDownMessage` we only dispatch a runnable
561 * to call `ContentParent::ShutDownProcess` if there are no registered remote
562 * worker actors, and we ensure that the check for the number of registered
563 * actors and the dispatching of the runnable are atomic. That happens on the
564 * main thread, so here on the background thread, while
565 * `ContentParent::mRemoteWorkerActorData` is locked, if `mCount` > 0, we can
566 * register a remote worker actor "early" and guarantee that the corresponding
567 * content process will not shutdown.
568 */
SelectTargetActorInternal(const RemoteWorkerData & aData,base::ProcessId aProcessId) const569 RemoteWorkerServiceParent* RemoteWorkerManager::SelectTargetActorInternal(
570 const RemoteWorkerData& aData, base::ProcessId aProcessId) const {
571 AssertIsOnBackgroundThread();
572 MOZ_ASSERT(!mChildActors.IsEmpty());
573
574 RemoteWorkerServiceParent* actor = nullptr;
575
576 const auto& workerRemoteType = aData.remoteType();
577
578 ForEachActor(
579 [&](RemoteWorkerServiceParent* aActor,
580 RefPtr<ContentParent>&& aContentParent) {
581 // Make sure to choose an actor related to a child process that is not
582 // going to shutdown while we are still in the process of launching the
583 // remote worker.
584 //
585 // ForEachActor will start from the child actor coming from the child
586 // process with a pid equal to aProcessId if any, otherwise it would
587 // start from a random actor in the mChildActors array, this guarantees
588 // that we will choose that actor if it does also match the remote type.
589 auto lock = aContentParent->mRemoteWorkerActorData.Lock();
590
591 if ((lock->mCount || !lock->mShutdownStarted) &&
592 (aActor->OtherPid() == aProcessId || !actor)) {
593 ++lock->mCount;
594
595 actor = aActor;
596 return false;
597 }
598
599 MOZ_ASSERT(!actor);
600 return true;
601 },
602 workerRemoteType, IsServiceWorker(aData) ? Nothing() : Some(aProcessId));
603
604 return actor;
605 }
606
SelectTargetActor(const RemoteWorkerData & aData,base::ProcessId aProcessId)607 RemoteWorkerServiceParent* RemoteWorkerManager::SelectTargetActor(
608 const RemoteWorkerData& aData, base::ProcessId aProcessId) {
609 AssertIsInMainProcess();
610 AssertIsOnBackgroundThread();
611
612 // System principal workers should run on the parent process.
613 if (aData.principalInfo().type() == PrincipalInfo::TSystemPrincipalInfo) {
614 MOZ_ASSERT(mParentActor);
615 return mParentActor;
616 }
617
618 // Extension principal workers are allowed to run on the parent process
619 // when "extension.webextensions.remote" pref is false.
620 if (aProcessId == base::GetCurrentProcId() &&
621 aData.remoteType().Equals(NOT_REMOTE_TYPE) &&
622 !StaticPrefs::extensions_webextensions_remote() &&
623 HasExtensionPrincipal(aData)) {
624 MOZ_ASSERT(mParentActor);
625 return mParentActor;
626 }
627
628 // If e10s is off, use the parent process.
629 if (!BrowserTabsRemoteAutostart()) {
630 MOZ_ASSERT(mParentActor);
631 return mParentActor;
632 }
633
634 // We shouldn't have to worry about content-principal parent-process workers.
635 MOZ_ASSERT(aProcessId != base::GetCurrentProcId());
636
637 if (mChildActors.IsEmpty()) {
638 return nullptr;
639 }
640
641 return SelectTargetActorInternal(aData, aProcessId);
642 }
643
LaunchNewContentProcess(const RemoteWorkerData & aData)644 void RemoteWorkerManager::LaunchNewContentProcess(
645 const RemoteWorkerData& aData) {
646 AssertIsInMainProcess();
647 AssertIsOnBackgroundThread();
648
649 nsCOMPtr<nsISerialEventTarget> bgEventTarget = GetCurrentSerialEventTarget();
650
651 using CallbackParamType = ContentParent::LaunchPromise::ResolveOrRejectValue;
652
653 // A new content process must be requested on the main thread. On success,
654 // the success callback will also run on the main thread. On failure, however,
655 // the failure callback must be run on the background thread - it uses
656 // RemoteWorkerManager, and RemoteWorkerManager isn't threadsafe, so the
657 // promise callback will just dispatch the "real" failure callback to the
658 // background thread.
659 auto processLaunchCallback = [principalInfo = aData.principalInfo(),
660 bgEventTarget = std::move(bgEventTarget),
661 self = RefPtr<RemoteWorkerManager>(this)](
662 const CallbackParamType& aValue,
663 const nsCString& remoteType) mutable {
664 if (aValue.IsResolve()) {
665 LOG(("LaunchNewContentProcess: successfully got child process"));
666
667 // The failure callback won't run, and we're on the main thread, so
668 // we need to properly release the thread-unsafe RemoteWorkerManager.
669 NS_ProxyRelease(__func__, bgEventTarget, self.forget());
670 } else {
671 // The "real" failure callback.
672 nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
673 __func__, [self = std::move(self), remoteType] {
674 nsTArray<Pending> uncancelled;
675 auto pendings = std::move(self->mPendings);
676
677 for (const auto& pending : pendings) {
678 const auto& workerRemoteType = pending.mData.remoteType();
679 if (self->MatchRemoteType(remoteType, workerRemoteType)) {
680 LOG(
681 ("LaunchNewContentProcess: Cancel pending with "
682 "workerRemoteType=%s",
683 workerRemoteType.get()));
684 pending.mController->CreationFailed();
685 } else {
686 uncancelled.AppendElement(pending);
687 }
688 }
689
690 std::swap(self->mPendings, uncancelled);
691 });
692
693 bgEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
694 }
695 };
696
697 LOG(("LaunchNewContentProcess: remoteType=%s", aData.remoteType().get()));
698
699 nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
700 __func__, [callback = std::move(processLaunchCallback),
701 workerRemoteType = aData.remoteType()]() mutable {
702 auto remoteType =
703 workerRemoteType.IsEmpty() ? DEFAULT_REMOTE_TYPE : workerRemoteType;
704
705 // Request a process making sure to specify aPreferUsed=true. For a
706 // given remoteType there's a pool size limit. If we pass aPreferUsed
707 // here, then if there's any process in the pool already, we will use
708 // that. If we pass false (which is the default if omitted), then this
709 // call will spawn a new process if the pool isn't at its limit yet.
710 //
711 // (Our intent is never to grow the pool size here. Our logic gets here
712 // because our current logic on PBackground is only aware of
713 // RemoteWorkerServiceParent actors that have registered themselves,
714 // which is fundamentally unaware of processes that will match in the
715 // future when they register. So we absolutely are fine with and want
716 // any existing processes.)
717 ContentParent::GetNewOrUsedBrowserProcessAsync(
718 /* aRemoteType = */ remoteType,
719 /* aGroup */ nullptr,
720 hal::ProcessPriority::PROCESS_PRIORITY_FOREGROUND,
721 /* aPreferUsed */ true)
722 ->Then(GetCurrentSerialEventTarget(), __func__,
723 [callback = std::move(callback),
724 remoteType](const CallbackParamType& aValue) mutable {
725 callback(aValue, remoteType);
726 });
727 });
728
729 SchedulerGroup::Dispatch(TaskCategory::Other, r.forget());
730 }
731
732 } // namespace dom
733 } // namespace mozilla
734