RemoteWorkerManager.cpp (18840B)
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 "RemoteWorkerServiceParent.h" 12 #include "mozilla/AppShutdown.h" 13 #include "mozilla/SchedulerGroup.h" 14 #include "mozilla/StaticPrefs_extensions.h" 15 #include "mozilla/dom/ContentChild.h" // ContentChild::GetSingleton 16 #include "mozilla/dom/PRemoteWorkerNonLifeCycleOpControllerChild.h" 17 #include "mozilla/dom/PRemoteWorkerNonLifeCycleOpControllerParent.h" 18 #include "mozilla/dom/ProcessIsolation.h" 19 #include "mozilla/dom/RemoteWorkerController.h" 20 #include "mozilla/dom/RemoteWorkerNonLifeCycleOpControllerParent.h" 21 #include "mozilla/dom/RemoteWorkerParent.h" 22 #include "mozilla/ipc/BackgroundParent.h" 23 #include "mozilla/ipc/BackgroundUtils.h" 24 #include "mozilla/ipc/PBackgroundParent.h" 25 #include "mozilla/net/CookieServiceParent.h" 26 #include "mozilla/net/NeckoParent.h" 27 #include "nsCOMPtr.h" 28 #include "nsIXULRuntime.h" 29 #include "nsImportModule.h" 30 #include "nsTArray.h" 31 #include "nsThreadUtils.h" 32 33 mozilla::LazyLogModule gRemoteWorkerManagerLog("RemoteWorkerManager"); 34 35 #ifdef LOG 36 # undef LOG 37 #endif 38 #define LOG(fmt) \ 39 MOZ_LOG(gRemoteWorkerManagerLog, mozilla::LogLevel::Verbose, fmt) 40 41 namespace mozilla { 42 43 using namespace ipc; 44 using namespace net; 45 46 namespace dom { 47 48 namespace { 49 50 // Raw pointer because this object is kept alive by RemoteWorkerServiceParent 51 // actors. 52 RemoteWorkerManager* sRemoteWorkerManager; 53 54 bool IsServiceWorker(const RemoteWorkerData& aData) { 55 return aData.serviceWorkerData().type() == 56 OptionalServiceWorkerData::TServiceWorkerData; 57 } 58 59 void TransmitPermissionsAndCookiesAndBlobURLsForPrincipalInfo( 60 ContentParent* aContentParent, const PrincipalInfo& aPrincipalInfo) { 61 AssertIsOnMainThread(); 62 MOZ_ASSERT(aContentParent); 63 64 auto principalOrErr = PrincipalInfoToPrincipal(aPrincipalInfo); 65 66 if (NS_WARN_IF(principalOrErr.isErr())) { 67 return; 68 } 69 70 nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap(); 71 72 aContentParent->TransmitBlobURLsForPrincipal(principal); 73 74 MOZ_ALWAYS_SUCCEEDS( 75 aContentParent->TransmitPermissionsForPrincipal(principal)); 76 77 CookieServiceParent* cs = nullptr; 78 79 PNeckoParent* neckoParent = 80 LoneManagedOrNullAsserts(aContentParent->ManagedPNeckoParent()); 81 if (neckoParent) { 82 PCookieServiceParent* csParent = 83 LoneManagedOrNullAsserts(neckoParent->ManagedPCookieServiceParent()); 84 if (csParent) { 85 cs = static_cast<CookieServiceParent*>(csParent); 86 } 87 } 88 89 if (cs) { 90 nsCOMPtr<nsIURI> uri = principal->GetURI(); 91 cs->UpdateCookieInContentList(uri, principal->OriginAttributesRef()); 92 } else { 93 aContentParent->AddPrincipalToCookieInProcessCache(principal); 94 } 95 } 96 97 } // namespace 98 99 // static 100 bool RemoteWorkerManager::MatchRemoteType(const nsACString& processRemoteType, 101 const nsACString& workerRemoteType) { 102 LOG(("MatchRemoteType [processRemoteType=%s, workerRemoteType=%s]", 103 PromiseFlatCString(processRemoteType).get(), 104 PromiseFlatCString(workerRemoteType).get())); 105 106 // Respecting COOP and COEP requires processing headers in the parent 107 // process in order to choose an appropriate content process, but the 108 // workers' ScriptLoader processes headers in content processes. An 109 // intermediary step that provides security guarantees is to simply never 110 // allow SharedWorkers and ServiceWorkers to exist in a COOP+COEP process. 111 // The ultimate goal is to allow these worker types to be put in such 112 // processes based on their script response headers. 113 // https://bugzilla.mozilla.org/show_bug.cgi?id=1595206 114 // 115 // RemoteWorkerManager::GetRemoteType should not select this remoteType 116 // and so workerRemoteType is not expected to be set to a coop+coep 117 // remoteType and here we can just assert that it is not happening. 118 MOZ_ASSERT(!IsWebCoopCoepRemoteType(workerRemoteType)); 119 120 return processRemoteType.Equals(workerRemoteType); 121 } 122 123 // static 124 Result<nsCString, nsresult> RemoteWorkerManager::GetRemoteType( 125 const nsCOMPtr<nsIPrincipal>& aPrincipal, WorkerKind aWorkerKind) { 126 AssertIsOnMainThread(); 127 128 MOZ_ASSERT_IF(aWorkerKind == WorkerKind::WorkerKindService, 129 aPrincipal->GetIsContentPrincipal()); 130 131 // If E10S is fully disabled, there are no decisions to be made, and we need 132 // to finish the load in the parent process. 133 if (!BrowserTabsRemoteAutostart()) { 134 LOG(("GetRemoteType: Loading in parent process as e10s is disabled")); 135 return NOT_REMOTE_TYPE; 136 } 137 138 nsCString preferredRemoteType = DEFAULT_REMOTE_TYPE; 139 if (aWorkerKind == WorkerKind::WorkerKindShared) { 140 if (auto* contentChild = ContentChild::GetSingleton()) { 141 // For a shared worker set the preferred remote type to the content 142 // child process remote type. 143 preferredRemoteType = contentChild->GetRemoteType(); 144 } else if (aPrincipal->IsSystemPrincipal()) { 145 preferredRemoteType = NOT_REMOTE_TYPE; 146 } 147 } 148 149 auto result = IsolationOptionsForWorker( 150 aPrincipal, aWorkerKind, preferredRemoteType, FissionAutostart()); 151 if (NS_WARN_IF(result.isErr())) { 152 LOG(("GetRemoteType Abort: IsolationOptionsForWorker failed")); 153 return Err(NS_ERROR_DOM_ABORT_ERR); 154 } 155 auto options = result.unwrap(); 156 157 if (MOZ_LOG_TEST(gRemoteWorkerManagerLog, LogLevel::Verbose)) { 158 nsCString principalOrigin; 159 aPrincipal->GetOrigin(principalOrigin); 160 161 LOG( 162 ("GetRemoteType workerType=%s, principal=%s, " 163 "preferredRemoteType=%s, selectedRemoteType=%s", 164 aWorkerKind == WorkerKind::WorkerKindService ? "service" : "shared", 165 principalOrigin.get(), preferredRemoteType.get(), 166 options.mRemoteType.get())); 167 } 168 169 return options.mRemoteType; 170 } 171 172 // static 173 bool RemoteWorkerManager::HasExtensionPrincipal(const RemoteWorkerData& aData) { 174 auto principalInfo = aData.principalInfo(); 175 return principalInfo.type() == PrincipalInfo::TContentPrincipalInfo && 176 // This helper method is also called from the background thread and so 177 // we can't check if the principal does have an addonPolicy object 178 // associated and we have to resort to check the url scheme instead. 179 StringBeginsWith(principalInfo.get_ContentPrincipalInfo().spec(), 180 "moz-extension://"_ns); 181 } 182 183 /* static */ 184 already_AddRefed<RemoteWorkerManager> RemoteWorkerManager::GetOrCreate() { 185 AssertIsInMainProcess(); 186 AssertIsOnBackgroundThread(); 187 188 if (!sRemoteWorkerManager) { 189 sRemoteWorkerManager = new RemoteWorkerManager(); 190 } 191 192 RefPtr<RemoteWorkerManager> rwm = sRemoteWorkerManager; 193 return rwm.forget(); 194 } 195 196 RemoteWorkerManager::RemoteWorkerManager() : mParentActor(nullptr) { 197 AssertIsInMainProcess(); 198 AssertIsOnBackgroundThread(); 199 MOZ_ASSERT(!sRemoteWorkerManager); 200 } 201 202 RemoteWorkerManager::~RemoteWorkerManager() { 203 AssertIsInMainProcess(); 204 AssertIsOnBackgroundThread(); 205 MOZ_ASSERT(sRemoteWorkerManager == this); 206 sRemoteWorkerManager = nullptr; 207 } 208 209 void RemoteWorkerManager::RegisterActor(RemoteWorkerServiceParent* aActor) { 210 AssertIsInMainProcess(); 211 AssertIsOnBackgroundThread(); 212 MOZ_ASSERT(aActor); 213 214 if (!aActor->IsOtherProcessActor()) { 215 MOZ_ASSERT(!mParentActor); 216 mParentActor = aActor; 217 return; 218 } 219 220 MOZ_ASSERT(!mChildActors.Contains(aActor)); 221 mChildActors.AppendElement(aActor); 222 } 223 224 void RemoteWorkerManager::UnregisterActor(RemoteWorkerServiceParent* aActor) { 225 AssertIsInMainProcess(); 226 AssertIsOnBackgroundThread(); 227 MOZ_ASSERT(aActor); 228 229 if (aActor == mParentActor) { 230 mParentActor = nullptr; 231 } else { 232 MOZ_ASSERT(mChildActors.Contains(aActor)); 233 mChildActors.RemoveElement(aActor); 234 } 235 } 236 237 void RemoteWorkerManager::Launch(RemoteWorkerController* aController, 238 const RemoteWorkerData& aData, 239 base::ProcessId aProcessId) { 240 AssertIsInMainProcess(); 241 AssertIsOnBackgroundThread(); 242 243 TargetActorAndKeepAlive target = SelectTargetActor(aData, aProcessId); 244 245 // If there is no available actor, try to start a process, and connect to it. 246 if (!target.mActor) { 247 // Launching is async, so we cannot check for failures right here. 248 LaunchNewContentProcess(aData)->Then( 249 GetCurrentSerialEventTarget(), __func__, 250 [self = RefPtr{this}, controller = RefPtr{aController}, 251 data = aData](TargetActorAndKeepAlive&& aTarget) { 252 if (aTarget.mActor->CanSend()) { 253 self->LaunchInternal(controller, aTarget.mActor, 254 std::move(aTarget.mKeepAlive), data); 255 } else { 256 controller->CreationFailed(); 257 } 258 }, 259 [controller = RefPtr{aController}](nsresult) { 260 controller->CreationFailed(); 261 }); 262 return; 263 } 264 265 LaunchInternal(aController, target.mActor, std::move(target.mKeepAlive), 266 aData); 267 } 268 269 void RemoteWorkerManager::LaunchInternal( 270 RemoteWorkerController* aController, 271 RemoteWorkerServiceParent* aTargetActor, 272 UniqueThreadsafeContentParentKeepAlive&& aKeepAlive, 273 const RemoteWorkerData& aData) { 274 AssertIsInMainProcess(); 275 AssertIsOnBackgroundThread(); 276 MOZ_ASSERT(aController); 277 MOZ_ASSERT(aTargetActor); 278 MOZ_ASSERT(aTargetActor == mParentActor || 279 mChildActors.Contains(aTargetActor)); 280 281 // We need to send permissions to content processes, but not if we're spawning 282 // the worker here in the parent process. 283 if (aTargetActor != mParentActor) { 284 MOZ_ASSERT(aKeepAlive); 285 286 // This won't cause any race conditions because the content process 287 // should wait for the permissions to be received before executing the 288 // Service Worker. 289 nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( 290 __func__, [contentHandle = RefPtr{aKeepAlive.get()}, 291 principalInfo = aData.principalInfo()] { 292 AssertIsOnMainThread(); 293 if (RefPtr<ContentParent> contentParent = 294 contentHandle->GetContentParent()) { 295 TransmitPermissionsAndCookiesAndBlobURLsForPrincipalInfo( 296 contentParent, principalInfo); 297 } 298 }); 299 300 MOZ_ALWAYS_SUCCEEDS(SchedulerGroup::Dispatch(r.forget())); 301 } 302 303 RefPtr<RemoteWorkerParent> workerActor = 304 MakeAndAddRef<RemoteWorkerParent>(std::move(aKeepAlive)); 305 306 mozilla::ipc::Endpoint<PRemoteWorkerNonLifeCycleOpControllerParent> parentEp; 307 mozilla::ipc::Endpoint<PRemoteWorkerNonLifeCycleOpControllerChild> childEp; 308 MOZ_ALWAYS_SUCCEEDS(PRemoteWorkerNonLifeCycleOpController::CreateEndpoints( 309 &parentEp, &childEp)); 310 311 MOZ_ASSERT(!aController->mNonLifeCycleOpController); 312 aController->mNonLifeCycleOpController = 313 MakeAndAddRef<RemoteWorkerNonLifeCycleOpControllerParent>(aController); 314 315 parentEp.Bind(aController->mNonLifeCycleOpController); 316 317 if (!aTargetActor->SendPRemoteWorkerConstructor(workerActor, aData, 318 std::move(childEp))) { 319 AsyncCreationFailed(aController); 320 return; 321 } 322 323 // This makes the link better the 2 actors. 324 aController->SetWorkerActor(workerActor); 325 workerActor->SetController(aController); 326 } 327 328 void RemoteWorkerManager::AsyncCreationFailed( 329 RemoteWorkerController* aController) { 330 RefPtr<RemoteWorkerController> controller = aController; 331 nsCOMPtr<nsIRunnable> r = 332 NS_NewRunnableFunction("RemoteWorkerManager::AsyncCreationFailed", 333 [controller]() { controller->CreationFailed(); }); 334 335 NS_DispatchToCurrentThread(r.forget()); 336 } 337 338 template <typename Callback> 339 void RemoteWorkerManager::ForEachActor( 340 Callback&& aCallback, const nsACString& aRemoteType, 341 Maybe<base::ProcessId> aProcessId) const { 342 AssertIsOnBackgroundThread(); 343 344 const auto length = mChildActors.Length(); 345 346 auto end = static_cast<uint32_t>(rand()) % length; 347 if (aProcessId) { 348 // Start from the actor with the given processId instead of starting from 349 // a random index. 350 for (auto j = length - 1; j > 0; j--) { 351 if (mChildActors[j]->OtherPid() == *aProcessId) { 352 end = j; 353 break; 354 } 355 } 356 } 357 358 uint32_t i = end; 359 360 do { 361 MOZ_ASSERT(i < mChildActors.Length()); 362 RemoteWorkerServiceParent* actor = mChildActors[i]; 363 364 if (MatchRemoteType(actor->GetRemoteType(), aRemoteType)) { 365 ThreadsafeContentParentHandle* contentHandle = 366 actor->GetContentParentHandle(); 367 368 if (!aCallback(actor, contentHandle)) { 369 break; 370 } 371 } 372 373 i = (i + 1) % length; 374 } while (i != end); 375 } 376 377 /** 378 * When selecting a target actor for a given remote worker, we have to consider 379 * that: 380 * 381 * - Service Workers can spawn even when their registering page/script isn't 382 * active (e.g. push notifications), so we don't attempt to spawn the worker 383 * in its registering script's process. We search linearly and choose the 384 * search's starting position randomly. 385 * 386 * - When Fission is enabled, Shared Workers may have to be spawned into 387 * different child process from the one where it has been registered from, and 388 * that child process may be going to be marked as dead and shutdown. 389 * 390 * ContentParent provides a way to add a KeepAlive, which will prevent the 391 * process from being shut down, through a ThreadsafeContentParentHandle in an 392 * atomic way. This call will fail if the process is already being shut down. 393 * When selecting a content process on the PBackground thread, we'll acquire the 394 * KeepAlive in that way. 395 */ 396 RemoteWorkerManager::TargetActorAndKeepAlive 397 RemoteWorkerManager::SelectTargetActorInternal( 398 const RemoteWorkerData& aData, base::ProcessId aProcessId) const { 399 AssertIsOnBackgroundThread(); 400 MOZ_ASSERT(!mChildActors.IsEmpty()); 401 402 RemoteWorkerServiceParent* actor = nullptr; 403 UniqueThreadsafeContentParentKeepAlive keepAlive; 404 405 const auto& workerRemoteType = aData.remoteType(); 406 407 ForEachActor( 408 [&](RemoteWorkerServiceParent* aActor, 409 ThreadsafeContentParentHandle* aContentHandle) { 410 // Make sure to choose an actor related to a child process that is not 411 // going to shutdown while we are still in the process of launching the 412 // remote worker. 413 // 414 // ForEachActor will start from the child actor coming from the child 415 // process with a pid equal to aProcessId if any, otherwise it would 416 // start from a random actor in the mChildActors array, this guarantees 417 // that we will choose that actor if it does also match the remote type. 418 if ((keepAlive = aContentHandle->TryAddKeepAlive())) { 419 actor = aActor; 420 return false; 421 } 422 MOZ_ASSERT(!actor); 423 return true; 424 }, 425 workerRemoteType, IsServiceWorker(aData) ? Nothing() : Some(aProcessId)); 426 427 return {actor, std::move(keepAlive)}; 428 } 429 430 RemoteWorkerManager::TargetActorAndKeepAlive 431 RemoteWorkerManager::SelectTargetActor(const RemoteWorkerData& aData, 432 base::ProcessId aProcessId) { 433 AssertIsInMainProcess(); 434 AssertIsOnBackgroundThread(); 435 436 // System principal workers should run on the parent process. 437 if (aData.principalInfo().type() == PrincipalInfo::TSystemPrincipalInfo) { 438 MOZ_ASSERT(mParentActor); 439 return {mParentActor, nullptr}; 440 } 441 442 // Extension principal workers are allowed to run on the parent process 443 // when "extensions.webextensions.remote" pref is false. 444 if (aProcessId == base::GetCurrentProcId() && 445 aData.remoteType().Equals(NOT_REMOTE_TYPE) && 446 !StaticPrefs::extensions_webextensions_remote() && 447 HasExtensionPrincipal(aData)) { 448 MOZ_ASSERT(mParentActor); 449 return {mParentActor, nullptr}; 450 } 451 452 // If e10s is off, use the parent process. 453 if (!BrowserTabsRemoteAutostart()) { 454 MOZ_ASSERT(mParentActor); 455 return {mParentActor, nullptr}; 456 } 457 458 // We shouldn't have to worry about content-principal parent-process workers. 459 MOZ_ASSERT(aProcessId != base::GetCurrentProcId()); 460 461 if (mChildActors.IsEmpty()) { 462 return {nullptr, nullptr}; 463 } 464 465 return SelectTargetActorInternal(aData, aProcessId); 466 } 467 468 RefPtr<RemoteWorkerManager::LaunchProcessPromise> 469 RemoteWorkerManager::LaunchNewContentProcess(const RemoteWorkerData& aData) { 470 AssertIsInMainProcess(); 471 AssertIsOnBackgroundThread(); 472 473 // Request a process making sure to specify aPreferUsed=true. For a given 474 // remoteType there's a pool size limit. If we pass aPreferUsed here, then if 475 // there's any process in the pool already, we will use that. If we pass 476 // false (which is the default if omitted), then this call will spawn a new 477 // process if the pool isn't at its limit yet. 478 // 479 // (Our intent is never to grow the pool size here. Our logic gets here 480 // because our current logic on PBackground is only aware of 481 // RemoteWorkerServiceParent actors that have registered themselves, which is 482 // fundamentally unaware of processes that will match in the future when they 483 // register. So we absolutely are fine with and want any existing processes.) 484 return InvokeAsync(GetMainThreadSerialEventTarget(), __func__, 485 [remoteType = aData.remoteType()]() { 486 if (AppShutdown::IsInOrBeyond( 487 ShutdownPhase::AppShutdownConfirmed)) { 488 return ContentParent::LaunchPromise::CreateAndReject( 489 NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__); 490 } 491 492 return ContentParent::GetNewOrUsedBrowserProcessAsync( 493 /* aRemoteType = */ remoteType, 494 /* aGroup */ nullptr, 495 hal::ProcessPriority::PROCESS_PRIORITY_FOREGROUND, 496 /* aPreferUsed */ true); 497 }) 498 ->Then( 499 GetMainThreadSerialEventTarget(), __func__, 500 [](UniqueContentParentKeepAlive&& aContentParent) { 501 RefPtr<RemoteWorkerServiceParent> actor = 502 aContentParent->GetRemoteWorkerServiceParent(); 503 MOZ_ASSERT(actor, "RemoteWorkerServiceParent not initialized?"); 504 return RemoteWorkerManager::LaunchProcessPromise::CreateAndResolve( 505 TargetActorAndKeepAlive{ 506 actor, UniqueContentParentKeepAliveToThreadsafe( 507 std::move(aContentParent))}, 508 __func__); 509 }, 510 [](nsresult aError) { 511 return RemoteWorkerManager::LaunchProcessPromise::CreateAndReject( 512 aError, __func__); 513 }); 514 } 515 516 } // namespace dom 517 } // namespace mozilla