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