RuntimeService.cpp (84938B)
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 "RuntimeService.h" 8 9 #include <algorithm> 10 11 #include "GeckoProfiler.h" 12 #include "XPCSelfHostedShmem.h" 13 #include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin 14 #include "js/ContextOptions.h" 15 #include "js/GCVector.h" 16 #include "js/Initialization.h" 17 #include "js/LocaleSensitive.h" 18 #include "js/Value.h" 19 #include "js/WasmFeatures.h" 20 #include "js/experimental/CTypes.h" // JS::CTypesActivityType, JS::SetCTypesActivityCallback 21 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* 22 #include "jsfriendapi.h" 23 #include "mozilla/Atomics.h" 24 #include "mozilla/Attributes.h" 25 #include "mozilla/CycleCollectedJSContext.h" 26 #include "mozilla/CycleCollectedJSRuntime.h" 27 #include "mozilla/FlowMarkers.h" 28 #include "mozilla/Monitor.h" 29 #include "mozilla/Preferences.h" 30 #include "mozilla/ScopeExit.h" 31 #include "mozilla/StaticPrefs_javascript.h" 32 #include "mozilla/TimeStamp.h" 33 #include "mozilla/dom/AtomList.h" 34 #include "mozilla/dom/BindingUtils.h" 35 #include "mozilla/dom/Document.h" 36 #include "mozilla/dom/ErrorEventBinding.h" 37 #include "mozilla/dom/EventTargetBinding.h" 38 #include "mozilla/dom/FetchUtil.h" 39 #include "mozilla/dom/IndexedDatabaseManager.h" 40 #include "mozilla/dom/MessageChannel.h" 41 #include "mozilla/dom/MessageEventBinding.h" 42 #include "mozilla/dom/Navigator.h" 43 #include "mozilla/dom/PerformanceService.h" 44 #include "mozilla/dom/RemoteWorkerChild.h" 45 #include "mozilla/dom/ScriptSettings.h" 46 #include "mozilla/dom/ShadowRealmGlobalScope.h" 47 #include "mozilla/dom/TimeoutHandler.h" 48 #include "mozilla/dom/TrustedTypeUtils.h" 49 #include "mozilla/dom/WorkerBinding.h" 50 #include "mozilla/extensions/WebExtensionPolicy.h" 51 #include "mozilla/glean/DomServiceworkersMetrics.h" 52 #include "mozilla/glean/DomWorkersMetrics.h" 53 #include "mozilla/ipc/BackgroundChild.h" 54 #include "nsContentSecurityUtils.h" 55 #include "nsContentUtils.h" 56 #include "nsCycleCollector.h" 57 #include "nsDOMJSUtils.h" 58 #include "nsGlobalWindowInner.h" 59 #include "nsIContentSecurityPolicy.h" 60 #include "nsIObserverService.h" 61 #include "nsIScriptContext.h" 62 #include "nsIStreamTransportService.h" 63 #include "nsISupportsImpl.h" 64 #include "nsISupportsPriority.h" 65 #include "nsITimer.h" 66 #include "nsIURI.h" 67 #include "nsIXULRuntime.h" 68 #include "nsLayoutStatics.h" 69 #include "nsNetUtil.h" 70 #include "nsPIDOMWindow.h" 71 #include "nsServiceManagerUtils.h" 72 #include "nsThreadUtils.h" 73 #include "nsXPCOM.h" 74 #include "nsXPCOMPrivate.h" 75 #include "xpcpublic.h" 76 77 #if defined(XP_MACOSX) 78 # include "nsMacUtilsImpl.h" 79 #endif 80 81 #include "WorkerDebuggerManager.h" 82 #include "WorkerError.h" 83 #include "WorkerLoadInfo.h" 84 #include "WorkerRunnable.h" 85 #include "WorkerScope.h" 86 #include "WorkerThread.h" 87 #include "prsystem.h" 88 89 #ifdef DEBUG 90 # include "nsICookieJarSettings.h" 91 #endif 92 93 #define WORKERS_SHUTDOWN_TOPIC "web-workers-shutdown" 94 95 static mozilla::LazyLogModule gWorkerShutdownDumpLog("WorkerShutdownDump"); 96 97 #ifdef SHUTDOWN_LOG 98 # undef SHUTDOWN_LOG 99 #endif 100 #define SHUTDOWN_LOG(msg) MOZ_LOG(gWorkerShutdownDumpLog, LogLevel::Debug, msg); 101 102 namespace mozilla { 103 104 using namespace ipc; 105 106 namespace dom { 107 108 using namespace workerinternals; 109 110 namespace workerinternals { 111 112 // The size of the worker runtime heaps in bytes. May be changed via pref. 113 #define WORKER_DEFAULT_RUNTIME_HEAPSIZE 32 * 1024 * 1024 114 115 // The size of the worker JS allocation threshold in MB. May be changed via 116 // pref. 117 #define WORKER_DEFAULT_ALLOCATION_THRESHOLD 30 118 119 // Half the size of the actual C stack, to be safe. 120 #define WORKER_CONTEXT_NATIVE_STACK_LIMIT 128 * sizeof(size_t) * 1024 121 122 // The maximum number of threads to use for workers, overridable via pref. 123 #define MAX_WORKERS_PER_DOMAIN 512 124 125 static_assert(MAX_WORKERS_PER_DOMAIN >= 1, 126 "We should allow at least one worker per domain."); 127 128 #define PREF_WORKERS_PREFIX "dom.workers." 129 #define PREF_WORKERS_MAX_PER_DOMAIN PREF_WORKERS_PREFIX "maxPerDomain" 130 131 #define GC_REQUEST_OBSERVER_TOPIC "child-gc-request" 132 #define CC_REQUEST_OBSERVER_TOPIC "child-cc-request" 133 #define MEMORY_PRESSURE_OBSERVER_TOPIC "memory-pressure" 134 #define LOW_MEMORY_DATA "low-memory" 135 #define LOW_MEMORY_ONGOING_DATA "low-memory-ongoing" 136 #define MEMORY_PRESSURE_STOP_OBSERVER_TOPIC "memory-pressure-stop" 137 138 // Prefixes for observing preference changes. 139 #define PREF_JS_OPTIONS_PREFIX "javascript.options." 140 #define PREF_MEM_OPTIONS_PREFIX "mem." 141 #define PREF_GCZEAL_OPTIONS_PREFIX "mem.gc_zeal." 142 #define PREF_MODE "mode" 143 #define PREF_FREQUENCY "frequency" 144 145 static NS_DEFINE_CID(kStreamTransportServiceCID, NS_STREAMTRANSPORTSERVICE_CID); 146 147 namespace { 148 149 const uint32_t kNoIndex = uint32_t(-1); 150 151 uint32_t gMaxWorkersPerDomain = MAX_WORKERS_PER_DOMAIN; 152 153 // Does not hold an owning reference. 154 Atomic<RuntimeService*> gRuntimeService(nullptr); 155 156 // Only true during the call to Init. 157 bool gRuntimeServiceDuringInit = false; 158 159 class LiteralRebindingCString : public nsDependentCString { 160 public: 161 template <int N> 162 void RebindLiteral(const char (&aStr)[N]) { 163 Rebind(aStr, N - 1); 164 } 165 }; 166 167 template <typename T> 168 struct PrefTraits; 169 170 template <> 171 struct PrefTraits<bool> { 172 using PrefValueType = bool; 173 174 static inline PrefValueType Get(const char* aPref) { 175 AssertIsOnMainThread(); 176 return Preferences::GetBool(aPref); 177 } 178 179 static inline bool Exists(const char* aPref) { 180 AssertIsOnMainThread(); 181 return Preferences::GetType(aPref) == nsIPrefBranch::PREF_BOOL; 182 } 183 }; 184 185 template <> 186 struct PrefTraits<int32_t> { 187 using PrefValueType = int32_t; 188 189 static inline PrefValueType Get(const char* aPref) { 190 AssertIsOnMainThread(); 191 return Preferences::GetInt(aPref); 192 } 193 194 static inline bool Exists(const char* aPref) { 195 AssertIsOnMainThread(); 196 return Preferences::GetType(aPref) == nsIPrefBranch::PREF_INT; 197 } 198 }; 199 200 template <typename T> 201 T GetPref(const char* aFullPref, const T aDefault, bool* aPresent = nullptr) { 202 AssertIsOnMainThread(); 203 204 using PrefHelper = PrefTraits<T>; 205 206 T result; 207 bool present = true; 208 209 if (PrefHelper::Exists(aFullPref)) { 210 result = PrefHelper::Get(aFullPref); 211 } else { 212 result = aDefault; 213 present = false; 214 } 215 216 if (aPresent) { 217 *aPresent = present; 218 } 219 return result; 220 } 221 222 void LoadContextOptions(const char* aPrefName, void* /* aClosure */) { 223 AssertIsOnMainThread(); 224 225 RuntimeService* rts = RuntimeService::GetService(); 226 if (!rts) { 227 // May be shutting down, just bail. 228 return; 229 } 230 231 const nsDependentCString prefName(aPrefName); 232 233 // Several other pref branches will get included here so bail out if there is 234 // another callback that will handle this change. 235 if (StringBeginsWith( 236 prefName, 237 nsLiteralCString(PREF_JS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX))) { 238 return; 239 } 240 241 JS::ContextOptions contextOptions; 242 xpc::SetPrefableContextOptions(contextOptions); 243 244 nsCOMPtr<nsIXULRuntime> xr = do_GetService("@mozilla.org/xre/runtime;1"); 245 if (xr) { 246 bool safeMode = false; 247 xr->GetInSafeMode(&safeMode); 248 if (safeMode) { 249 contextOptions.disableOptionsForSafeMode(); 250 } 251 } 252 253 RuntimeService::SetDefaultContextOptions(contextOptions); 254 255 if (rts) { 256 rts->UpdateAllWorkerContextOptions(); 257 } 258 } 259 260 #ifdef JS_GC_ZEAL 261 void LoadGCZealOptions(const char* /* aPrefName */, void* /* aClosure */) { 262 AssertIsOnMainThread(); 263 264 RuntimeService* rts = RuntimeService::GetService(); 265 if (!rts) { 266 // May be shutting down, just bail. 267 return; 268 } 269 270 int32_t mode = GetPref<int32_t>( 271 PREF_JS_OPTIONS_PREFIX PREF_GCZEAL_OPTIONS_PREFIX PREF_MODE, -1); 272 if (mode < 0) { 273 mode = 0; 274 } 275 276 int32_t frequency = GetPref<int32_t>( 277 PREF_JS_OPTIONS_PREFIX PREF_GCZEAL_OPTIONS_PREFIX PREF_FREQUENCY, -1); 278 if (frequency < 0) { 279 frequency = JS::BrowserDefaultGCZealFrequency; 280 } 281 282 RuntimeService::SetDefaultGCZeal(uint8_t(mode), uint32_t(frequency)); 283 284 if (rts) { 285 rts->UpdateAllWorkerGCZeal(); 286 } 287 } 288 #endif 289 290 void UpdateCommonJSGCMemoryOption(RuntimeService* aRuntimeService, 291 const char* aPrefName, JSGCParamKey aKey) { 292 AssertIsOnMainThread(); 293 NS_ASSERTION(aPrefName, "Null pref name!"); 294 295 int32_t prefValue = GetPref(aPrefName, -1); 296 Maybe<uint32_t> value = (prefValue < 0 || prefValue >= 10000) 297 ? Nothing() 298 : Some(uint32_t(prefValue)); 299 300 RuntimeService::SetDefaultJSGCSettings(aKey, value); 301 302 if (aRuntimeService) { 303 aRuntimeService->UpdateAllWorkerMemoryParameter(aKey, value); 304 } 305 } 306 307 void UpdateOtherJSGCMemoryOption(RuntimeService* aRuntimeService, 308 JSGCParamKey aKey, Maybe<uint32_t> aValue) { 309 AssertIsOnMainThread(); 310 311 RuntimeService::SetDefaultJSGCSettings(aKey, aValue); 312 313 if (aRuntimeService) { 314 aRuntimeService->UpdateAllWorkerMemoryParameter(aKey, aValue); 315 } 316 } 317 318 void LoadJSGCMemoryOptions(const char* aPrefName, void* /* aClosure */) { 319 AssertIsOnMainThread(); 320 321 RuntimeService* rts = RuntimeService::GetService(); 322 323 if (!rts) { 324 // May be shutting down, just bail. 325 return; 326 } 327 328 constexpr auto memPrefix = 329 nsLiteralCString{PREF_JS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX}; 330 const nsDependentCString fullPrefName(aPrefName); 331 332 // Pull out the string that actually distinguishes the parameter we need to 333 // change. 334 nsDependentCSubstring memPrefName; 335 if (StringBeginsWith(fullPrefName, memPrefix)) { 336 memPrefName.Rebind(fullPrefName, memPrefix.Length()); 337 } else { 338 NS_ERROR("Unknown pref name!"); 339 return; 340 } 341 342 struct WorkerGCPref { 343 nsLiteralCString memName; 344 const char* fullName; 345 JSGCParamKey key; 346 }; 347 348 #define PREF(suffix_, key_) \ 349 { \ 350 nsLiteralCString(suffix_), \ 351 PREF_JS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX suffix_, key_ \ 352 } 353 constexpr WorkerGCPref kWorkerPrefs[] = { 354 PREF("max", JSGC_MAX_BYTES), 355 PREF("gc_high_frequency_time_limit_ms", JSGC_HIGH_FREQUENCY_TIME_LIMIT), 356 PREF("gc_low_frequency_heap_growth", JSGC_LOW_FREQUENCY_HEAP_GROWTH), 357 PREF("gc_high_frequency_large_heap_growth", 358 JSGC_HIGH_FREQUENCY_LARGE_HEAP_GROWTH), 359 PREF("gc_high_frequency_small_heap_growth", 360 JSGC_HIGH_FREQUENCY_SMALL_HEAP_GROWTH), 361 PREF("gc_small_heap_size_max_mb", JSGC_SMALL_HEAP_SIZE_MAX), 362 PREF("gc_large_heap_size_min_mb", JSGC_LARGE_HEAP_SIZE_MIN), 363 PREF("gc_balanced_heap_limits", JSGC_BALANCED_HEAP_LIMITS_ENABLED), 364 PREF("gc_heap_growth_factor", JSGC_HEAP_GROWTH_FACTOR), 365 PREF("gc_allocation_threshold_mb", JSGC_ALLOCATION_THRESHOLD), 366 PREF("gc_malloc_threshold_base_mb", JSGC_MALLOC_THRESHOLD_BASE), 367 PREF("gc_small_heap_incremental_limit", 368 JSGC_SMALL_HEAP_INCREMENTAL_LIMIT), 369 PREF("gc_large_heap_incremental_limit", 370 JSGC_LARGE_HEAP_INCREMENTAL_LIMIT), 371 PREF("gc_urgent_threshold_mb", JSGC_URGENT_THRESHOLD_MB), 372 PREF("gc_incremental_slice_ms", JSGC_SLICE_TIME_BUDGET_MS), 373 PREF("gc_min_empty_chunk_count", JSGC_MIN_EMPTY_CHUNK_COUNT), 374 PREF("gc_compacting", JSGC_COMPACTING_ENABLED), 375 PREF("gc_parallel_marking", JSGC_PARALLEL_MARKING_ENABLED), 376 PREF("gc_parallel_marking_threshold_mb", 377 JSGC_PARALLEL_MARKING_THRESHOLD_MB), 378 PREF("gc_max_parallel_marking_threads", JSGC_MAX_MARKING_THREADS), 379 #ifdef NIGHTLY_BUILD 380 PREF("gc_experimental_semispace_nursery", JSGC_SEMISPACE_NURSERY_ENABLED), 381 #endif 382 PREF("nursery_max_time_goal_ms", JSGC_NURSERY_MAX_TIME_GOAL_MS), 383 // Note: Workers do not currently trigger eager minor GC, but if that is 384 // desired the following parameters should be added: 385 // javascript.options.mem.nursery_eager_collection_threshold_kb 386 // javascript.options.mem.nursery_eager_collection_threshold_percent 387 // javascript.options.mem.nursery_eager_collection_timeout_ms 388 }; 389 #undef PREF 390 391 auto pref = kWorkerPrefs; 392 auto end = kWorkerPrefs + std::size(kWorkerPrefs); 393 394 if (gRuntimeServiceDuringInit) { 395 // During init, we want to update every pref in kWorkerPrefs. 396 MOZ_ASSERT(memPrefName.IsEmpty(), 397 "Pref branch prefix only expected during init"); 398 } else { 399 // Otherwise, find the single pref that changed. 400 while (pref != end) { 401 if (pref->memName == memPrefName) { 402 end = pref + 1; 403 break; 404 } 405 ++pref; 406 } 407 #ifdef DEBUG 408 if (pref == end) { 409 nsAutoCString message("Workers don't support the '"); 410 message.Append(memPrefName); 411 message.AppendLiteral("' preference!"); 412 NS_WARNING(message.get()); 413 } 414 #endif 415 } 416 417 while (pref != end) { 418 switch (pref->key) { 419 case JSGC_MAX_BYTES: { 420 int32_t prefValue = GetPref(pref->fullName, -1); 421 Maybe<uint32_t> value = (prefValue <= 0 || prefValue >= 0x1000) 422 ? Nothing() 423 : Some(uint32_t(prefValue) * 1024 * 1024); 424 UpdateOtherJSGCMemoryOption(rts, pref->key, value); 425 break; 426 } 427 case JSGC_SLICE_TIME_BUDGET_MS: { 428 int32_t prefValue = GetPref(pref->fullName, -1); 429 Maybe<uint32_t> value = (prefValue <= 0 || prefValue >= 100000) 430 ? Nothing() 431 : Some(uint32_t(prefValue)); 432 UpdateOtherJSGCMemoryOption(rts, pref->key, value); 433 break; 434 } 435 case JSGC_COMPACTING_ENABLED: 436 case JSGC_PARALLEL_MARKING_ENABLED: 437 #ifdef NIGHTLY_BUILD 438 case JSGC_SEMISPACE_NURSERY_ENABLED: 439 #endif 440 case JSGC_BALANCED_HEAP_LIMITS_ENABLED: { 441 bool present; 442 bool prefValue = GetPref(pref->fullName, false, &present); 443 Maybe<uint32_t> value = present ? Some(prefValue ? 1 : 0) : Nothing(); 444 UpdateOtherJSGCMemoryOption(rts, pref->key, value); 445 break; 446 } 447 case JSGC_HIGH_FREQUENCY_TIME_LIMIT: 448 case JSGC_LOW_FREQUENCY_HEAP_GROWTH: 449 case JSGC_HIGH_FREQUENCY_LARGE_HEAP_GROWTH: 450 case JSGC_HIGH_FREQUENCY_SMALL_HEAP_GROWTH: 451 case JSGC_SMALL_HEAP_SIZE_MAX: 452 case JSGC_LARGE_HEAP_SIZE_MIN: 453 case JSGC_ALLOCATION_THRESHOLD: 454 case JSGC_MALLOC_THRESHOLD_BASE: 455 case JSGC_SMALL_HEAP_INCREMENTAL_LIMIT: 456 case JSGC_LARGE_HEAP_INCREMENTAL_LIMIT: 457 case JSGC_URGENT_THRESHOLD_MB: 458 case JSGC_MIN_EMPTY_CHUNK_COUNT: 459 case JSGC_HEAP_GROWTH_FACTOR: 460 case JSGC_PARALLEL_MARKING_THRESHOLD_MB: 461 case JSGC_MAX_MARKING_THREADS: 462 case JSGC_NURSERY_MAX_TIME_GOAL_MS: 463 UpdateCommonJSGCMemoryOption(rts, pref->fullName, pref->key); 464 break; 465 default: 466 MOZ_ASSERT_UNREACHABLE("Unknown JSGCParamKey value"); 467 break; 468 } 469 ++pref; 470 } 471 } 472 473 MOZ_CAN_RUN_SCRIPT bool InterruptCallback(JSContext* aCx) { 474 WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx); 475 MOZ_ASSERT(worker); 476 477 // Now is a good time to turn on profiling if it's pending. 478 PROFILER_JS_INTERRUPT_CALLBACK(); 479 480 return MOZ_KnownLive(worker)->InterruptCallback(aCx); 481 } 482 483 class LogViolationDetailsRunnable final : public WorkerMainThreadRunnable { 484 uint16_t mViolationType; 485 nsCString mFileName; 486 uint32_t mLineNum; 487 uint32_t mColumnNum; 488 nsString mScriptSample; 489 490 public: 491 LogViolationDetailsRunnable(WorkerPrivate* aWorker, uint16_t aViolationType, 492 const nsCString& aFileName, uint32_t aLineNum, 493 uint32_t aColumnNum, 494 const nsAString& aScriptSample) 495 : WorkerMainThreadRunnable(aWorker, 496 "RuntimeService :: LogViolationDetails"_ns), 497 mViolationType(aViolationType), 498 mFileName(aFileName), 499 mLineNum(aLineNum), 500 mColumnNum(aColumnNum), 501 mScriptSample(aScriptSample) { 502 MOZ_ASSERT(aWorker); 503 } 504 505 virtual bool MainThreadRun() override; 506 507 private: 508 ~LogViolationDetailsRunnable() = default; 509 }; 510 511 MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION bool ContentSecurityPolicyAllows( 512 JSContext* aCx, JS::RuntimeCode aKind, JS::Handle<JSString*> aCodeString, 513 JS::CompilationType aCompilationType, 514 JS::Handle<JS::StackGCVector<JSString*>> aParameterStrings, 515 JS::Handle<JSString*> aBodyString, 516 JS::Handle<JS::StackGCVector<JS::Value>> aParameterArgs, 517 JS::Handle<JS::Value> aBodyArg, bool* aOutCanCompileStrings) { 518 WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx); 519 worker->AssertIsOnWorkerThread(); 520 521 // Allow eval by default without a CSP. 522 bool evalOK = true; 523 bool reportViolation = false; 524 uint16_t violationType; 525 nsAutoJSString scriptSample; 526 if (aKind == JS::RuntimeCode::JS) { 527 ErrorResult error; 528 // FIXME(Bug 1990732): Need to pass a principal here to skip TT enforcement 529 // when this code is run from a WebExtension content script. 530 bool areArgumentsTrusted = TrustedTypeUtils:: 531 AreArgumentsTrustedForEnsureCSPDoesNotBlockStringCompilation( 532 aCx, aCodeString, aCompilationType, aParameterStrings, aBodyString, 533 aParameterArgs, aBodyArg, nullptr, error); 534 if (error.MaybeSetPendingException(aCx)) { 535 return false; 536 } 537 if (!areArgumentsTrusted) { 538 *aOutCanCompileStrings = false; 539 return true; 540 } 541 542 if (NS_WARN_IF(!scriptSample.init(aCx, aCodeString))) { 543 return false; 544 } 545 546 if (!nsContentSecurityUtils::IsEvalAllowed( 547 aCx, worker->UsesSystemPrincipal(), scriptSample)) { 548 *aOutCanCompileStrings = false; 549 return true; 550 } 551 552 if (WorkerCSPContext* ctx = worker->GetCSPContext()) { 553 evalOK = ctx->IsEvalAllowed(reportViolation); 554 } 555 violationType = nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL; 556 } else { 557 if (WorkerCSPContext* ctx = worker->GetCSPContext()) { 558 evalOK = ctx->IsWasmEvalAllowed(reportViolation); 559 } 560 561 // As for nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction, 562 // for MV2 extensions we have to allow wasm by default and report violations 563 // for historical reasons. 564 // TODO bug 1770909: remove this exception. 565 auto* principal = BasePrincipal::Cast(worker->GetPrincipal()); 566 RefPtr<extensions::WebExtensionPolicyCore> policy = 567 principal ? principal->AddonPolicyCore() : nullptr; 568 if (!evalOK && policy && policy->ManifestVersion() == 2) { 569 evalOK = true; 570 reportViolation = true; 571 } 572 573 violationType = nsIContentSecurityPolicy::VIOLATION_TYPE_WASM_EVAL; 574 } 575 576 if (reportViolation) { 577 auto caller = JSCallingLocation::Get(aCx); 578 RefPtr<LogViolationDetailsRunnable> runnable = 579 new LogViolationDetailsRunnable(worker, violationType, 580 caller.FileName(), caller.mLine, 581 caller.mColumn, scriptSample); 582 583 ErrorResult rv; 584 runnable->Dispatch(worker, Killing, rv); 585 if (NS_WARN_IF(rv.Failed())) { 586 rv.SuppressException(); 587 } 588 } 589 590 *aOutCanCompileStrings = evalOK; 591 return true; 592 } 593 594 void CTypesActivityCallback(JSContext* aCx, JS::CTypesActivityType aType) { 595 WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx); 596 worker->AssertIsOnWorkerThread(); 597 598 switch (aType) { 599 case JS::CTypesActivityType::BeginCall: 600 worker->BeginCTypesCall(); 601 break; 602 603 case JS::CTypesActivityType::EndCall: 604 worker->EndCTypesCall(); 605 break; 606 607 case JS::CTypesActivityType::BeginCallback: 608 worker->BeginCTypesCallback(); 609 break; 610 611 case JS::CTypesActivityType::EndCallback: 612 worker->EndCTypesCallback(); 613 break; 614 615 default: 616 MOZ_CRASH("Unknown type flag!"); 617 } 618 } 619 620 // JSDispatchableRunnables are WorkerRunnables used to dispatch JS::Dispatchable 621 // back to their worker thread. A WorkerRunnable is used for two reasons: 622 // 623 // 1. The JS::Dispatchable::run() callback may run JS so we cannot use a control 624 // runnable since they use async interrupts and break JS run-to-completion. 625 // 626 // 2. The DispatchToEventLoopCallback interface is *required* to fail during 627 // shutdown (see jsapi.h) which is exactly what WorkerRunnable::Dispatch() will 628 // do. Moreover, JS_DestroyContext() does *not* block on JS::Dispatchable::run 629 // being called, DispatchToEventLoopCallback failure is expected to happen 630 // during shutdown. 631 class JSDispatchableRunnable final : public WorkerThreadRunnable { 632 js::UniquePtr<JS::Dispatchable> mDispatchable; 633 634 ~JSDispatchableRunnable() { MOZ_ASSERT(!mDispatchable); } 635 636 // Disable the usual pre/post-dispatch thread assertions since we are 637 // dispatching from some random JS engine internal thread: 638 639 bool PreDispatch(WorkerPrivate* aWorkerPrivate) override { return true; } 640 641 void PostDispatch(WorkerPrivate* aWorkerPrivate, 642 bool aDispatchResult) override { 643 if (!aDispatchResult) { 644 // It is possible (for example in WASM failed compilation) that a 645 // worker will not run, and in this case we need to 646 // release the task as a failed task for deletion by the JS runtime. 647 JS::Dispatchable::ReleaseFailedTask(std::move(mDispatchable)); 648 } 649 } 650 651 public: 652 JSDispatchableRunnable(WorkerPrivate* aWorkerPrivate, 653 js::UniquePtr<JS::Dispatchable>&& aDispatchable) 654 : WorkerThreadRunnable("JSDispatchableRunnable"), 655 mDispatchable(std::move(aDispatchable)) { 656 MOZ_ASSERT(mDispatchable); 657 } 658 659 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { 660 MOZ_ASSERT(aCx == aWorkerPrivate->GetJSContext()); 661 MOZ_ASSERT(mDispatchable); 662 663 AutoJSAPI jsapi; 664 jsapi.Init(); 665 666 JS::Dispatchable::Run(aWorkerPrivate->GetJSContext(), 667 std::move(mDispatchable), 668 JS::Dispatchable::NotShuttingDown); 669 // mDispatchable is no longer valid after this point. 670 // The delete has been handled on the JS engine side 671 672 return true; 673 } 674 675 nsresult Cancel() override { 676 MOZ_ASSERT(mDispatchable); 677 678 AutoJSAPI jsapi; 679 jsapi.Init(); 680 681 // TODO: Make this make more sense 682 // Why are we calling Run here? Because the way the API was designed 683 // is so that once control is passed to the runnable, then both cancellation 684 // and running are handled through `Run` by either passing NotShuttingDown 685 // or ShuttingDown (for cancellation). 686 JS::Dispatchable::Run(GetCurrentThreadWorkerPrivate()->GetJSContext(), 687 std::move(mDispatchable), 688 JS::Dispatchable::ShuttingDown); 689 // mDispatchable is no longer valid after this point. 690 // The delete has been handled on the JS engine side 691 692 return NS_OK; 693 } 694 }; 695 696 static bool DispatchToEventLoop( 697 void* aClosure, js::UniquePtr<JS::Dispatchable>&& aDispatchable) { 698 // This callback may execute either on the worker thread or a random 699 // JS-internal helper thread. 700 701 // See comment at JS::InitDispatchToEventLoop() below for how we know the 702 // WorkerPrivate is alive. 703 WorkerPrivate* workerPrivate = reinterpret_cast<WorkerPrivate*>(aClosure); 704 705 // Dispatch is expected to fail during shutdown for the reasons outlined in 706 // the JSDispatchableRunnable comment above. 707 RefPtr<JSDispatchableRunnable> r = 708 new JSDispatchableRunnable(workerPrivate, std::move(aDispatchable)); 709 return r->Dispatch(workerPrivate); 710 } 711 712 static bool DelayedDispatchToEventLoop( 713 void* aClosure, js::UniquePtr<JS::Dispatchable>&& aDispatchable, 714 uint32_t delay) { 715 // See comment at JS::InitDispatchsToEventLoop() below for how we know the 716 // WorkerPrivate is alive. 717 WorkerPrivate* workerPrivate = reinterpret_cast<WorkerPrivate*>(aClosure); 718 719 workerPrivate->AssertIsOnWorkerThread(); 720 721 JSContext* cx = workerPrivate->GetJSContext(); 722 TimeoutHandler* handler = 723 new DelayedJSDispatchableHandler(cx, std::move(aDispatchable)); 724 workerPrivate->SetTimeout(cx, handler, delay, /* aIsInterval */ false, 725 Timeout::Reason::eJSTimeout, IgnoreErrors()); 726 727 return true; 728 } 729 730 static void AsyncTaskStarted(void* aClosure, JS::Dispatchable* aDispatchable) { 731 // See comment at JS::InitDispatchsToEventLoop() below for how we know the 732 // WorkerPrivate is alive. 733 WorkerPrivate* workerPrivate = reinterpret_cast<WorkerPrivate*>(aClosure); 734 workerPrivate->AssertIsOnWorkerThread(); 735 workerPrivate->JSAsyncTaskStarted(aDispatchable); 736 } 737 738 static void AsyncTaskFinished(void* aClosure, JS::Dispatchable* aDispatchable) { 739 // See comment at JS::InitDispatchsToEventLoop() below for how we know the 740 // WorkerPrivate is alive. 741 WorkerPrivate* workerPrivate = reinterpret_cast<WorkerPrivate*>(aClosure); 742 workerPrivate->AssertIsOnWorkerThread(); 743 workerPrivate->JSAsyncTaskFinished(aDispatchable); 744 } 745 746 static bool ConsumeStream(JSContext* aCx, JS::Handle<JSObject*> aObj, 747 JS::MimeType aMimeType, 748 JS::StreamConsumer* aConsumer) { 749 WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx); 750 if (!worker) { 751 JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr, 752 JSMSG_WASM_ERROR_CONSUMING_RESPONSE); 753 return false; 754 } 755 756 return FetchUtil::StreamResponseToJS(aCx, aObj, aMimeType, aConsumer, worker); 757 } 758 759 bool InitJSContextForWorker(WorkerPrivate* aWorkerPrivate, 760 JSContext* aWorkerCx) { 761 aWorkerPrivate->AssertIsOnWorkerThread(); 762 NS_ASSERTION(!aWorkerPrivate->GetJSContext(), "Already has a context!"); 763 764 JSSettings settings; 765 aWorkerPrivate->CopyJSSettings(settings); 766 767 JS::ContextOptionsRef(aWorkerCx) = settings.contextOptions; 768 769 // This is the real place where we set the max memory for the runtime. 770 for (const auto& setting : settings.gcSettings) { 771 if (setting.value) { 772 JS_SetGCParameter(aWorkerCx, setting.key, *setting.value); 773 } else { 774 JS_ResetGCParameter(aWorkerCx, setting.key); 775 } 776 } 777 778 JS_SetNativeStackQuota(aWorkerCx, WORKER_CONTEXT_NATIVE_STACK_LIMIT); 779 780 // Security policy: 781 static const JSSecurityCallbacks securityCallbacks = { 782 ContentSecurityPolicyAllows, TrustedTypeUtils::HostGetCodeForEval}; 783 JS_SetSecurityCallbacks(aWorkerCx, &securityCallbacks); 784 785 // A WorkerPrivate lives strictly longer than its JSRuntime so we can safely 786 // store a raw pointer as the callback's closure argument on the JSRuntime. 787 JS::InitAsyncTaskCallbacks(aWorkerCx, DispatchToEventLoop, 788 DelayedDispatchToEventLoop, AsyncTaskStarted, 789 AsyncTaskFinished, (void*)aWorkerPrivate); 790 791 JS::InitConsumeStreamCallback(aWorkerCx, ConsumeStream, 792 FetchUtil::ReportJSStreamError); 793 794 // When available, set the self-hosted shared memory to be read, so that we 795 // can decode the self-hosted content instead of parsing it. 796 auto& shm = xpc::SelfHostedShmem::GetSingleton(); 797 JS::SelfHostedCache selfHostedContent = shm.Content(); 798 799 if (!JS::InitSelfHostedCode(aWorkerCx, selfHostedContent)) { 800 NS_WARNING("Could not init self-hosted code!"); 801 return false; 802 } 803 804 JS_AddInterruptCallback(aWorkerCx, InterruptCallback); 805 806 JS::SetCTypesActivityCallback(aWorkerCx, CTypesActivityCallback); 807 808 #ifdef JS_GC_ZEAL 809 JS::SetGCZeal(aWorkerCx, settings.gcZeal, settings.gcZealFrequency); 810 #endif 811 812 return true; 813 } 814 815 static bool PreserveWrapper(JSContext* cx, JS::Handle<JSObject*> obj) { 816 MOZ_ASSERT(cx); 817 MOZ_ASSERT(obj); 818 MOZ_ASSERT(mozilla::dom::IsDOMObject(obj)); 819 820 return mozilla::dom::TryPreserveWrapper(obj); 821 } 822 823 static bool IsWorkerDebuggerGlobalOrSandbox(JS::Handle<JSObject*> aGlobal) { 824 return IsWorkerDebuggerGlobal(aGlobal) || IsWorkerDebuggerSandbox(aGlobal); 825 } 826 827 JSObject* Wrap(JSContext* cx, JS::Handle<JSObject*> existing, 828 JS::Handle<JSObject*> obj) { 829 JS::Rooted<JSObject*> targetGlobal(cx, JS::CurrentGlobalOrNull(cx)); 830 831 // Note: the JS engine unwraps CCWs before calling this callback. 832 JS::Rooted<JSObject*> originGlobal(cx, JS::GetNonCCWObjectGlobal(obj)); 833 834 const js::Wrapper* wrapper = nullptr; 835 if (IsWorkerDebuggerGlobalOrSandbox(targetGlobal) && 836 IsWorkerDebuggerGlobalOrSandbox(originGlobal)) { 837 wrapper = &js::CrossCompartmentWrapper::singleton; 838 } else { 839 wrapper = &js::OpaqueCrossCompartmentWrapper::singleton; 840 } 841 842 if (existing) { 843 js::Wrapper::Renew(existing, obj, wrapper); 844 } 845 return js::Wrapper::New(cx, obj, wrapper); 846 } 847 848 static const JSWrapObjectCallbacks WrapObjectCallbacks = { 849 Wrap, 850 nullptr, 851 }; 852 853 class WorkerJSRuntime final : public mozilla::CycleCollectedJSRuntime { 854 public: 855 // The heap size passed here doesn't matter, we will change it later in the 856 // call to JS_SetGCParameter inside InitJSContextForWorker. 857 explicit WorkerJSRuntime(JSContext* aCx, WorkerPrivate* aWorkerPrivate) 858 : CycleCollectedJSRuntime(aCx), mWorkerPrivate(aWorkerPrivate) { 859 MOZ_COUNT_CTOR_INHERITED(WorkerJSRuntime, CycleCollectedJSRuntime); 860 MOZ_ASSERT(aWorkerPrivate); 861 862 { 863 JS::UniqueChars defaultLocale = aWorkerPrivate->AdoptDefaultLocale(); 864 MOZ_ASSERT(defaultLocale, 865 "failure of a WorkerPrivate to have a default locale should " 866 "have made the worker fail to spawn"); 867 868 if (!JS_SetDefaultLocale(Runtime(), defaultLocale.get())) { 869 NS_WARNING("failed to set workerCx's default locale"); 870 } 871 } 872 } 873 874 void Shutdown(JSContext* cx) override { 875 // The CC is shut down, and the superclass destructor will GC, so make sure 876 // we don't try to CC again. 877 mWorkerPrivate = nullptr; 878 879 CycleCollectedJSRuntime::Shutdown(cx); 880 } 881 882 ~WorkerJSRuntime() { 883 MOZ_COUNT_DTOR_INHERITED(WorkerJSRuntime, CycleCollectedJSRuntime); 884 } 885 886 virtual void PrepareForForgetSkippable() override {} 887 888 virtual void BeginCycleCollectionCallback( 889 mozilla::CCReason aReason) override {} 890 891 virtual void EndCycleCollectionCallback( 892 CycleCollectorResults& aResults) override {} 893 894 void DispatchDeferredDeletion(bool aContinuation, bool aPurge) override { 895 MOZ_ASSERT(!aContinuation); 896 897 // Do it immediately, no need for asynchronous behavior here. 898 nsCycleCollector_doDeferredDeletion(); 899 } 900 901 virtual void CustomGCCallback(JSGCStatus aStatus) override { 902 if (!mWorkerPrivate) { 903 // We're shutting down, no need to do anything. 904 return; 905 } 906 907 mWorkerPrivate->AssertIsOnWorkerThread(); 908 909 if (aStatus == JSGC_END) { 910 bool collectedAnything = 911 nsCycleCollector_collect(CCReason::GC_FINISHED, nullptr); 912 mWorkerPrivate->SetCCCollectedAnything(collectedAnything); 913 } 914 } 915 916 void TraceAdditionalNativeBlackRoots(JSTracer* aTracer) override { 917 if (!mWorkerPrivate || !mWorkerPrivate->MayContinueRunning()) { 918 return; 919 } 920 921 if (WorkerGlobalScope* scope = mWorkerPrivate->GlobalScope()) { 922 if (EventListenerManager* elm = scope->GetExistingListenerManager()) { 923 elm->TraceListeners(aTracer); 924 } 925 } 926 927 if (WorkerDebuggerGlobalScope* debuggerScope = 928 mWorkerPrivate->DebuggerGlobalScope()) { 929 if (EventListenerManager* elm = 930 debuggerScope->GetExistingListenerManager()) { 931 elm->TraceListeners(aTracer); 932 } 933 } 934 }; 935 936 private: 937 WorkerPrivate* mWorkerPrivate; 938 }; 939 940 } // anonymous namespace 941 942 } // namespace workerinternals 943 944 class WorkerJSContext final : public mozilla::CycleCollectedJSContext { 945 public: 946 // The heap size passed here doesn't matter, we will change it later in the 947 // call to JS_SetGCParameter inside InitJSContextForWorker. 948 explicit WorkerJSContext(WorkerPrivate* aWorkerPrivate) 949 : mWorkerPrivate(aWorkerPrivate) { 950 MOZ_COUNT_CTOR_INHERITED(WorkerJSContext, CycleCollectedJSContext); 951 MOZ_ASSERT(aWorkerPrivate); 952 // Magical number 2. Workers have the base recursion depth 1, and normal 953 // runnables run at level 2, and we don't want to process microtasks 954 // at any other level. 955 SetTargetedMicroTaskRecursionDepth(2); 956 } 957 958 // MOZ_CAN_RUN_SCRIPT_BOUNDARY because otherwise we have to annotate the 959 // SpiderMonkey JS::JobQueue's destructor as MOZ_CAN_RUN_SCRIPT, which is a 960 // bit of a pain. 961 MOZ_CAN_RUN_SCRIPT_BOUNDARY ~WorkerJSContext() { 962 MOZ_COUNT_DTOR_INHERITED(WorkerJSContext, CycleCollectedJSContext); 963 JSContext* cx = MaybeContext(); 964 if (!cx) { 965 return; // Initialize() must have failed 966 } 967 968 // We expect to come here with the cycle collector already shut down. 969 // The superclass destructor will run the GC one final time and finalize any 970 // JSObjects that were participating in cycles that were broken during CC 971 // shutdown. 972 // Make sure we don't try to CC again. 973 mWorkerPrivate = nullptr; 974 } 975 976 WorkerJSContext* GetAsWorkerJSContext() override { return this; } 977 978 CycleCollectedJSRuntime* CreateRuntime(JSContext* aCx) override { 979 return new WorkerJSRuntime(aCx, mWorkerPrivate); 980 } 981 982 nsresult Initialize(JSRuntime* aParentRuntime) { 983 nsresult rv = CycleCollectedJSContext::Initialize( 984 aParentRuntime, WORKER_DEFAULT_RUNTIME_HEAPSIZE); 985 if (NS_WARN_IF(NS_FAILED(rv))) { 986 return rv; 987 } 988 989 JSContext* cx = Context(); 990 991 js::SetPreserveWrapperCallbacks(cx, PreserveWrapper, HasReleasedWrapper); 992 JS_InitDestroyPrincipalsCallback(cx, nsJSPrincipals::Destroy); 993 JS_InitReadPrincipalsCallback(cx, nsJSPrincipals::ReadPrincipals); 994 JS_SetWrapObjectCallbacks(cx, &WrapObjectCallbacks); 995 if (mWorkerPrivate->IsDedicatedWorker()) { 996 JS_SetFutexCanWait(cx); 997 } 998 999 return NS_OK; 1000 } 1001 1002 virtual bool useDebugQueue(JS::Handle<JSObject*> global) const override { 1003 MOZ_ASSERT(!NS_IsMainThread()); 1004 1005 return !(IsWorkerGlobal(global) || IsShadowRealmGlobal(global)); 1006 } 1007 1008 virtual void DispatchToMicroTask( 1009 already_AddRefed<MicroTaskRunnable> aRunnable) override { 1010 RefPtr<MicroTaskRunnable> runnable(aRunnable); 1011 1012 MOZ_ASSERT(!NS_IsMainThread()); 1013 MOZ_ASSERT(runnable); 1014 1015 JSContext* cx = Context(); 1016 NS_ASSERTION(cx, "This should never be null!"); 1017 1018 JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx)); 1019 NS_ASSERTION(global, "This should never be null!"); 1020 1021 JS::JobQueueMayNotBeEmpty(cx); 1022 if (StaticPrefs::javascript_options_use_js_microtask_queue()) { 1023 PROFILER_MARKER_FLOW_ONLY("WorkerJSContext::DispatchToMicroTask", OTHER, 1024 {}, FlowMarker, 1025 Flow::FromPointer(runnable.get())); 1026 1027 // On worker threads, if the current global is the worker global or 1028 // ShadowRealm global, we use the main micro task queue. Otherwise, the 1029 // current global must be either the debugger global or a debugger 1030 // sandbox, and we use the debugger micro task queue instead. 1031 if (IsWorkerGlobal(global) || IsShadowRealmGlobal(global)) { 1032 if (!EnqueueMicroTask(cx, runnable.forget())) { 1033 // This should never fail, but if it does, we have no choice but to 1034 // crash. This is always an OOM. 1035 NS_ABORT_OOM(0); 1036 } 1037 } else { 1038 MOZ_ASSERT(IsWorkerDebuggerGlobal(global) || 1039 IsWorkerDebuggerSandbox(global)); 1040 if (!EnqueueDebugMicroTask(cx, runnable.forget())) { 1041 // This should never fail, but if it does, we have no choice but to 1042 // crash. This is always an OOM. 1043 NS_ABORT_OOM(0); 1044 } 1045 } 1046 } else { 1047 std::deque<RefPtr<MicroTaskRunnable>>* microTaskQueue = nullptr; 1048 // On worker threads, if the current global is the worker global or 1049 // ShadowRealm global, we use the main micro task queue. Otherwise, the 1050 // current global must be either the debugger global or a debugger 1051 // sandbox, and we use the debugger micro task queue instead. 1052 if (IsWorkerGlobal(global) || IsShadowRealmGlobal(global)) { 1053 microTaskQueue = &GetMicroTaskQueue(); 1054 } else { 1055 MOZ_ASSERT(IsWorkerDebuggerGlobal(global) || 1056 IsWorkerDebuggerSandbox(global)); 1057 1058 microTaskQueue = &GetDebuggerMicroTaskQueue(); 1059 } 1060 1061 if (!runnable->isInList()) { 1062 // A recycled object may be in the list already. 1063 mMicrotasksToTrace.insertBack(runnable); 1064 } 1065 PROFILER_MARKER_FLOW_ONLY("WorkerJSContext::DispatchToMicroTask", OTHER, 1066 {}, FlowMarker, 1067 Flow::FromPointer(runnable.get())); 1068 microTaskQueue->push_back(std::move(runnable)); 1069 } 1070 } 1071 1072 bool IsSystemCaller() const override { 1073 return mWorkerPrivate->UsesSystemPrincipal(); 1074 } 1075 1076 void ReportError(JSErrorReport* aReport, 1077 JS::ConstUTF8CharsZ aToStringResult) override { 1078 mWorkerPrivate->ReportError(Context(), aToStringResult, aReport); 1079 } 1080 1081 WorkerPrivate* GetWorkerPrivate() const { return mWorkerPrivate; } 1082 1083 private: 1084 WorkerPrivate* mWorkerPrivate; 1085 }; 1086 1087 namespace workerinternals { 1088 1089 namespace { 1090 1091 class WorkerThreadPrimaryRunnable final : public Runnable { 1092 WorkerPrivate* mWorkerPrivate; 1093 SafeRefPtr<WorkerThread> mThread; 1094 JSRuntime* mParentRuntime; 1095 1096 class FinishedRunnable final : public Runnable { 1097 SafeRefPtr<WorkerThread> mThread; 1098 1099 public: 1100 explicit FinishedRunnable(SafeRefPtr<WorkerThread> aThread) 1101 : Runnable("WorkerThreadPrimaryRunnable::FinishedRunnable"), 1102 mThread(std::move(aThread)) { 1103 MOZ_ASSERT(mThread); 1104 } 1105 1106 NS_INLINE_DECL_REFCOUNTING_INHERITED(FinishedRunnable, Runnable) 1107 1108 private: 1109 ~FinishedRunnable() = default; 1110 1111 NS_DECL_NSIRUNNABLE 1112 }; 1113 1114 public: 1115 WorkerThreadPrimaryRunnable(WorkerPrivate* aWorkerPrivate, 1116 SafeRefPtr<WorkerThread> aThread, 1117 JSRuntime* aParentRuntime) 1118 : mozilla::Runnable("WorkerThreadPrimaryRunnable"), 1119 mWorkerPrivate(aWorkerPrivate), 1120 mThread(std::move(aThread)), 1121 mParentRuntime(aParentRuntime) { 1122 MOZ_ASSERT(aWorkerPrivate); 1123 MOZ_ASSERT(mThread); 1124 } 1125 1126 NS_INLINE_DECL_REFCOUNTING_INHERITED(WorkerThreadPrimaryRunnable, Runnable) 1127 1128 private: 1129 ~WorkerThreadPrimaryRunnable() = default; 1130 1131 NS_DECL_NSIRUNNABLE 1132 }; 1133 1134 void PrefLanguagesChanged(const char* /* aPrefName */, void* /* aClosure */) { 1135 AssertIsOnMainThread(); 1136 1137 nsTArray<nsString> languages; 1138 Navigator::GetAcceptLanguages(languages, nullptr); 1139 1140 RuntimeService* runtime = RuntimeService::GetService(); 1141 if (runtime) { 1142 runtime->UpdateAllWorkerLanguages(languages); 1143 } 1144 } 1145 1146 void AppVersionOverrideChanged(const char* /* aPrefName */, 1147 void* /* aClosure */) { 1148 AssertIsOnMainThread(); 1149 1150 nsAutoString override; 1151 Preferences::GetString("general.appversion.override", override); 1152 1153 RuntimeService* runtime = RuntimeService::GetService(); 1154 if (runtime) { 1155 runtime->UpdateAppVersionOverridePreference(override); 1156 } 1157 } 1158 1159 void PlatformOverrideChanged(const char* /* aPrefName */, 1160 void* /* aClosure */) { 1161 AssertIsOnMainThread(); 1162 1163 nsAutoString override; 1164 Preferences::GetString("general.platform.override", override); 1165 1166 RuntimeService* runtime = RuntimeService::GetService(); 1167 if (runtime) { 1168 runtime->UpdatePlatformOverridePreference(override); 1169 } 1170 } 1171 1172 } /* anonymous namespace */ 1173 1174 // This is only touched on the main thread. Initialized in Init() below. 1175 StaticAutoPtr<JSSettings> RuntimeService::sDefaultJSSettings; 1176 1177 RuntimeService::RuntimeService() 1178 : mMutex("RuntimeService::mMutex"), 1179 mObserved(false), 1180 mShuttingDown(false), 1181 mNavigatorPropertiesLoaded(false) { 1182 AssertIsOnMainThread(); 1183 MOZ_ASSERT(!GetService(), "More than one service!"); 1184 } 1185 1186 RuntimeService::~RuntimeService() { 1187 AssertIsOnMainThread(); 1188 1189 // gRuntimeService can be null if Init() fails. 1190 MOZ_ASSERT(!GetService() || GetService() == this, "More than one service!"); 1191 1192 gRuntimeService = nullptr; 1193 } 1194 1195 // static 1196 RuntimeService* RuntimeService::GetOrCreateService() { 1197 AssertIsOnMainThread(); 1198 1199 if (!gRuntimeService) { 1200 // The observer service now owns us until shutdown. 1201 gRuntimeService = new RuntimeService(); 1202 if (NS_FAILED((*gRuntimeService).Init())) { 1203 NS_WARNING("Failed to initialize!"); 1204 (*gRuntimeService).Cleanup(); 1205 gRuntimeService = nullptr; 1206 return nullptr; 1207 } 1208 } 1209 1210 return gRuntimeService; 1211 } 1212 1213 // static 1214 RuntimeService* RuntimeService::GetService() { return gRuntimeService; } 1215 1216 bool RuntimeService::RegisterWorker(WorkerPrivate& aWorkerPrivate) { 1217 aWorkerPrivate.AssertIsOnParentThread(); 1218 1219 WorkerPrivate* parent = aWorkerPrivate.GetParent(); 1220 if (!parent) { 1221 AssertIsOnMainThread(); 1222 1223 if (mShuttingDown) { 1224 return false; 1225 } 1226 } 1227 1228 const bool isServiceWorker = aWorkerPrivate.IsServiceWorker(); 1229 const bool isSharedWorker = aWorkerPrivate.IsSharedWorker(); 1230 const bool isDedicatedWorker = aWorkerPrivate.IsDedicatedWorker(); 1231 if (isServiceWorker) { 1232 AssertIsOnMainThread(); 1233 } 1234 1235 nsCString sharedWorkerScriptSpec; 1236 if (isSharedWorker) { 1237 AssertIsOnMainThread(); 1238 1239 nsCOMPtr<nsIURI> scriptURI = aWorkerPrivate.GetResolvedScriptURI(); 1240 NS_ASSERTION(scriptURI, "Null script URI!"); 1241 1242 nsresult rv = scriptURI->GetSpec(sharedWorkerScriptSpec); 1243 if (NS_FAILED(rv)) { 1244 NS_WARNING("GetSpec failed?!"); 1245 return false; 1246 } 1247 1248 NS_ASSERTION(!sharedWorkerScriptSpec.IsEmpty(), "Empty spec!"); 1249 } 1250 1251 bool exemptFromPerDomainMax = false; 1252 if (isServiceWorker) { 1253 AssertIsOnMainThread(); 1254 exemptFromPerDomainMax = Preferences::GetBool( 1255 "dom.serviceWorkers.exemptFromPerDomainMax", false); 1256 } 1257 1258 const nsCString& domain = aWorkerPrivate.Domain(); 1259 1260 bool queued = false; 1261 { 1262 MutexAutoLock lock(mMutex); 1263 1264 auto* const domainInfo = 1265 mDomainMap 1266 .LookupOrInsertWith( 1267 domain, 1268 [&domain, parent] { 1269 NS_ASSERTION(!parent, "Shouldn't have a parent here!"); 1270 (void)parent; // silence clang -Wunused-lambda-capture in 1271 // opt builds 1272 auto wdi = MakeUnique<WorkerDomainInfo>(); 1273 wdi->mDomain = domain; 1274 return wdi; 1275 }) 1276 .get(); 1277 1278 queued = gMaxWorkersPerDomain && 1279 domainInfo->ActiveWorkerCount() >= gMaxWorkersPerDomain && 1280 !domain.IsEmpty() && !exemptFromPerDomainMax; 1281 1282 if (queued) { 1283 domainInfo->mQueuedWorkers.AppendElement(&aWorkerPrivate); 1284 1285 // Worker spawn gets queued due to hitting max workers per domain 1286 // limit so let's log a warning. 1287 WorkerPrivate::ReportErrorToConsole(nsIScriptError::warningFlag, "DOM"_ns, 1288 nsContentUtils::eDOM_PROPERTIES, 1289 "HittingMaxWorkersPerDomain2"_ns); 1290 1291 if (isServiceWorker) { 1292 glean::workers::service_worker_spawn_gets_queued.Add(1); 1293 } else if (isSharedWorker) { 1294 glean::workers::shared_worker_spawn_gets_queued.Add(1); 1295 } else if (isDedicatedWorker) { 1296 glean::workers::dedicated_worker_spawn_gets_queued.Add(1); 1297 } 1298 } else if (parent) { 1299 domainInfo->mChildWorkerCount++; 1300 } else if (isServiceWorker) { 1301 domainInfo->mActiveServiceWorkers.AppendElement(&aWorkerPrivate); 1302 } else { 1303 domainInfo->mActiveWorkers.AppendElement(&aWorkerPrivate); 1304 } 1305 } 1306 1307 aWorkerPrivate.SetIsQueued(queued); 1308 1309 // From here on out we must call UnregisterWorker if something fails! 1310 if (parent) { 1311 if (!parent->AddChildWorker(aWorkerPrivate)) { 1312 UnregisterWorker(aWorkerPrivate); 1313 return false; 1314 } 1315 } else { 1316 if (!mNavigatorPropertiesLoaded) { 1317 if (NS_FAILED(Navigator::GetAppVersion( 1318 mNavigatorProperties.mAppVersion, aWorkerPrivate.GetDocument(), 1319 false /* aUsePrefOverriddenValue */)) || 1320 NS_FAILED(Navigator::GetPlatform( 1321 mNavigatorProperties.mPlatform, aWorkerPrivate.GetDocument(), 1322 false /* aUsePrefOverriddenValue */))) { 1323 UnregisterWorker(aWorkerPrivate); 1324 return false; 1325 } 1326 1327 // The navigator overridden properties should have already been read. 1328 1329 Navigator::GetAcceptLanguages(mNavigatorProperties.mLanguages, nullptr); 1330 mNavigatorPropertiesLoaded = true; 1331 } 1332 1333 nsPIDOMWindowInner* window = aWorkerPrivate.GetWindow(); 1334 1335 if (!isServiceWorker) { 1336 // Service workers are excluded since their lifetime is separate from 1337 // that of dom windows. 1338 if (auto* const windowArray = mWindowMap.GetOrInsertNew(window, 1); 1339 !windowArray->Contains(&aWorkerPrivate)) { 1340 windowArray->AppendElement(&aWorkerPrivate); 1341 } else { 1342 MOZ_ASSERT(aWorkerPrivate.IsSharedWorker()); 1343 } 1344 } 1345 } 1346 1347 if (!queued && !ScheduleWorker(aWorkerPrivate)) { 1348 return false; 1349 } 1350 1351 if (isServiceWorker) { 1352 AssertIsOnMainThread(); 1353 } 1354 return true; 1355 } 1356 1357 void RuntimeService::UnregisterWorker(WorkerPrivate& aWorkerPrivate) { 1358 aWorkerPrivate.AssertIsOnParentThread(); 1359 1360 WorkerPrivate* parent = aWorkerPrivate.GetParent(); 1361 if (!parent) { 1362 AssertIsOnMainThread(); 1363 } 1364 1365 const nsCString& domain = aWorkerPrivate.Domain(); 1366 1367 WorkerPrivate* queuedWorker = nullptr; 1368 { 1369 MutexAutoLock lock(mMutex); 1370 1371 WorkerDomainInfo* domainInfo; 1372 if (!mDomainMap.Get(domain, &domainInfo)) { 1373 NS_ERROR("Don't have an entry for this domain!"); 1374 } 1375 1376 // Remove old worker from everywhere. 1377 uint32_t index = domainInfo->mQueuedWorkers.IndexOf(&aWorkerPrivate); 1378 if (index != kNoIndex) { 1379 // Was queued, remove from the list. 1380 domainInfo->mQueuedWorkers.RemoveElementAt(index); 1381 } else if (parent) { 1382 MOZ_ASSERT(domainInfo->mChildWorkerCount, "Must be non-zero!"); 1383 domainInfo->mChildWorkerCount--; 1384 } else if (aWorkerPrivate.IsServiceWorker()) { 1385 MOZ_ASSERT(domainInfo->mActiveServiceWorkers.Contains(&aWorkerPrivate), 1386 "Don't know about this worker!"); 1387 domainInfo->mActiveServiceWorkers.RemoveElement(&aWorkerPrivate); 1388 } else { 1389 MOZ_ASSERT(domainInfo->mActiveWorkers.Contains(&aWorkerPrivate), 1390 "Don't know about this worker!"); 1391 domainInfo->mActiveWorkers.RemoveElement(&aWorkerPrivate); 1392 } 1393 1394 // See if there's a queued worker we can schedule. 1395 if (domainInfo->ActiveWorkerCount() < gMaxWorkersPerDomain && 1396 !domainInfo->mQueuedWorkers.IsEmpty()) { 1397 queuedWorker = domainInfo->mQueuedWorkers[0]; 1398 domainInfo->mQueuedWorkers.RemoveElementAt(0); 1399 1400 if (queuedWorker->GetParent()) { 1401 domainInfo->mChildWorkerCount++; 1402 } else if (queuedWorker->IsServiceWorker()) { 1403 domainInfo->mActiveServiceWorkers.AppendElement(queuedWorker); 1404 } else { 1405 domainInfo->mActiveWorkers.AppendElement(queuedWorker); 1406 } 1407 } 1408 1409 if (domainInfo->HasNoWorkers()) { 1410 MOZ_ASSERT(domainInfo->mQueuedWorkers.IsEmpty()); 1411 mDomainMap.Remove(domain); 1412 } 1413 } 1414 1415 // NB: For Shared Workers we used to call ShutdownOnMainThread on the 1416 // RemoteWorkerController; however, that was redundant because 1417 // RemoteWorkerChild uses a WeakWorkerRef which notifies at about the 1418 // same time as us calling into the code here and would race with us. 1419 1420 if (parent) { 1421 parent->RemoveChildWorker(aWorkerPrivate); 1422 } else if (aWorkerPrivate.IsSharedWorker()) { 1423 AssertIsOnMainThread(); 1424 1425 mWindowMap.RemoveIf([&aWorkerPrivate](const auto& iter) { 1426 const auto& workers = iter.Data(); 1427 MOZ_ASSERT(workers); 1428 1429 if (workers->RemoveElement(&aWorkerPrivate)) { 1430 MOZ_ASSERT(!workers->Contains(&aWorkerPrivate), 1431 "Added worker more than once!"); 1432 1433 return workers->IsEmpty(); 1434 } 1435 1436 return false; 1437 }); 1438 } else if (aWorkerPrivate.IsDedicatedWorker()) { 1439 // May be null. 1440 nsPIDOMWindowInner* window = aWorkerPrivate.GetWindow(); 1441 if (auto entry = mWindowMap.Lookup(window)) { 1442 MOZ_ALWAYS_TRUE(entry.Data()->RemoveElement(&aWorkerPrivate)); 1443 if (entry.Data()->IsEmpty()) { 1444 entry.Remove(); 1445 } 1446 } else { 1447 MOZ_ASSERT_UNREACHABLE("window is not in mWindowMap"); 1448 } 1449 } 1450 1451 if (queuedWorker && !ScheduleWorker(*queuedWorker)) { 1452 UnregisterWorker(*queuedWorker); 1453 } 1454 } 1455 1456 bool RuntimeService::ScheduleWorker(WorkerPrivate& aWorkerPrivate) { 1457 if (!aWorkerPrivate.Start()) { 1458 // This is ok, means that we didn't need to make a thread for this worker. 1459 return true; 1460 } 1461 1462 const WorkerThreadFriendKey friendKey; 1463 1464 SafeRefPtr<WorkerThread> thread = WorkerThread::Create(friendKey); 1465 if (!thread) { 1466 UnregisterWorker(aWorkerPrivate); 1467 return false; 1468 } 1469 1470 if (NS_FAILED(thread->SetPriority(nsISupportsPriority::PRIORITY_NORMAL))) { 1471 NS_WARNING("Could not set the thread's priority!"); 1472 } 1473 1474 aWorkerPrivate.SetThread(thread.unsafeGetRawPtr()); 1475 JSContext* cx = CycleCollectedJSContext::Get()->Context(); 1476 1477 nsCOMPtr<nsIRunnable> runnable = new WorkerThreadPrimaryRunnable( 1478 &aWorkerPrivate, thread.clonePtr(), JS_GetParentRuntime(cx)); 1479 if (NS_FAILED( 1480 thread->DispatchPrimaryRunnable(friendKey, runnable.forget()))) { 1481 UnregisterWorker(aWorkerPrivate); 1482 return false; 1483 } 1484 1485 // The worker was queued when creating, so enable remote debugger now. 1486 if (aWorkerPrivate.IsQueued()) { 1487 aWorkerPrivate.SetIsQueued(false); 1488 aWorkerPrivate.EnableRemoteDebugger(); 1489 } 1490 1491 return true; 1492 } 1493 1494 nsresult RuntimeService::Init() { 1495 AssertIsOnMainThread(); 1496 1497 nsLayoutStatics::AddRef(); 1498 1499 // Initialize JSSettings. 1500 sDefaultJSSettings = new JSSettings(); 1501 SetDefaultJSGCSettings(JSGC_MAX_BYTES, Some(WORKER_DEFAULT_RUNTIME_HEAPSIZE)); 1502 SetDefaultJSGCSettings(JSGC_ALLOCATION_THRESHOLD, 1503 Some(WORKER_DEFAULT_ALLOCATION_THRESHOLD)); 1504 1505 // nsIStreamTransportService is thread-safe but it must be initialized on the 1506 // main-thread. FileReader needs it, so, let's initialize it now. 1507 nsresult rv; 1508 nsCOMPtr<nsIStreamTransportService> sts = 1509 do_GetService(kStreamTransportServiceCID, &rv); 1510 NS_ENSURE_TRUE(sts, NS_ERROR_FAILURE); 1511 1512 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 1513 NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE); 1514 1515 rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, false); 1516 NS_ENSURE_SUCCESS(rv, rv); 1517 1518 rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); 1519 NS_ENSURE_SUCCESS(rv, rv); 1520 1521 mObserved = true; 1522 1523 if (NS_FAILED(obs->AddObserver(this, GC_REQUEST_OBSERVER_TOPIC, false))) { 1524 NS_WARNING("Failed to register for GC request notifications!"); 1525 } 1526 1527 if (NS_FAILED(obs->AddObserver(this, CC_REQUEST_OBSERVER_TOPIC, false))) { 1528 NS_WARNING("Failed to register for CC request notifications!"); 1529 } 1530 1531 if (NS_FAILED( 1532 obs->AddObserver(this, MEMORY_PRESSURE_OBSERVER_TOPIC, false))) { 1533 NS_WARNING("Failed to register for memory pressure notifications!"); 1534 } 1535 1536 if (NS_FAILED( 1537 obs->AddObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC, false))) { 1538 NS_WARNING("Failed to register for offline notification event!"); 1539 } 1540 1541 MOZ_ASSERT(!gRuntimeServiceDuringInit, "This should be false!"); 1542 gRuntimeServiceDuringInit = true; 1543 1544 #define WORKER_PREF(name, callback) \ 1545 NS_FAILED(Preferences::RegisterCallbackAndCall(callback, name)) 1546 1547 if (NS_FAILED(Preferences::RegisterPrefixCallbackAndCall( 1548 LoadJSGCMemoryOptions, 1549 PREF_JS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX)) || 1550 #ifdef JS_GC_ZEAL 1551 NS_FAILED(Preferences::RegisterCallback( 1552 LoadGCZealOptions, 1553 PREF_JS_OPTIONS_PREFIX PREF_GCZEAL_OPTIONS_PREFIX)) || 1554 #endif 1555 WORKER_PREF("intl.accept_languages", PrefLanguagesChanged) || 1556 WORKER_PREF("general.appversion.override", AppVersionOverrideChanged) || 1557 WORKER_PREF("general.platform.override", PlatformOverrideChanged) || 1558 NS_FAILED(Preferences::RegisterPrefixCallbackAndCall( 1559 LoadContextOptions, PREF_JS_OPTIONS_PREFIX))) { 1560 NS_WARNING("Failed to register pref callbacks!"); 1561 } 1562 1563 #undef WORKER_PREF 1564 1565 MOZ_ASSERT(gRuntimeServiceDuringInit, "Should be true!"); 1566 gRuntimeServiceDuringInit = false; 1567 1568 int32_t maxPerDomain = 1569 Preferences::GetInt(PREF_WORKERS_MAX_PER_DOMAIN, MAX_WORKERS_PER_DOMAIN); 1570 gMaxWorkersPerDomain = std::max(0, maxPerDomain); 1571 1572 IndexedDatabaseManager* idm = IndexedDatabaseManager::GetOrCreate(); 1573 if (NS_WARN_IF(!idm)) { 1574 return NS_ERROR_UNEXPECTED; 1575 } 1576 1577 rv = idm->EnsureLocale(); 1578 if (NS_WARN_IF(NS_FAILED(rv))) { 1579 return rv; 1580 } 1581 1582 // PerformanceService must be initialized on the main-thread. 1583 PerformanceService::GetOrCreate(); 1584 1585 return NS_OK; 1586 } 1587 1588 void RuntimeService::Shutdown() { 1589 AssertIsOnMainThread(); 1590 1591 MOZ_ASSERT(!mShuttingDown); 1592 // That's it, no more workers. 1593 mShuttingDown = true; 1594 1595 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 1596 NS_WARNING_ASSERTION(obs, "Failed to get observer service?!"); 1597 1598 // Tell anyone that cares that they're about to lose worker support. 1599 if (obs && NS_FAILED(obs->NotifyObservers(nullptr, WORKERS_SHUTDOWN_TOPIC, 1600 nullptr))) { 1601 NS_WARNING("NotifyObservers failed!"); 1602 } 1603 1604 { 1605 AutoTArray<WorkerPrivate*, 100> workers; 1606 1607 { 1608 MutexAutoLock lock(mMutex); 1609 1610 AddAllTopLevelWorkersToArray(workers); 1611 } 1612 1613 // Cancel all top-level workers. 1614 for (const auto& worker : workers) { 1615 if (!worker->Cancel()) { 1616 NS_WARNING("Failed to cancel worker!"); 1617 } 1618 } 1619 } 1620 1621 sDefaultJSSettings = nullptr; 1622 } 1623 1624 namespace { 1625 1626 class DumpCrashInfoRunnable final : public WorkerControlRunnable { 1627 public: 1628 explicit DumpCrashInfoRunnable(WorkerPrivate* aWorkerPrivate) 1629 : WorkerControlRunnable("DumpCrashInfoRunnable"), 1630 mMonitor("DumpCrashInfoRunnable::mMonitor"), 1631 mWorkerPrivate(aWorkerPrivate) {} 1632 1633 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { 1634 MonitorAutoLock lock(mMonitor); 1635 if (!mHasMsg) { 1636 aWorkerPrivate->DumpCrashInformation(mMsg); 1637 mHasMsg.Flip(); 1638 } 1639 lock.Notify(); 1640 return true; 1641 } 1642 1643 nsresult Cancel() override { 1644 MonitorAutoLock lock(mMonitor); 1645 if (!mHasMsg) { 1646 mMsg.Assign("Canceled"); 1647 mHasMsg.Flip(); 1648 } 1649 lock.Notify(); 1650 1651 return NS_OK; 1652 } 1653 1654 bool DispatchAndWait() { 1655 MonitorAutoLock lock(mMonitor); 1656 1657 if (!Dispatch(mWorkerPrivate)) { 1658 // The worker is already dead but the main thread still didn't remove it 1659 // from RuntimeService's registry. 1660 return false; 1661 } 1662 1663 // To avoid any possibility of process hangs we never receive reports on 1664 // we give the worker 1sec to react. 1665 lock.Wait(TimeDuration::FromMilliseconds(1000)); 1666 if (!mHasMsg) { 1667 mMsg.Append("NoResponse"); 1668 mHasMsg.Flip(); 1669 } 1670 return true; 1671 } 1672 1673 const nsCString& MsgData() const { return mMsg; } 1674 1675 private: 1676 bool PreDispatch(WorkerPrivate* aWorkerPrivate) override { return true; } 1677 1678 void PostDispatch(WorkerPrivate* aWorkerPrivate, 1679 bool aDispatchResult) override {} 1680 1681 Monitor mMonitor MOZ_UNANNOTATED; 1682 nsCString mMsg; 1683 FlippedOnce<false> mHasMsg; 1684 WorkerPrivate* mWorkerPrivate; 1685 }; 1686 1687 struct ActiveWorkerStats { 1688 template <uint32_t ActiveWorkerStats::* Category> 1689 void Update(const nsTArray<WorkerPrivate*>& aWorkers) { 1690 for (const auto worker : aWorkers) { 1691 RefPtr<DumpCrashInfoRunnable> runnable = 1692 new DumpCrashInfoRunnable(worker); 1693 if (runnable->DispatchAndWait()) { 1694 ++(this->*Category); 1695 mMessage.Append(runnable->MsgData()); 1696 } 1697 } 1698 } 1699 1700 uint32_t mWorkers = 0; 1701 uint32_t mServiceWorkers = 0; 1702 nsCString mMessage; 1703 }; 1704 1705 } // namespace 1706 1707 void RuntimeService::CrashIfHanging() { 1708 MutexAutoLock lock(mMutex); 1709 1710 // If we never wanted to shut down we cannot hang. 1711 if (!mShuttingDown) { 1712 return; 1713 } 1714 1715 ActiveWorkerStats activeStats; 1716 uint32_t inactiveWorkers = 0; 1717 1718 for (const auto& aData : mDomainMap.Values()) { 1719 activeStats.Update<&ActiveWorkerStats::mWorkers>(aData->mActiveWorkers); 1720 activeStats.Update<&ActiveWorkerStats::mServiceWorkers>( 1721 aData->mActiveServiceWorkers); 1722 1723 // These might not be top-level workers... 1724 inactiveWorkers += std::count_if( 1725 aData->mQueuedWorkers.begin(), aData->mQueuedWorkers.end(), 1726 [](const auto* const worker) { return !worker->GetParent(); }); 1727 } 1728 1729 if (activeStats.mWorkers + activeStats.mServiceWorkers + inactiveWorkers == 1730 0) { 1731 return; 1732 } 1733 1734 nsCString msg; 1735 1736 // A: active Workers | S: active ServiceWorkers | Q: queued Workers 1737 msg.AppendPrintf("Workers Hanging - %d|A:%d|S:%d|Q:%d", mShuttingDown ? 1 : 0, 1738 activeStats.mWorkers, activeStats.mServiceWorkers, 1739 inactiveWorkers); 1740 msg.Append(activeStats.mMessage); 1741 1742 // This string will be leaked. 1743 MOZ_CRASH_UNSAFE(strdup(msg.BeginReading())); 1744 } 1745 1746 // This spins the event loop until all workers are finished and their threads 1747 // have been joined. 1748 void RuntimeService::Cleanup() { 1749 AssertIsOnMainThread(); 1750 1751 if (!mShuttingDown) { 1752 Shutdown(); 1753 } 1754 1755 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 1756 NS_WARNING_ASSERTION(obs, "Failed to get observer service?!"); 1757 1758 { 1759 MutexAutoLock lock(mMutex); 1760 1761 AutoTArray<WorkerPrivate*, 100> workers; 1762 AddAllTopLevelWorkersToArray(workers); 1763 1764 if (!workers.IsEmpty()) { 1765 nsIThread* currentThread = NS_GetCurrentThread(); 1766 NS_ASSERTION(currentThread, "This should never be null!"); 1767 1768 // If the loop below takes too long, we probably have a problematic 1769 // worker. MOZ_LOG some info before the parent process forcibly 1770 // terminates us so that in the event we are a content process, the log 1771 // output can provide useful context about the workers that did not 1772 // cleanly shut down. 1773 nsCOMPtr<nsITimer> timer; 1774 RefPtr<RuntimeService> self = this; 1775 nsresult rv = NS_NewTimerWithCallback( 1776 getter_AddRefs(timer), 1777 [self](nsITimer*) { self->DumpRunningWorkers(); }, 1778 TimeDuration::FromSeconds(1), nsITimer::TYPE_ONE_SHOT, 1779 "RuntimeService::WorkerShutdownDump"_ns); 1780 (void)NS_WARN_IF(NS_FAILED(rv)); 1781 1782 // And make sure all their final messages have run and all their threads 1783 // have joined. 1784 while (mDomainMap.Count()) { 1785 MutexAutoUnlock unlock(mMutex); 1786 1787 if (!NS_ProcessNextEvent(currentThread)) { 1788 NS_WARNING("Something bad happened!"); 1789 break; 1790 } 1791 } 1792 1793 if (NS_SUCCEEDED(rv)) { 1794 timer->Cancel(); 1795 } 1796 } 1797 } 1798 1799 NS_ASSERTION(!mWindowMap.Count(), "All windows should have been released!"); 1800 1801 #define WORKER_PREF(name, callback) \ 1802 NS_FAILED(Preferences::UnregisterCallback(callback, name)) 1803 1804 if (mObserved) { 1805 if (NS_FAILED(Preferences::UnregisterPrefixCallback( 1806 LoadContextOptions, PREF_JS_OPTIONS_PREFIX)) || 1807 WORKER_PREF("intl.accept_languages", PrefLanguagesChanged) || 1808 WORKER_PREF("general.appversion.override", AppVersionOverrideChanged) || 1809 WORKER_PREF("general.platform.override", PlatformOverrideChanged) || 1810 #ifdef JS_GC_ZEAL 1811 NS_FAILED(Preferences::UnregisterCallback( 1812 LoadGCZealOptions, 1813 PREF_JS_OPTIONS_PREFIX PREF_GCZEAL_OPTIONS_PREFIX)) || 1814 #endif 1815 NS_FAILED(Preferences::UnregisterPrefixCallback( 1816 LoadJSGCMemoryOptions, 1817 PREF_JS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX))) { 1818 NS_WARNING("Failed to unregister pref callbacks!"); 1819 } 1820 1821 #undef WORKER_PREF 1822 1823 if (obs) { 1824 if (NS_FAILED(obs->RemoveObserver(this, GC_REQUEST_OBSERVER_TOPIC))) { 1825 NS_WARNING("Failed to unregister for GC request notifications!"); 1826 } 1827 1828 if (NS_FAILED(obs->RemoveObserver(this, CC_REQUEST_OBSERVER_TOPIC))) { 1829 NS_WARNING("Failed to unregister for CC request notifications!"); 1830 } 1831 1832 if (NS_FAILED( 1833 obs->RemoveObserver(this, MEMORY_PRESSURE_OBSERVER_TOPIC))) { 1834 NS_WARNING("Failed to unregister for memory pressure notifications!"); 1835 } 1836 1837 if (NS_FAILED( 1838 obs->RemoveObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC))) { 1839 NS_WARNING("Failed to unregister for offline notification event!"); 1840 } 1841 obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID); 1842 obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); 1843 mObserved = false; 1844 } 1845 } 1846 1847 nsLayoutStatics::Release(); 1848 } 1849 1850 void RuntimeService::AddAllTopLevelWorkersToArray( 1851 nsTArray<WorkerPrivate*>& aWorkers) { 1852 for (const auto& aData : mDomainMap.Values()) { 1853 #ifdef DEBUG 1854 for (const auto& activeWorker : aData->mActiveWorkers) { 1855 MOZ_ASSERT(!activeWorker->GetParent(), 1856 "Shouldn't have a parent in this list!"); 1857 } 1858 for (const auto& activeServiceWorker : aData->mActiveServiceWorkers) { 1859 MOZ_ASSERT(!activeServiceWorker->GetParent(), 1860 "Shouldn't have a parent in this list!"); 1861 } 1862 #endif 1863 1864 aWorkers.AppendElements(aData->mActiveWorkers); 1865 aWorkers.AppendElements(aData->mActiveServiceWorkers); 1866 1867 // These might not be top-level workers... 1868 std::copy_if(aData->mQueuedWorkers.begin(), aData->mQueuedWorkers.end(), 1869 MakeBackInserter(aWorkers), 1870 [](const auto& worker) { return !worker->GetParent(); }); 1871 } 1872 } 1873 1874 nsTArray<WorkerPrivate*> RuntimeService::GetWorkersForWindow( 1875 const nsPIDOMWindowInner& aWindow) const { 1876 AssertIsOnMainThread(); 1877 1878 nsTArray<WorkerPrivate*> result; 1879 if (nsTArray<WorkerPrivate*>* const workers = mWindowMap.Get(&aWindow)) { 1880 NS_ASSERTION(!workers->IsEmpty(), "Should have been removed!"); 1881 result.AppendElements(*workers); 1882 } 1883 return result; 1884 } 1885 1886 void RuntimeService::CancelWorkersForWindow(const nsPIDOMWindowInner& aWindow) { 1887 AssertIsOnMainThread(); 1888 1889 for (WorkerPrivate* const worker : GetWorkersForWindow(aWindow)) { 1890 MOZ_ASSERT(!worker->IsSharedWorker()); 1891 worker->Cancel(); 1892 } 1893 } 1894 1895 void RuntimeService::UpdateWorkersBackgroundState( 1896 const nsPIDOMWindowInner& aWindow, bool aIsBackground) { 1897 AssertIsOnMainThread(); 1898 for (WorkerPrivate* const worker : GetWorkersForWindow(aWindow)) { 1899 MOZ_ASSERT(!worker->IsSharedWorker()); 1900 if (aIsBackground) { 1901 worker->SetIsRunningInBackground(); 1902 } else { 1903 worker->SetIsRunningInForeground(); 1904 } 1905 } 1906 } 1907 1908 void RuntimeService::FreezeWorkersForWindow(const nsPIDOMWindowInner& aWindow) { 1909 AssertIsOnMainThread(); 1910 1911 for (WorkerPrivate* const worker : GetWorkersForWindow(aWindow)) { 1912 MOZ_ASSERT(!worker->IsSharedWorker()); 1913 worker->Freeze(&aWindow); 1914 } 1915 } 1916 1917 void RuntimeService::ThawWorkersForWindow(const nsPIDOMWindowInner& aWindow) { 1918 AssertIsOnMainThread(); 1919 1920 for (WorkerPrivate* const worker : GetWorkersForWindow(aWindow)) { 1921 MOZ_ASSERT(!worker->IsSharedWorker()); 1922 worker->Thaw(&aWindow); 1923 } 1924 } 1925 1926 void RuntimeService::SuspendWorkersForWindow( 1927 const nsPIDOMWindowInner& aWindow) { 1928 AssertIsOnMainThread(); 1929 1930 for (WorkerPrivate* const worker : GetWorkersForWindow(aWindow)) { 1931 MOZ_ASSERT(!worker->IsSharedWorker()); 1932 worker->ParentWindowPaused(); 1933 } 1934 } 1935 1936 void RuntimeService::ResumeWorkersForWindow(const nsPIDOMWindowInner& aWindow) { 1937 AssertIsOnMainThread(); 1938 1939 for (WorkerPrivate* const worker : GetWorkersForWindow(aWindow)) { 1940 MOZ_ASSERT(!worker->IsSharedWorker()); 1941 worker->ParentWindowResumed(); 1942 } 1943 } 1944 1945 void RuntimeService::PropagateStorageAccessPermissionGranted( 1946 const nsPIDOMWindowInner& aWindow) { 1947 AssertIsOnMainThread(); 1948 MOZ_ASSERT_IF(aWindow.GetExtantDoc(), aWindow.GetExtantDoc() 1949 ->CookieJarSettings() 1950 ->GetRejectThirdPartyContexts()); 1951 1952 for (WorkerPrivate* const worker : GetWorkersForWindow(aWindow)) { 1953 worker->PropagateStorageAccessPermissionGranted(); 1954 } 1955 } 1956 1957 template <typename Func> 1958 void RuntimeService::BroadcastAllWorkers(const Func& aFunc) { 1959 AssertIsOnMainThread(); 1960 1961 AutoTArray<WorkerPrivate*, 100> workers; 1962 { 1963 MutexAutoLock lock(mMutex); 1964 1965 AddAllTopLevelWorkersToArray(workers); 1966 } 1967 1968 for (const auto& worker : workers) { 1969 aFunc(*worker); 1970 } 1971 } 1972 1973 void RuntimeService::UpdateAllWorkerContextOptions() { 1974 BroadcastAllWorkers([](auto& worker) { 1975 worker.UpdateContextOptions(sDefaultJSSettings->contextOptions); 1976 }); 1977 } 1978 1979 void RuntimeService::UpdateAppVersionOverridePreference( 1980 const nsAString& aValue) { 1981 AssertIsOnMainThread(); 1982 mNavigatorProperties.mAppVersionOverridden = aValue; 1983 } 1984 1985 void RuntimeService::UpdatePlatformOverridePreference(const nsAString& aValue) { 1986 AssertIsOnMainThread(); 1987 mNavigatorProperties.mPlatformOverridden = aValue; 1988 } 1989 1990 void RuntimeService::UpdateAllWorkerLanguages( 1991 const nsTArray<nsString>& aLanguages) { 1992 MOZ_ASSERT(NS_IsMainThread()); 1993 1994 mNavigatorProperties.mLanguages = aLanguages.Clone(); 1995 BroadcastAllWorkers( 1996 [&aLanguages](auto& worker) { worker.UpdateLanguages(aLanguages); }); 1997 } 1998 1999 void RuntimeService::UpdateAllWorkerMemoryParameter(JSGCParamKey aKey, 2000 Maybe<uint32_t> aValue) { 2001 BroadcastAllWorkers([aKey, aValue](auto& worker) { 2002 worker.UpdateJSWorkerMemoryParameter(aKey, aValue); 2003 }); 2004 } 2005 2006 #ifdef JS_GC_ZEAL 2007 void RuntimeService::UpdateAllWorkerGCZeal() { 2008 BroadcastAllWorkers([](auto& worker) { 2009 worker.UpdateGCZeal(sDefaultJSSettings->gcZeal, 2010 sDefaultJSSettings->gcZealFrequency); 2011 }); 2012 } 2013 #endif 2014 2015 void RuntimeService::SetLowMemoryStateAllWorkers(bool aState) { 2016 BroadcastAllWorkers( 2017 [aState](auto& worker) { worker.SetLowMemoryState(aState); }); 2018 } 2019 2020 void RuntimeService::GarbageCollectAllWorkers(bool aShrinking) { 2021 BroadcastAllWorkers( 2022 [aShrinking](auto& worker) { worker.GarbageCollect(aShrinking); }); 2023 } 2024 2025 void RuntimeService::CycleCollectAllWorkers() { 2026 BroadcastAllWorkers([](auto& worker) { worker.CycleCollect(); }); 2027 } 2028 2029 void RuntimeService::SendOfflineStatusChangeEventToAllWorkers(bool aIsOffline) { 2030 BroadcastAllWorkers([aIsOffline](auto& worker) { 2031 worker.OfflineStatusChangeEvent(aIsOffline); 2032 }); 2033 } 2034 2035 void RuntimeService::MemoryPressureAllWorkers() { 2036 BroadcastAllWorkers([](auto& worker) { worker.MemoryPressure(); }); 2037 } 2038 2039 uint32_t RuntimeService::ClampedHardwareConcurrency(bool aRFPHardcoded, 2040 bool aRFPTiered) const { 2041 // The Firefox Hardware Report says 34% of Firefox users have exactly 4 cores. 2042 // When the resistFingerprinting pref is set, we want to blend into the crowd 2043 // so spoof navigator.hardwareConcurrency = 4 to reduce user uniqueness. On 2044 // OSX, the majority of Macs have 8 cores. 2045 if (MOZ_UNLIKELY(aRFPHardcoded)) { 2046 #ifdef XP_MACOSX 2047 return 8; 2048 #else 2049 return 4; 2050 #endif 2051 } 2052 2053 // This needs to be atomic, because multiple workers, and even mainthread, 2054 // could race to initialize it at once. 2055 static Atomic<uint32_t> unclampedHardwareConcurrency; 2056 2057 // No need to loop here: if compareExchange fails, that just means that some 2058 // other worker has initialized numberOfProcessors, so we're good to go. 2059 if (!unclampedHardwareConcurrency) { 2060 int32_t numberOfProcessors = 0; 2061 #if defined(XP_MACOSX) 2062 if (nsMacUtilsImpl::IsTCSMAvailable()) { 2063 // On failure, zero is returned from GetPhysicalCPUCount() 2064 // and we fallback to PR_GetNumberOfProcessors below. 2065 numberOfProcessors = nsMacUtilsImpl::GetPhysicalCPUCount(); 2066 } 2067 #endif 2068 if (numberOfProcessors == 0) { 2069 numberOfProcessors = PR_GetNumberOfProcessors(); 2070 } 2071 if (numberOfProcessors <= 0) { 2072 numberOfProcessors = 1; // Must be one there somewhere 2073 } 2074 (void)unclampedHardwareConcurrency.compareExchange(0, numberOfProcessors); 2075 } 2076 2077 if (MOZ_UNLIKELY(aRFPTiered)) { 2078 if (unclampedHardwareConcurrency >= 8) { 2079 return 8; 2080 } 2081 return 4; 2082 } 2083 2084 return std::min(uint32_t(unclampedHardwareConcurrency), 2085 StaticPrefs::dom_maxHardwareConcurrency()); 2086 } 2087 2088 // nsISupports 2089 NS_IMPL_ISUPPORTS(RuntimeService, nsIObserver) 2090 2091 // nsIObserver 2092 NS_IMETHODIMP 2093 RuntimeService::Observe(nsISupports* aSubject, const char* aTopic, 2094 const char16_t* aData) { 2095 AssertIsOnMainThread(); 2096 2097 if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { 2098 Shutdown(); 2099 return NS_OK; 2100 } 2101 if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID)) { 2102 Cleanup(); 2103 return NS_OK; 2104 } 2105 if (!strcmp(aTopic, GC_REQUEST_OBSERVER_TOPIC)) { 2106 GarbageCollectAllWorkers(/* shrinking = */ false); 2107 return NS_OK; 2108 } 2109 if (!strcmp(aTopic, CC_REQUEST_OBSERVER_TOPIC)) { 2110 CycleCollectAllWorkers(); 2111 return NS_OK; 2112 } 2113 if (!strcmp(aTopic, MEMORY_PRESSURE_OBSERVER_TOPIC)) { 2114 nsDependentString data(aData); 2115 // Don't continue to GC/CC if we are in an ongoing low-memory state since 2116 // its very slow and it likely won't help us anyway. 2117 if (data.EqualsLiteral(LOW_MEMORY_ONGOING_DATA)) { 2118 return NS_OK; 2119 } 2120 if (data.EqualsLiteral(LOW_MEMORY_DATA)) { 2121 SetLowMemoryStateAllWorkers(true); 2122 } 2123 GarbageCollectAllWorkers(/* shrinking = */ true); 2124 CycleCollectAllWorkers(); 2125 MemoryPressureAllWorkers(); 2126 return NS_OK; 2127 } 2128 if (!strcmp(aTopic, MEMORY_PRESSURE_STOP_OBSERVER_TOPIC)) { 2129 SetLowMemoryStateAllWorkers(false); 2130 return NS_OK; 2131 } 2132 if (!strcmp(aTopic, NS_IOSERVICE_OFFLINE_STATUS_TOPIC)) { 2133 SendOfflineStatusChangeEventToAllWorkers(NS_IsOffline()); 2134 return NS_OK; 2135 } 2136 2137 MOZ_ASSERT_UNREACHABLE("Unknown observer topic!"); 2138 return NS_OK; 2139 } 2140 2141 namespace { 2142 const char* WorkerKindToString(WorkerKind kind) { 2143 switch (kind) { 2144 case WorkerKindDedicated: 2145 return "dedicated"; 2146 case WorkerKindShared: 2147 return "shared"; 2148 case WorkerKindService: 2149 return "service"; 2150 default: 2151 NS_WARNING("Unknown worker type"); 2152 return "unknown worker type"; 2153 } 2154 } 2155 2156 void LogWorker(WorkerPrivate* worker, const char* category) { 2157 AssertIsOnMainThread(); 2158 2159 SHUTDOWN_LOG(("Found %s (%s): %s", category, 2160 WorkerKindToString(worker->Kind()), 2161 NS_ConvertUTF16toUTF8(worker->ScriptURL()).get())); 2162 2163 if (worker->Kind() == WorkerKindService) { 2164 SHUTDOWN_LOG(("Scope: %s", worker->ServiceWorkerScope().get())); 2165 } 2166 2167 nsCString origin; 2168 worker->GetPrincipal()->GetOrigin(origin); 2169 SHUTDOWN_LOG(("Principal: %s", origin.get())); 2170 2171 nsCString loadingOrigin; 2172 worker->GetLoadingPrincipal()->GetOrigin(loadingOrigin); 2173 SHUTDOWN_LOG(("LoadingPrincipal: %s", loadingOrigin.get())); 2174 2175 RefPtr<DumpCrashInfoRunnable> runnable = new DumpCrashInfoRunnable(worker); 2176 if (runnable->DispatchAndWait()) { 2177 SHUTDOWN_LOG(("CrashInfo: %s", runnable->MsgData().get())); 2178 } else { 2179 SHUTDOWN_LOG(("CrashInfo: dispatch failed")); 2180 } 2181 } 2182 } // namespace 2183 2184 void RuntimeService::DumpRunningWorkers() { 2185 // Temporarily set the LogLevel high enough to be certain the messages are 2186 // visible. 2187 LogModule* module = gWorkerShutdownDumpLog; 2188 LogLevel prevLevel = module->Level(); 2189 2190 const auto cleanup = 2191 MakeScopeExit([module, prevLevel] { module->SetLevel(prevLevel); }); 2192 2193 if (prevLevel < LogLevel::Debug) { 2194 module->SetLevel(LogLevel::Debug); 2195 } 2196 2197 MutexAutoLock lock(mMutex); 2198 2199 for (const auto& info : mDomainMap.Values()) { 2200 for (WorkerPrivate* worker : info->mActiveWorkers) { 2201 LogWorker(worker, "ActiveWorker"); 2202 } 2203 2204 for (WorkerPrivate* worker : info->mActiveServiceWorkers) { 2205 LogWorker(worker, "ActiveServiceWorker"); 2206 } 2207 2208 for (WorkerPrivate* worker : info->mQueuedWorkers) { 2209 LogWorker(worker, "QueuedWorker"); 2210 } 2211 } 2212 } 2213 2214 void RuntimeService::UpdateWorkersPlaybackState( 2215 const nsPIDOMWindowInner& aWindow, bool aIsPlayingAudio) { 2216 AssertIsOnMainThread(); 2217 2218 for (WorkerPrivate* const worker : GetWorkersForWindow(aWindow)) { 2219 MOZ_ASSERT(!worker->IsSharedWorker()); 2220 worker->SetIsPlayingAudio(aIsPlayingAudio); 2221 } 2222 } 2223 2224 void RuntimeService::UpdateWorkersPeerConnections( 2225 const nsPIDOMWindowInner& aWindow, bool aHasPeerConnections) { 2226 AssertIsOnMainThread(); 2227 2228 for (WorkerPrivate* const worker : GetWorkersForWindow(aWindow)) { 2229 MOZ_ASSERT(!worker->IsSharedWorker()); 2230 worker->SetActivePeerConnections(aHasPeerConnections); 2231 } 2232 } 2233 2234 bool LogViolationDetailsRunnable::MainThreadRun() { 2235 AssertIsOnMainThread(); 2236 MOZ_ASSERT(mWorkerRef); 2237 2238 nsIContentSecurityPolicy* csp = mWorkerRef->Private()->GetCsp(); 2239 if (csp) { 2240 csp->LogViolationDetails(mViolationType, 2241 nullptr, // triggering element 2242 mWorkerRef->Private()->CSPEventListener(), 2243 mFileName, mScriptSample, mLineNum, mColumnNum, 2244 u""_ns, u""_ns); 2245 } 2246 2247 return true; 2248 } 2249 2250 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See 2251 // bug 1535398. 2252 MOZ_CAN_RUN_SCRIPT_BOUNDARY 2253 NS_IMETHODIMP 2254 WorkerThreadPrimaryRunnable::Run() { 2255 NS_ConvertUTF16toUTF8 url(mWorkerPrivate->ScriptURL()); 2256 AUTO_PROFILER_LABEL_DYNAMIC_CSTR("WorkerThreadPrimaryRunnable::Run", OTHER, 2257 url.get()); 2258 2259 using mozilla::ipc::BackgroundChild; 2260 { 2261 bool runLoopRan = false; 2262 auto failureCleanup = MakeScopeExit([&]() { 2263 // If Worker initialization fails, call WorkerPrivate::ScheduleDeletion() 2264 // to release the WorkerPrivate in the parent thread. 2265 mWorkerPrivate->ScheduleDeletion(WorkerPrivate::WorkerRan); 2266 }); 2267 2268 mWorkerPrivate->SetWorkerPrivateInWorkerThread(mThread.unsafeGetRawPtr()); 2269 2270 const auto threadCleanup = MakeScopeExit([&] { 2271 // If Worker initialization fails, such as creating a BackgroundChild or 2272 // the worker's JSContext initialization failing, call 2273 // WorkerPrivate::RunLoopNeverRan() to set the Worker to the correct 2274 // status, which means "Dead," to forbid WorkerThreadRunnable dispatching. 2275 if (!runLoopRan) { 2276 mWorkerPrivate->RunLoopNeverRan(); 2277 } 2278 mWorkerPrivate->ResetWorkerPrivateInWorkerThread(); 2279 }); 2280 2281 mWorkerPrivate->AssertIsOnWorkerThread(); 2282 2283 // This needs to be initialized on the worker thread before being used on 2284 // the main thread and calling BackgroundChild::GetOrCreateForCurrentThread 2285 // exposes it to the main thread. 2286 mWorkerPrivate->EnsurePerformanceStorage(); 2287 2288 if (NS_WARN_IF(!BackgroundChild::GetOrCreateForCurrentThread())) { 2289 return NS_ERROR_FAILURE; 2290 } 2291 2292 nsWeakPtr globalScopeSentinel; 2293 nsWeakPtr debuggerScopeSentinel; 2294 // Never use the following pointers without checking their corresponding 2295 // nsWeakPtr sentinel, defined above and initialized after DoRunLoop ends. 2296 WorkerGlobalScopeBase* globalScopeRawPtr = nullptr; 2297 WorkerGlobalScopeBase* debuggerScopeRawPtr = nullptr; 2298 { 2299 nsCycleCollector_startup(); 2300 2301 auto context = MakeUnique<WorkerJSContext>(mWorkerPrivate); 2302 nsresult rv = context->Initialize(mParentRuntime); 2303 if (NS_WARN_IF(NS_FAILED(rv))) { 2304 return rv; 2305 } 2306 2307 JSContext* cx = context->Context(); 2308 2309 if (!InitJSContextForWorker(mWorkerPrivate, cx)) { 2310 return NS_ERROR_FAILURE; 2311 } 2312 2313 failureCleanup.release(); 2314 2315 // Binding the RemoteWorkerDebugger child endpoint after initailzation 2316 // successfully. 2317 // mWorkerPrivate->BindRemoteWorkerDebuggerChild(); 2318 2319 runLoopRan = true; 2320 2321 { 2322 PROFILER_SET_JS_CONTEXT(context.get()); 2323 2324 { 2325 // We're on the worker thread here, and WorkerPrivate's refcounting is 2326 // non-threadsafe: you can only do it on the parent thread. What that 2327 // means in practice is that we're relying on it being kept alive 2328 // while we run. Hopefully. 2329 MOZ_KnownLive(mWorkerPrivate)->DoRunLoop(cx); 2330 // The AutoJSAPI in DoRunLoop should have reported any exceptions left 2331 // on cx. 2332 MOZ_ASSERT(!JS_IsExceptionPending(cx)); 2333 } 2334 2335 mWorkerPrivate->ShutdownModuleLoader(); 2336 2337 mWorkerPrivate->RunShutdownTasks(); 2338 2339 BackgroundChild::CloseForCurrentThread(); 2340 2341 PROFILER_CLEAR_JS_CONTEXT(); 2342 } 2343 2344 // There may still be runnables on the debugger event queue that hold a 2345 // strong reference to the debugger global scope. These runnables are not 2346 // visible to the cycle collector, so we need to make sure to clear the 2347 // debugger event queue before we try to destroy the context. If we don't, 2348 // the garbage collector will crash. 2349 // Note that this just releases the runnables and does not execute them. 2350 mWorkerPrivate->ClearDebuggerEventQueue(); 2351 2352 // Before shutting down the cycle collector we need to do one more pass 2353 // through the event loop to clean up any C++ objects that need deferred 2354 // cleanup. 2355 NS_ProcessPendingEvents(nullptr); 2356 2357 // At this point we expect the scopes to be alive if they were ever 2358 // created successfully, keep weak references and set up the sentinels. 2359 globalScopeRawPtr = mWorkerPrivate->GlobalScope(); 2360 if (globalScopeRawPtr) { 2361 globalScopeSentinel = do_GetWeakReference(globalScopeRawPtr); 2362 } 2363 MOZ_ASSERT(!globalScopeRawPtr || globalScopeSentinel); 2364 debuggerScopeRawPtr = mWorkerPrivate->DebuggerGlobalScope(); 2365 if (debuggerScopeRawPtr) { 2366 debuggerScopeSentinel = do_GetWeakReference(debuggerScopeRawPtr); 2367 } 2368 MOZ_ASSERT(!debuggerScopeRawPtr || debuggerScopeSentinel); 2369 2370 // To our best knowledge nobody should need a reference to our globals 2371 // now (NS_ProcessPendingEvents is the last expected potential usage) 2372 // and we can unroot them. 2373 mWorkerPrivate->UnrootGlobalScopes(); 2374 2375 // Perform a full GC until we collect the main worker global and CC, 2376 // which should break all cycles that touch JS. 2377 bool repeatGCCC = true; 2378 while (repeatGCCC) { 2379 JS::PrepareForFullGC(cx); 2380 JS::NonIncrementalGC(cx, JS::GCOptions::Shutdown, 2381 JS::GCReason::WORKER_SHUTDOWN); 2382 2383 // If we CCed something or got new events as a side effect, repeat. 2384 repeatGCCC = mWorkerPrivate->isLastCCCollectedAnything() || 2385 NS_HasPendingEvents(nullptr); 2386 NS_ProcessPendingEvents(nullptr); 2387 } 2388 2389 // The worker global should be unrooted and the shutdown of cycle 2390 // collection should break all the remaining cycles. 2391 nsCycleCollector_shutdown(); 2392 2393 // If ever the CC shutdown run caused side effects, process them. 2394 NS_ProcessPendingEvents(nullptr); 2395 2396 // Now WorkerJSContext goes out of scope. Do not use any cycle 2397 // collectable objects nor JS after this point! 2398 } 2399 2400 // Check sentinels if we actually removed all global scope references. 2401 // In case use the earlier set-aside raw pointers to not mess with the 2402 // ref counting after the cycle collector has gone away. 2403 if (NS_WARN_IF(globalScopeSentinel && globalScopeSentinel->IsAlive())) { 2404 MOZ_ASSERT_UNREACHABLE("WorkerGlobalScope alive after worker shutdown"); 2405 globalScopeRawPtr->NoteWorkerTerminated(); 2406 globalScopeRawPtr = nullptr; 2407 } 2408 if (NS_WARN_IF(debuggerScopeSentinel && debuggerScopeSentinel->IsAlive())) { 2409 MOZ_ASSERT_UNREACHABLE("Debugger global alive after worker shutdown"); 2410 debuggerScopeRawPtr->NoteWorkerTerminated(); 2411 debuggerScopeRawPtr = nullptr; 2412 } 2413 } 2414 2415 mWorkerPrivate->ScheduleDeletion(WorkerPrivate::WorkerRan); 2416 2417 // It is no longer safe to touch mWorkerPrivate. 2418 mWorkerPrivate = nullptr; 2419 2420 // Now recycle this thread. 2421 nsCOMPtr<nsIEventTarget> mainTarget = GetMainThreadSerialEventTarget(); 2422 MOZ_ASSERT(mainTarget); 2423 2424 RefPtr<FinishedRunnable> finishedRunnable = 2425 new FinishedRunnable(std::move(mThread)); 2426 MOZ_ALWAYS_SUCCEEDS( 2427 mainTarget->Dispatch(finishedRunnable, NS_DISPATCH_NORMAL)); 2428 2429 return NS_OK; 2430 } 2431 2432 NS_IMETHODIMP 2433 WorkerThreadPrimaryRunnable::FinishedRunnable::Run() { 2434 AssertIsOnMainThread(); 2435 2436 SafeRefPtr<WorkerThread> thread = std::move(mThread); 2437 if (thread->ShutdownRequired()) { 2438 MOZ_ALWAYS_SUCCEEDS(thread->Shutdown()); 2439 } 2440 2441 return NS_OK; 2442 } 2443 2444 } // namespace workerinternals 2445 2446 // This is mostly for invoking within a debugger. 2447 void DumpRunningWorkers() { 2448 RuntimeService* runtimeService = RuntimeService::GetService(); 2449 if (runtimeService) { 2450 runtimeService->DumpRunningWorkers(); 2451 } else { 2452 NS_WARNING("RuntimeService not found"); 2453 } 2454 } 2455 2456 void CancelWorkersForWindow(const nsPIDOMWindowInner& aWindow) { 2457 AssertIsOnMainThread(); 2458 RuntimeService* runtime = RuntimeService::GetService(); 2459 if (runtime) { 2460 runtime->CancelWorkersForWindow(aWindow); 2461 } 2462 } 2463 2464 void UpdateWorkersBackgroundState(const nsPIDOMWindowInner& aWindow, 2465 bool aIsBackground) { 2466 AssertIsOnMainThread(); 2467 RuntimeService* runtime = RuntimeService::GetService(); 2468 if (runtime) { 2469 runtime->UpdateWorkersBackgroundState(aWindow, aIsBackground); 2470 } 2471 } 2472 2473 void FreezeWorkersForWindow(const nsPIDOMWindowInner& aWindow) { 2474 AssertIsOnMainThread(); 2475 RuntimeService* runtime = RuntimeService::GetService(); 2476 if (runtime) { 2477 runtime->FreezeWorkersForWindow(aWindow); 2478 } 2479 } 2480 2481 void ThawWorkersForWindow(const nsPIDOMWindowInner& aWindow) { 2482 AssertIsOnMainThread(); 2483 RuntimeService* runtime = RuntimeService::GetService(); 2484 if (runtime) { 2485 runtime->ThawWorkersForWindow(aWindow); 2486 } 2487 } 2488 2489 void SuspendWorkersForWindow(const nsPIDOMWindowInner& aWindow) { 2490 AssertIsOnMainThread(); 2491 RuntimeService* runtime = RuntimeService::GetService(); 2492 if (runtime) { 2493 runtime->SuspendWorkersForWindow(aWindow); 2494 } 2495 } 2496 2497 void ResumeWorkersForWindow(const nsPIDOMWindowInner& aWindow) { 2498 AssertIsOnMainThread(); 2499 RuntimeService* runtime = RuntimeService::GetService(); 2500 if (runtime) { 2501 runtime->ResumeWorkersForWindow(aWindow); 2502 } 2503 } 2504 2505 void PropagateStorageAccessPermissionGrantedToWorkers( 2506 const nsPIDOMWindowInner& aWindow) { 2507 AssertIsOnMainThread(); 2508 MOZ_ASSERT_IF(aWindow.GetExtantDoc(), aWindow.GetExtantDoc() 2509 ->CookieJarSettings() 2510 ->GetRejectThirdPartyContexts()); 2511 2512 RuntimeService* runtime = RuntimeService::GetService(); 2513 if (runtime) { 2514 runtime->PropagateStorageAccessPermissionGranted(aWindow); 2515 } 2516 } 2517 2518 WorkerPrivate* GetWorkerPrivateFromContext(JSContext* aCx) { 2519 MOZ_ASSERT(!NS_IsMainThread()); 2520 MOZ_ASSERT(aCx); 2521 2522 CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::GetFor(aCx); 2523 if (!ccjscx) { 2524 return nullptr; 2525 } 2526 2527 WorkerJSContext* workerjscx = ccjscx->GetAsWorkerJSContext(); 2528 // GetWorkerPrivateFromContext is called only for worker contexts. The 2529 // context private is cleared early in ~CycleCollectedJSContext() and so 2530 // GetFor() returns null above if called after ccjscx is no longer a 2531 // WorkerJSContext. 2532 MOZ_ASSERT(workerjscx); 2533 return workerjscx->GetWorkerPrivate(); 2534 } 2535 2536 WorkerPrivate* GetCurrentThreadWorkerPrivate() { 2537 if (NS_IsMainThread()) { 2538 return nullptr; 2539 } 2540 2541 CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get(); 2542 if (!ccjscx) { 2543 return nullptr; 2544 } 2545 2546 WorkerJSContext* workerjscx = ccjscx->GetAsWorkerJSContext(); 2547 // Even when GetCurrentThreadWorkerPrivate() is called on worker 2548 // threads, the ccjscx will no longer be a WorkerJSContext if called from 2549 // stable state events during ~CycleCollectedJSContext(). 2550 if (!workerjscx) { 2551 return nullptr; 2552 } 2553 2554 return workerjscx->GetWorkerPrivate(); 2555 } 2556 2557 bool IsCurrentThreadRunningWorker() { 2558 return !NS_IsMainThread() && !!GetCurrentThreadWorkerPrivate(); 2559 } 2560 2561 bool IsCurrentThreadRunningChromeWorker() { 2562 WorkerPrivate* wp = GetCurrentThreadWorkerPrivate(); 2563 return wp && wp->UsesSystemPrincipal(); 2564 } 2565 2566 JSContext* GetCurrentWorkerThreadJSContext() { 2567 WorkerPrivate* wp = GetCurrentThreadWorkerPrivate(); 2568 if (!wp) { 2569 return nullptr; 2570 } 2571 return wp->GetJSContext(); 2572 } 2573 2574 JSObject* GetCurrentThreadWorkerGlobal() { 2575 WorkerPrivate* wp = GetCurrentThreadWorkerPrivate(); 2576 if (!wp) { 2577 return nullptr; 2578 } 2579 WorkerGlobalScope* scope = wp->GlobalScope(); 2580 if (!scope) { 2581 return nullptr; 2582 } 2583 return scope->GetGlobalJSObject(); 2584 } 2585 2586 JSObject* GetCurrentThreadWorkerDebuggerGlobal() { 2587 WorkerPrivate* wp = GetCurrentThreadWorkerPrivate(); 2588 if (!wp) { 2589 return nullptr; 2590 } 2591 WorkerDebuggerGlobalScope* scope = wp->DebuggerGlobalScope(); 2592 if (!scope) { 2593 return nullptr; 2594 } 2595 return scope->GetGlobalJSObject(); 2596 } 2597 2598 void UpdateWorkersPlaybackState(const nsPIDOMWindowInner& aWindow, 2599 bool aIsPlayingAudio) { 2600 AssertIsOnMainThread(); 2601 RuntimeService* runtime = RuntimeService::GetService(); 2602 if (runtime) { 2603 runtime->UpdateWorkersPlaybackState(aWindow, aIsPlayingAudio); 2604 } 2605 } 2606 2607 void UpdateWorkersPeerConnections(const nsPIDOMWindowInner& aWindow, 2608 bool aHasPeerConnections) { 2609 AssertIsOnMainThread(); 2610 RuntimeService* runtime = RuntimeService::GetService(); 2611 if (runtime) { 2612 runtime->UpdateWorkersPeerConnections(aWindow, aHasPeerConnections); 2613 } 2614 } 2615 2616 } // namespace dom 2617 } // namespace mozilla