Bug 1874684 - Part 38: Enable now passing tests. r=allstarschh
[gecko.git] / dom / workers / RuntimeService.cpp
blob321895e700a3129febba908fd7f35664c56ff637
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "RuntimeService.h"
9 #include "nsContentSecurityUtils.h"
10 #include "nsIContentSecurityPolicy.h"
11 #include "mozilla/dom/Document.h"
12 #include "nsIObserverService.h"
13 #include "nsIScriptContext.h"
14 #include "nsIStreamTransportService.h"
15 #include "nsISupportsPriority.h"
16 #include "nsITimer.h"
17 #include "nsIURI.h"
18 #include "nsIXULRuntime.h"
19 #include "nsPIDOMWindow.h"
21 #include <algorithm>
22 #include "mozilla/ipc/BackgroundChild.h"
23 #include "GeckoProfiler.h"
24 #include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin
25 #include "js/experimental/CTypes.h" // JS::CTypesActivityType, JS::SetCTypesActivityCallback
26 #include "jsfriendapi.h"
27 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
28 #include "js/ContextOptions.h"
29 #include "js/Initialization.h"
30 #include "js/LocaleSensitive.h"
31 #include "js/WasmFeatures.h"
32 #include "mozilla/ArrayUtils.h"
33 #include "mozilla/Atomics.h"
34 #include "mozilla/Attributes.h"
35 #include "mozilla/CycleCollectedJSContext.h"
36 #include "mozilla/CycleCollectedJSRuntime.h"
37 #include "mozilla/Telemetry.h"
38 #include "mozilla/TimeStamp.h"
39 #include "mozilla/dom/AtomList.h"
40 #include "mozilla/dom/BindingUtils.h"
41 #include "mozilla/dom/ErrorEventBinding.h"
42 #include "mozilla/dom/EventTargetBinding.h"
43 #include "mozilla/dom/FetchUtil.h"
44 #include "mozilla/dom/MessageChannel.h"
45 #include "mozilla/dom/MessageEventBinding.h"
46 #include "mozilla/dom/PerformanceService.h"
47 #include "mozilla/dom/RemoteWorkerChild.h"
48 #include "mozilla/dom/WorkerBinding.h"
49 #include "mozilla/dom/ScriptSettings.h"
50 #include "mozilla/dom/ShadowRealmGlobalScope.h"
51 #include "mozilla/dom/IndexedDatabaseManager.h"
52 #include "mozilla/DebugOnly.h"
53 #include "mozilla/Preferences.h"
54 #include "mozilla/ScopeExit.h"
55 #include "mozilla/dom/Navigator.h"
56 #include "mozilla/Monitor.h"
57 #include "nsContentUtils.h"
58 #include "nsCycleCollector.h"
59 #include "nsDOMJSUtils.h"
60 #include "nsISupportsImpl.h"
61 #include "nsLayoutStatics.h"
62 #include "nsNetUtil.h"
63 #include "nsServiceManagerUtils.h"
64 #include "nsThreadUtils.h"
65 #include "nsXPCOM.h"
66 #include "nsXPCOMPrivate.h"
67 #include "xpcpublic.h"
68 #include "XPCSelfHostedShmem.h"
70 #if defined(XP_MACOSX)
71 # include "nsMacUtilsImpl.h"
72 #endif
74 #include "WorkerDebuggerManager.h"
75 #include "WorkerError.h"
76 #include "WorkerLoadInfo.h"
77 #include "WorkerRunnable.h"
78 #include "WorkerScope.h"
79 #include "WorkerThread.h"
80 #include "prsystem.h"
82 #ifdef DEBUG
83 # include "nsICookieJarSettings.h"
84 #endif
86 #define WORKERS_SHUTDOWN_TOPIC "web-workers-shutdown"
88 static mozilla::LazyLogModule gWorkerShutdownDumpLog("WorkerShutdownDump");
90 #ifdef SHUTDOWN_LOG
91 # undef SHUTDOWN_LOG
92 #endif
93 #define SHUTDOWN_LOG(msg) MOZ_LOG(gWorkerShutdownDumpLog, LogLevel::Debug, msg);
95 namespace mozilla {
97 using namespace ipc;
99 namespace dom {
101 using namespace workerinternals;
103 namespace workerinternals {
105 // The size of the worker runtime heaps in bytes. May be changed via pref.
106 #define WORKER_DEFAULT_RUNTIME_HEAPSIZE 32 * 1024 * 1024
108 // The size of the worker JS allocation threshold in MB. May be changed via
109 // pref.
110 #define WORKER_DEFAULT_ALLOCATION_THRESHOLD 30
112 // Half the size of the actual C stack, to be safe.
113 #define WORKER_CONTEXT_NATIVE_STACK_LIMIT 128 * sizeof(size_t) * 1024
115 // The maximum number of threads to use for workers, overridable via pref.
116 #define MAX_WORKERS_PER_DOMAIN 512
118 static_assert(MAX_WORKERS_PER_DOMAIN >= 1,
119 "We should allow at least one worker per domain.");
121 #define PREF_WORKERS_PREFIX "dom.workers."
122 #define PREF_WORKERS_MAX_PER_DOMAIN PREF_WORKERS_PREFIX "maxPerDomain"
124 #define GC_REQUEST_OBSERVER_TOPIC "child-gc-request"
125 #define CC_REQUEST_OBSERVER_TOPIC "child-cc-request"
126 #define MEMORY_PRESSURE_OBSERVER_TOPIC "memory-pressure"
127 #define LOW_MEMORY_DATA "low-memory"
128 #define LOW_MEMORY_ONGOING_DATA "low-memory-ongoing"
129 #define MEMORY_PRESSURE_STOP_OBSERVER_TOPIC "memory-pressure-stop"
131 // Prefixes for observing preference changes.
132 #define PREF_JS_OPTIONS_PREFIX "javascript.options."
133 #define PREF_MEM_OPTIONS_PREFIX "mem."
134 #define PREF_GCZEAL "gczeal"
136 static NS_DEFINE_CID(kStreamTransportServiceCID, NS_STREAMTRANSPORTSERVICE_CID);
138 namespace {
140 const uint32_t kNoIndex = uint32_t(-1);
142 uint32_t gMaxWorkersPerDomain = MAX_WORKERS_PER_DOMAIN;
144 // Does not hold an owning reference.
145 Atomic<RuntimeService*> gRuntimeService(nullptr);
147 // Only true during the call to Init.
148 bool gRuntimeServiceDuringInit = false;
150 class LiteralRebindingCString : public nsDependentCString {
151 public:
152 template <int N>
153 void RebindLiteral(const char (&aStr)[N]) {
154 Rebind(aStr, N - 1);
158 template <typename T>
159 struct PrefTraits;
161 template <>
162 struct PrefTraits<bool> {
163 using PrefValueType = bool;
165 static inline PrefValueType Get(const char* aPref) {
166 AssertIsOnMainThread();
167 return Preferences::GetBool(aPref);
170 static inline bool Exists(const char* aPref) {
171 AssertIsOnMainThread();
172 return Preferences::GetType(aPref) == nsIPrefBranch::PREF_BOOL;
176 template <>
177 struct PrefTraits<int32_t> {
178 using PrefValueType = int32_t;
180 static inline PrefValueType Get(const char* aPref) {
181 AssertIsOnMainThread();
182 return Preferences::GetInt(aPref);
185 static inline bool Exists(const char* aPref) {
186 AssertIsOnMainThread();
187 return Preferences::GetType(aPref) == nsIPrefBranch::PREF_INT;
191 template <typename T>
192 T GetPref(const char* aFullPref, const T aDefault, bool* aPresent = nullptr) {
193 AssertIsOnMainThread();
195 using PrefHelper = PrefTraits<T>;
197 T result;
198 bool present = true;
200 if (PrefHelper::Exists(aFullPref)) {
201 result = PrefHelper::Get(aFullPref);
202 } else {
203 result = aDefault;
204 present = false;
207 if (aPresent) {
208 *aPresent = present;
210 return result;
213 void LoadContextOptions(const char* aPrefName, void* /* aClosure */) {
214 AssertIsOnMainThread();
216 RuntimeService* rts = RuntimeService::GetService();
217 if (!rts) {
218 // May be shutting down, just bail.
219 return;
222 const nsDependentCString prefName(aPrefName);
224 // Several other pref branches will get included here so bail out if there is
225 // another callback that will handle this change.
226 if (StringBeginsWith(
227 prefName,
228 nsLiteralCString(PREF_JS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX))) {
229 return;
232 #ifdef JS_GC_ZEAL
233 if (prefName.EqualsLiteral(PREF_JS_OPTIONS_PREFIX PREF_GCZEAL)) {
234 return;
236 #endif
238 JS::ContextOptions contextOptions;
239 xpc::SetPrefableContextOptions(contextOptions);
241 nsCOMPtr<nsIXULRuntime> xr = do_GetService("@mozilla.org/xre/runtime;1");
242 if (xr) {
243 bool safeMode = false;
244 xr->GetInSafeMode(&safeMode);
245 if (safeMode) {
246 contextOptions.disableOptionsForSafeMode();
250 RuntimeService::SetDefaultContextOptions(contextOptions);
252 if (rts) {
253 rts->UpdateAllWorkerContextOptions();
257 #ifdef JS_GC_ZEAL
258 void LoadGCZealOptions(const char* /* aPrefName */, void* /* aClosure */) {
259 AssertIsOnMainThread();
261 RuntimeService* rts = RuntimeService::GetService();
262 if (!rts) {
263 // May be shutting down, just bail.
264 return;
267 int32_t gczeal = GetPref<int32_t>(PREF_JS_OPTIONS_PREFIX PREF_GCZEAL, -1);
268 if (gczeal < 0) {
269 gczeal = 0;
272 int32_t frequency =
273 GetPref<int32_t>(PREF_JS_OPTIONS_PREFIX PREF_GCZEAL ".frequency", -1);
274 if (frequency < 0) {
275 frequency = JS_DEFAULT_ZEAL_FREQ;
278 RuntimeService::SetDefaultGCZeal(uint8_t(gczeal), uint32_t(frequency));
280 if (rts) {
281 rts->UpdateAllWorkerGCZeal();
284 #endif
286 void UpdateCommonJSGCMemoryOption(RuntimeService* aRuntimeService,
287 const char* aPrefName, JSGCParamKey aKey) {
288 AssertIsOnMainThread();
289 NS_ASSERTION(aPrefName, "Null pref name!");
291 int32_t prefValue = GetPref(aPrefName, -1);
292 Maybe<uint32_t> value = (prefValue < 0 || prefValue >= 10000)
293 ? Nothing()
294 : Some(uint32_t(prefValue));
296 RuntimeService::SetDefaultJSGCSettings(aKey, value);
298 if (aRuntimeService) {
299 aRuntimeService->UpdateAllWorkerMemoryParameter(aKey, value);
303 void UpdateOtherJSGCMemoryOption(RuntimeService* aRuntimeService,
304 JSGCParamKey aKey, Maybe<uint32_t> aValue) {
305 AssertIsOnMainThread();
307 RuntimeService::SetDefaultJSGCSettings(aKey, aValue);
309 if (aRuntimeService) {
310 aRuntimeService->UpdateAllWorkerMemoryParameter(aKey, aValue);
314 void LoadJSGCMemoryOptions(const char* aPrefName, void* /* aClosure */) {
315 AssertIsOnMainThread();
317 RuntimeService* rts = RuntimeService::GetService();
319 if (!rts) {
320 // May be shutting down, just bail.
321 return;
324 constexpr auto memPrefix =
325 nsLiteralCString{PREF_JS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX};
326 const nsDependentCString fullPrefName(aPrefName);
328 // Pull out the string that actually distinguishes the parameter we need to
329 // change.
330 nsDependentCSubstring memPrefName;
331 if (StringBeginsWith(fullPrefName, memPrefix)) {
332 memPrefName.Rebind(fullPrefName, memPrefix.Length());
333 } else {
334 NS_ERROR("Unknown pref name!");
335 return;
338 struct WorkerGCPref {
339 nsLiteralCString memName;
340 const char* fullName;
341 JSGCParamKey key;
344 #define PREF(suffix_, key_) \
346 nsLiteralCString(PREF_MEM_OPTIONS_PREFIX suffix_), \
347 PREF_JS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX suffix_, key_ \
349 constexpr WorkerGCPref kWorkerPrefs[] = {
350 PREF("max", JSGC_MAX_BYTES),
351 PREF("gc_high_frequency_time_limit_ms", JSGC_HIGH_FREQUENCY_TIME_LIMIT),
352 PREF("gc_low_frequency_heap_growth", JSGC_LOW_FREQUENCY_HEAP_GROWTH),
353 PREF("gc_high_frequency_large_heap_growth",
354 JSGC_HIGH_FREQUENCY_LARGE_HEAP_GROWTH),
355 PREF("gc_high_frequency_small_heap_growth",
356 JSGC_HIGH_FREQUENCY_SMALL_HEAP_GROWTH),
357 PREF("gc_small_heap_size_max_mb", JSGC_SMALL_HEAP_SIZE_MAX),
358 PREF("gc_large_heap_size_min_mb", JSGC_LARGE_HEAP_SIZE_MIN),
359 PREF("gc_balanced_heap_limits", JSGC_BALANCED_HEAP_LIMITS_ENABLED),
360 PREF("gc_heap_growth_factor", JSGC_HEAP_GROWTH_FACTOR),
361 PREF("gc_allocation_threshold_mb", JSGC_ALLOCATION_THRESHOLD),
362 PREF("gc_malloc_threshold_base_mb", JSGC_MALLOC_THRESHOLD_BASE),
363 PREF("gc_small_heap_incremental_limit",
364 JSGC_SMALL_HEAP_INCREMENTAL_LIMIT),
365 PREF("gc_large_heap_incremental_limit",
366 JSGC_LARGE_HEAP_INCREMENTAL_LIMIT),
367 PREF("gc_urgent_threshold_mb", JSGC_URGENT_THRESHOLD_MB),
368 PREF("gc_incremental_slice_ms", JSGC_SLICE_TIME_BUDGET_MS),
369 PREF("gc_min_empty_chunk_count", JSGC_MIN_EMPTY_CHUNK_COUNT),
370 PREF("gc_max_empty_chunk_count", JSGC_MAX_EMPTY_CHUNK_COUNT),
371 PREF("gc_compacting", JSGC_COMPACTING_ENABLED),
372 PREF("gc_parallel_marking", JSGC_PARALLEL_MARKING_ENABLED),
373 PREF("gc_parallel_marking_threshold_mb",
374 JSGC_PARALLEL_MARKING_THRESHOLD_MB),
375 #ifdef NIGHTLY_BUILD
376 PREF("gc_experimental_semispace_nursery", JSGC_SEMISPACE_NURSERY_ENABLED),
377 #endif
378 // Note: Workers do not currently trigger eager minor GC, but if that is
379 // desired the following parameters should be added:
380 // javascript.options.mem.nursery_eager_collection_threshold_kb
381 // javascript.options.mem.nursery_eager_collection_threshold_percent
382 // javascript.options.mem.nursery_eager_collection_timeout_ms
384 #undef PREF
386 auto pref = kWorkerPrefs;
387 auto end = kWorkerPrefs + ArrayLength(kWorkerPrefs);
389 if (gRuntimeServiceDuringInit) {
390 // During init, we want to update every pref in kWorkerPrefs.
391 MOZ_ASSERT(memPrefName.IsEmpty(),
392 "Pref branch prefix only expected during init");
393 } else {
394 // Otherwise, find the single pref that changed.
395 while (pref != end) {
396 if (pref->memName == memPrefName) {
397 end = pref + 1;
398 break;
400 ++pref;
402 #ifdef DEBUG
403 if (pref == end) {
404 nsAutoCString message("Workers don't support the '");
405 message.Append(memPrefName);
406 message.AppendLiteral("' preference!");
407 NS_WARNING(message.get());
409 #endif
412 while (pref != end) {
413 switch (pref->key) {
414 case JSGC_MAX_BYTES: {
415 int32_t prefValue = GetPref(pref->fullName, -1);
416 Maybe<uint32_t> value = (prefValue <= 0 || prefValue >= 0x1000)
417 ? Nothing()
418 : Some(uint32_t(prefValue) * 1024 * 1024);
419 UpdateOtherJSGCMemoryOption(rts, pref->key, value);
420 break;
422 case JSGC_SLICE_TIME_BUDGET_MS: {
423 int32_t prefValue = GetPref(pref->fullName, -1);
424 Maybe<uint32_t> value = (prefValue <= 0 || prefValue >= 100000)
425 ? Nothing()
426 : Some(uint32_t(prefValue));
427 UpdateOtherJSGCMemoryOption(rts, pref->key, value);
428 break;
430 case JSGC_COMPACTING_ENABLED:
431 case JSGC_PARALLEL_MARKING_ENABLED:
432 #ifdef NIGHTLY_BUILD
433 case JSGC_SEMISPACE_NURSERY_ENABLED:
434 #endif
435 case JSGC_BALANCED_HEAP_LIMITS_ENABLED: {
436 bool present;
437 bool prefValue = GetPref(pref->fullName, false, &present);
438 Maybe<uint32_t> value = present ? Some(prefValue ? 1 : 0) : Nothing();
439 UpdateOtherJSGCMemoryOption(rts, pref->key, value);
440 break;
442 case JSGC_HIGH_FREQUENCY_TIME_LIMIT:
443 case JSGC_LOW_FREQUENCY_HEAP_GROWTH:
444 case JSGC_HIGH_FREQUENCY_LARGE_HEAP_GROWTH:
445 case JSGC_HIGH_FREQUENCY_SMALL_HEAP_GROWTH:
446 case JSGC_SMALL_HEAP_SIZE_MAX:
447 case JSGC_LARGE_HEAP_SIZE_MIN:
448 case JSGC_ALLOCATION_THRESHOLD:
449 case JSGC_MALLOC_THRESHOLD_BASE:
450 case JSGC_SMALL_HEAP_INCREMENTAL_LIMIT:
451 case JSGC_LARGE_HEAP_INCREMENTAL_LIMIT:
452 case JSGC_URGENT_THRESHOLD_MB:
453 case JSGC_MIN_EMPTY_CHUNK_COUNT:
454 case JSGC_MAX_EMPTY_CHUNK_COUNT:
455 case JSGC_HEAP_GROWTH_FACTOR:
456 case JSGC_PARALLEL_MARKING_THRESHOLD_MB:
457 UpdateCommonJSGCMemoryOption(rts, pref->fullName, pref->key);
458 break;
459 default:
460 MOZ_ASSERT_UNREACHABLE("Unknown JSGCParamKey value");
461 break;
463 ++pref;
467 bool InterruptCallback(JSContext* aCx) {
468 WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx);
469 MOZ_ASSERT(worker);
471 // Now is a good time to turn on profiling if it's pending.
472 PROFILER_JS_INTERRUPT_CALLBACK();
474 return worker->InterruptCallback(aCx);
477 class LogViolationDetailsRunnable final : public WorkerMainThreadRunnable {
478 uint16_t mViolationType;
479 nsString mFileName;
480 uint32_t mLineNum;
481 uint32_t mColumnNum;
482 nsString mScriptSample;
484 public:
485 LogViolationDetailsRunnable(WorkerPrivate* aWorker, uint16_t aViolationType,
486 const nsString& aFileName, uint32_t aLineNum,
487 uint32_t aColumnNum,
488 const nsAString& aScriptSample)
489 : WorkerMainThreadRunnable(aWorker,
490 "RuntimeService :: LogViolationDetails"_ns),
491 mViolationType(aViolationType),
492 mFileName(aFileName),
493 mLineNum(aLineNum),
494 mColumnNum(aColumnNum),
495 mScriptSample(aScriptSample) {
496 MOZ_ASSERT(aWorker);
499 virtual bool MainThreadRun() override;
501 private:
502 ~LogViolationDetailsRunnable() = default;
505 bool ContentSecurityPolicyAllows(JSContext* aCx, JS::RuntimeCode aKind,
506 JS::Handle<JSString*> aCode) {
507 WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx);
508 worker->AssertIsOnWorkerThread();
510 bool evalOK;
511 bool reportViolation;
512 uint16_t violationType;
513 nsAutoJSString scriptSample;
514 if (aKind == JS::RuntimeCode::JS) {
515 if (NS_WARN_IF(!scriptSample.init(aCx, aCode))) {
516 JS_ClearPendingException(aCx);
517 return false;
520 if (!nsContentSecurityUtils::IsEvalAllowed(
521 aCx, worker->UsesSystemPrincipal(), scriptSample)) {
522 return false;
525 evalOK = worker->IsEvalAllowed();
526 reportViolation = worker->GetReportEvalCSPViolations();
527 violationType = nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL;
528 } else {
529 evalOK = worker->IsWasmEvalAllowed();
530 reportViolation = worker->GetReportWasmEvalCSPViolations();
531 violationType = nsIContentSecurityPolicy::VIOLATION_TYPE_WASM_EVAL;
534 if (reportViolation) {
535 nsString fileName;
536 uint32_t lineNum = 0;
537 JS::ColumnNumberOneOrigin columnNum;
539 JS::AutoFilename file;
540 if (JS::DescribeScriptedCaller(aCx, &file, &lineNum, &columnNum) &&
541 file.get()) {
542 CopyUTF8toUTF16(MakeStringSpan(file.get()), fileName);
543 } else {
544 MOZ_ASSERT(!JS_IsExceptionPending(aCx));
547 RefPtr<LogViolationDetailsRunnable> runnable =
548 new LogViolationDetailsRunnable(worker, violationType, fileName,
549 lineNum, columnNum.oneOriginValue(),
550 scriptSample);
552 ErrorResult rv;
553 runnable->Dispatch(Killing, rv);
554 if (NS_WARN_IF(rv.Failed())) {
555 rv.SuppressException();
559 return evalOK;
562 void CTypesActivityCallback(JSContext* aCx, JS::CTypesActivityType aType) {
563 WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx);
564 worker->AssertIsOnWorkerThread();
566 switch (aType) {
567 case JS::CTypesActivityType::BeginCall:
568 worker->BeginCTypesCall();
569 break;
571 case JS::CTypesActivityType::EndCall:
572 worker->EndCTypesCall();
573 break;
575 case JS::CTypesActivityType::BeginCallback:
576 worker->BeginCTypesCallback();
577 break;
579 case JS::CTypesActivityType::EndCallback:
580 worker->EndCTypesCallback();
581 break;
583 default:
584 MOZ_CRASH("Unknown type flag!");
588 // JSDispatchableRunnables are WorkerRunnables used to dispatch JS::Dispatchable
589 // back to their worker thread. A WorkerRunnable is used for two reasons:
591 // 1. The JS::Dispatchable::run() callback may run JS so we cannot use a control
592 // runnable since they use async interrupts and break JS run-to-completion.
594 // 2. The DispatchToEventLoopCallback interface is *required* to fail during
595 // shutdown (see jsapi.h) which is exactly what WorkerRunnable::Dispatch() will
596 // do. Moreover, JS_DestroyContext() does *not* block on JS::Dispatchable::run
597 // being called, DispatchToEventLoopCallback failure is expected to happen
598 // during shutdown.
599 class JSDispatchableRunnable final : public WorkerRunnable {
600 JS::Dispatchable* mDispatchable;
602 ~JSDispatchableRunnable() { MOZ_ASSERT(!mDispatchable); }
604 // Disable the usual pre/post-dispatch thread assertions since we are
605 // dispatching from some random JS engine internal thread:
607 bool PreDispatch(WorkerPrivate* aWorkerPrivate) override { return true; }
609 void PostDispatch(WorkerPrivate* aWorkerPrivate,
610 bool aDispatchResult) override {
611 // For the benefit of the destructor assert.
612 if (!aDispatchResult) {
613 mDispatchable = nullptr;
617 public:
618 JSDispatchableRunnable(WorkerPrivate* aWorkerPrivate,
619 JS::Dispatchable* aDispatchable)
620 : WorkerRunnable(aWorkerPrivate, "JSDispatchableRunnable",
621 WorkerRunnable::WorkerThread),
622 mDispatchable(aDispatchable) {
623 MOZ_ASSERT(mDispatchable);
626 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
627 MOZ_ASSERT(aWorkerPrivate == mWorkerPrivate);
628 MOZ_ASSERT(aCx == mWorkerPrivate->GetJSContext());
629 MOZ_ASSERT(mDispatchable);
631 AutoJSAPI jsapi;
632 jsapi.Init();
634 mDispatchable->run(mWorkerPrivate->GetJSContext(),
635 JS::Dispatchable::NotShuttingDown);
636 mDispatchable = nullptr; // mDispatchable may delete itself
638 return true;
641 nsresult Cancel() override {
642 MOZ_ASSERT(mDispatchable);
644 AutoJSAPI jsapi;
645 jsapi.Init();
647 mDispatchable->run(mWorkerPrivate->GetJSContext(),
648 JS::Dispatchable::ShuttingDown);
649 mDispatchable = nullptr; // mDispatchable may delete itself
651 return NS_OK;
655 static bool DispatchToEventLoop(void* aClosure,
656 JS::Dispatchable* aDispatchable) {
657 // This callback may execute either on the worker thread or a random
658 // JS-internal helper thread.
660 // See comment at JS::InitDispatchToEventLoop() below for how we know the
661 // WorkerPrivate is alive.
662 WorkerPrivate* workerPrivate = reinterpret_cast<WorkerPrivate*>(aClosure);
664 // Dispatch is expected to fail during shutdown for the reasons outlined in
665 // the JSDispatchableRunnable comment above.
666 RefPtr<JSDispatchableRunnable> r =
667 new JSDispatchableRunnable(workerPrivate, aDispatchable);
668 return r->Dispatch();
671 static bool ConsumeStream(JSContext* aCx, JS::Handle<JSObject*> aObj,
672 JS::MimeType aMimeType,
673 JS::StreamConsumer* aConsumer) {
674 WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx);
675 if (!worker) {
676 JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr,
677 JSMSG_WASM_ERROR_CONSUMING_RESPONSE);
678 return false;
681 return FetchUtil::StreamResponseToJS(aCx, aObj, aMimeType, aConsumer, worker);
684 bool InitJSContextForWorker(WorkerPrivate* aWorkerPrivate,
685 JSContext* aWorkerCx) {
686 aWorkerPrivate->AssertIsOnWorkerThread();
687 NS_ASSERTION(!aWorkerPrivate->GetJSContext(), "Already has a context!");
689 JSSettings settings;
690 aWorkerPrivate->CopyJSSettings(settings);
692 JS::ContextOptionsRef(aWorkerCx) = settings.contextOptions;
694 // This is the real place where we set the max memory for the runtime.
695 for (const auto& setting : settings.gcSettings) {
696 if (setting.value) {
697 JS_SetGCParameter(aWorkerCx, setting.key, *setting.value);
698 } else {
699 JS_ResetGCParameter(aWorkerCx, setting.key);
703 JS_SetNativeStackQuota(aWorkerCx, WORKER_CONTEXT_NATIVE_STACK_LIMIT);
705 // Security policy:
706 static const JSSecurityCallbacks securityCallbacks = {
707 ContentSecurityPolicyAllows};
708 JS_SetSecurityCallbacks(aWorkerCx, &securityCallbacks);
710 // A WorkerPrivate lives strictly longer than its JSRuntime so we can safely
711 // store a raw pointer as the callback's closure argument on the JSRuntime.
712 JS::InitDispatchToEventLoop(aWorkerCx, DispatchToEventLoop,
713 (void*)aWorkerPrivate);
715 JS::InitConsumeStreamCallback(aWorkerCx, ConsumeStream,
716 FetchUtil::ReportJSStreamError);
718 // When available, set the self-hosted shared memory to be read, so that we
719 // can decode the self-hosted content instead of parsing it.
720 auto& shm = xpc::SelfHostedShmem::GetSingleton();
721 JS::SelfHostedCache selfHostedContent = shm.Content();
723 if (!JS::InitSelfHostedCode(aWorkerCx, selfHostedContent)) {
724 NS_WARNING("Could not init self-hosted code!");
725 return false;
728 JS_AddInterruptCallback(aWorkerCx, InterruptCallback);
730 JS::SetCTypesActivityCallback(aWorkerCx, CTypesActivityCallback);
732 #ifdef JS_GC_ZEAL
733 JS_SetGCZeal(aWorkerCx, settings.gcZeal, settings.gcZealFrequency);
734 #endif
736 return true;
739 static bool PreserveWrapper(JSContext* cx, JS::Handle<JSObject*> obj) {
740 MOZ_ASSERT(cx);
741 MOZ_ASSERT(obj);
742 MOZ_ASSERT(mozilla::dom::IsDOMObject(obj));
744 return mozilla::dom::TryPreserveWrapper(obj);
747 static bool IsWorkerDebuggerGlobalOrSandbox(JS::Handle<JSObject*> aGlobal) {
748 return IsWorkerDebuggerGlobal(aGlobal) || IsWorkerDebuggerSandbox(aGlobal);
751 JSObject* Wrap(JSContext* cx, JS::Handle<JSObject*> existing,
752 JS::Handle<JSObject*> obj) {
753 JS::Rooted<JSObject*> targetGlobal(cx, JS::CurrentGlobalOrNull(cx));
755 // Note: the JS engine unwraps CCWs before calling this callback.
756 JS::Rooted<JSObject*> originGlobal(cx, JS::GetNonCCWObjectGlobal(obj));
758 const js::Wrapper* wrapper = nullptr;
759 if (IsWorkerDebuggerGlobalOrSandbox(targetGlobal) &&
760 IsWorkerDebuggerGlobalOrSandbox(originGlobal)) {
761 wrapper = &js::CrossCompartmentWrapper::singleton;
762 } else {
763 wrapper = &js::OpaqueCrossCompartmentWrapper::singleton;
766 if (existing) {
767 js::Wrapper::Renew(existing, obj, wrapper);
769 return js::Wrapper::New(cx, obj, wrapper);
772 static const JSWrapObjectCallbacks WrapObjectCallbacks = {
773 Wrap,
774 nullptr,
777 class WorkerJSRuntime final : public mozilla::CycleCollectedJSRuntime {
778 public:
779 // The heap size passed here doesn't matter, we will change it later in the
780 // call to JS_SetGCParameter inside InitJSContextForWorker.
781 explicit WorkerJSRuntime(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
782 : CycleCollectedJSRuntime(aCx), mWorkerPrivate(aWorkerPrivate) {
783 MOZ_COUNT_CTOR_INHERITED(WorkerJSRuntime, CycleCollectedJSRuntime);
784 MOZ_ASSERT(aWorkerPrivate);
787 JS::UniqueChars defaultLocale = aWorkerPrivate->AdoptDefaultLocale();
788 MOZ_ASSERT(defaultLocale,
789 "failure of a WorkerPrivate to have a default locale should "
790 "have made the worker fail to spawn");
792 if (!JS_SetDefaultLocale(Runtime(), defaultLocale.get())) {
793 NS_WARNING("failed to set workerCx's default locale");
798 void Shutdown(JSContext* cx) override {
799 // The CC is shut down, and the superclass destructor will GC, so make sure
800 // we don't try to CC again.
801 mWorkerPrivate = nullptr;
803 CycleCollectedJSRuntime::Shutdown(cx);
806 ~WorkerJSRuntime() {
807 MOZ_COUNT_DTOR_INHERITED(WorkerJSRuntime, CycleCollectedJSRuntime);
810 virtual void PrepareForForgetSkippable() override {}
812 virtual void BeginCycleCollectionCallback(
813 mozilla::CCReason aReason) override {}
815 virtual void EndCycleCollectionCallback(
816 CycleCollectorResults& aResults) override {}
818 void DispatchDeferredDeletion(bool aContinuation, bool aPurge) override {
819 MOZ_ASSERT(!aContinuation);
821 // Do it immediately, no need for asynchronous behavior here.
822 nsCycleCollector_doDeferredDeletion();
825 virtual void CustomGCCallback(JSGCStatus aStatus) override {
826 if (!mWorkerPrivate) {
827 // We're shutting down, no need to do anything.
828 return;
831 mWorkerPrivate->AssertIsOnWorkerThread();
833 if (aStatus == JSGC_END) {
834 bool collectedAnything =
835 nsCycleCollector_collect(CCReason::GC_FINISHED, nullptr);
836 mWorkerPrivate->SetCCCollectedAnything(collectedAnything);
840 private:
841 WorkerPrivate* mWorkerPrivate;
844 } // anonymous namespace
846 } // namespace workerinternals
848 class WorkerJSContext final : public mozilla::CycleCollectedJSContext {
849 public:
850 // The heap size passed here doesn't matter, we will change it later in the
851 // call to JS_SetGCParameter inside InitJSContextForWorker.
852 explicit WorkerJSContext(WorkerPrivate* aWorkerPrivate)
853 : mWorkerPrivate(aWorkerPrivate) {
854 MOZ_COUNT_CTOR_INHERITED(WorkerJSContext, CycleCollectedJSContext);
855 MOZ_ASSERT(aWorkerPrivate);
856 // Magical number 2. Workers have the base recursion depth 1, and normal
857 // runnables run at level 2, and we don't want to process microtasks
858 // at any other level.
859 SetTargetedMicroTaskRecursionDepth(2);
862 // MOZ_CAN_RUN_SCRIPT_BOUNDARY because otherwise we have to annotate the
863 // SpiderMonkey JS::JobQueue's destructor as MOZ_CAN_RUN_SCRIPT, which is a
864 // bit of a pain.
865 MOZ_CAN_RUN_SCRIPT_BOUNDARY ~WorkerJSContext() {
866 MOZ_COUNT_DTOR_INHERITED(WorkerJSContext, CycleCollectedJSContext);
867 JSContext* cx = MaybeContext();
868 if (!cx) {
869 return; // Initialize() must have failed
872 // We expect to come here with the cycle collector already shut down.
873 // The superclass destructor will run the GC one final time and finalize any
874 // JSObjects that were participating in cycles that were broken during CC
875 // shutdown.
876 // Make sure we don't try to CC again.
877 mWorkerPrivate = nullptr;
880 WorkerJSContext* GetAsWorkerJSContext() override { return this; }
882 CycleCollectedJSRuntime* CreateRuntime(JSContext* aCx) override {
883 return new WorkerJSRuntime(aCx, mWorkerPrivate);
886 nsresult Initialize(JSRuntime* aParentRuntime) {
887 nsresult rv = CycleCollectedJSContext::Initialize(
888 aParentRuntime, WORKER_DEFAULT_RUNTIME_HEAPSIZE);
889 if (NS_WARN_IF(NS_FAILED(rv))) {
890 return rv;
893 JSContext* cx = Context();
895 js::SetPreserveWrapperCallbacks(cx, PreserveWrapper, HasReleasedWrapper);
896 JS_InitDestroyPrincipalsCallback(cx, nsJSPrincipals::Destroy);
897 JS_InitReadPrincipalsCallback(cx, nsJSPrincipals::ReadPrincipals);
898 JS_SetWrapObjectCallbacks(cx, &WrapObjectCallbacks);
899 if (mWorkerPrivate->IsDedicatedWorker()) {
900 JS_SetFutexCanWait(cx);
903 return NS_OK;
906 virtual void DispatchToMicroTask(
907 already_AddRefed<MicroTaskRunnable> aRunnable) override {
908 RefPtr<MicroTaskRunnable> runnable(aRunnable);
910 MOZ_ASSERT(!NS_IsMainThread());
911 MOZ_ASSERT(runnable);
913 std::deque<RefPtr<MicroTaskRunnable>>* microTaskQueue = nullptr;
915 JSContext* cx = Context();
916 NS_ASSERTION(cx, "This should never be null!");
918 JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
919 NS_ASSERTION(global, "This should never be null!");
921 // On worker threads, if the current global is the worker global or
922 // ShadowRealm global, we use the main micro task queue. Otherwise, the
923 // current global must be either the debugger global or a debugger sandbox,
924 // and we use the debugger micro task queue instead.
925 if (IsWorkerGlobal(global) || IsShadowRealmGlobal(global)) {
926 microTaskQueue = &GetMicroTaskQueue();
927 } else {
928 MOZ_ASSERT(IsWorkerDebuggerGlobal(global) ||
929 IsWorkerDebuggerSandbox(global));
931 microTaskQueue = &GetDebuggerMicroTaskQueue();
934 JS::JobQueueMayNotBeEmpty(cx);
935 microTaskQueue->push_back(std::move(runnable));
938 bool IsSystemCaller() const override {
939 return mWorkerPrivate->UsesSystemPrincipal();
942 void ReportError(JSErrorReport* aReport,
943 JS::ConstUTF8CharsZ aToStringResult) override {
944 mWorkerPrivate->ReportError(Context(), aToStringResult, aReport);
947 WorkerPrivate* GetWorkerPrivate() const { return mWorkerPrivate; }
949 private:
950 WorkerPrivate* mWorkerPrivate;
953 namespace workerinternals {
955 namespace {
957 class WorkerThreadPrimaryRunnable final : public Runnable {
958 WorkerPrivate* mWorkerPrivate;
959 SafeRefPtr<WorkerThread> mThread;
960 JSRuntime* mParentRuntime;
962 class FinishedRunnable final : public Runnable {
963 SafeRefPtr<WorkerThread> mThread;
965 public:
966 explicit FinishedRunnable(SafeRefPtr<WorkerThread> aThread)
967 : Runnable("WorkerThreadPrimaryRunnable::FinishedRunnable"),
968 mThread(std::move(aThread)) {
969 MOZ_ASSERT(mThread);
972 NS_INLINE_DECL_REFCOUNTING_INHERITED(FinishedRunnable, Runnable)
974 private:
975 ~FinishedRunnable() = default;
977 NS_DECL_NSIRUNNABLE
980 public:
981 WorkerThreadPrimaryRunnable(WorkerPrivate* aWorkerPrivate,
982 SafeRefPtr<WorkerThread> aThread,
983 JSRuntime* aParentRuntime)
984 : mozilla::Runnable("WorkerThreadPrimaryRunnable"),
985 mWorkerPrivate(aWorkerPrivate),
986 mThread(std::move(aThread)),
987 mParentRuntime(aParentRuntime) {
988 MOZ_ASSERT(aWorkerPrivate);
989 MOZ_ASSERT(mThread);
992 NS_INLINE_DECL_REFCOUNTING_INHERITED(WorkerThreadPrimaryRunnable, Runnable)
994 private:
995 ~WorkerThreadPrimaryRunnable() = default;
997 NS_DECL_NSIRUNNABLE
1000 void PrefLanguagesChanged(const char* /* aPrefName */, void* /* aClosure */) {
1001 AssertIsOnMainThread();
1003 nsTArray<nsString> languages;
1004 Navigator::GetAcceptLanguages(languages);
1006 RuntimeService* runtime = RuntimeService::GetService();
1007 if (runtime) {
1008 runtime->UpdateAllWorkerLanguages(languages);
1012 void AppVersionOverrideChanged(const char* /* aPrefName */,
1013 void* /* aClosure */) {
1014 AssertIsOnMainThread();
1016 nsAutoString override;
1017 Preferences::GetString("general.appversion.override", override);
1019 RuntimeService* runtime = RuntimeService::GetService();
1020 if (runtime) {
1021 runtime->UpdateAppVersionOverridePreference(override);
1025 void PlatformOverrideChanged(const char* /* aPrefName */,
1026 void* /* aClosure */) {
1027 AssertIsOnMainThread();
1029 nsAutoString override;
1030 Preferences::GetString("general.platform.override", override);
1032 RuntimeService* runtime = RuntimeService::GetService();
1033 if (runtime) {
1034 runtime->UpdatePlatformOverridePreference(override);
1038 } /* anonymous namespace */
1040 // This is only touched on the main thread. Initialized in Init() below.
1041 StaticAutoPtr<JSSettings> RuntimeService::sDefaultJSSettings;
1043 RuntimeService::RuntimeService()
1044 : mMutex("RuntimeService::mMutex"),
1045 mObserved(false),
1046 mShuttingDown(false),
1047 mNavigatorPropertiesLoaded(false) {
1048 AssertIsOnMainThread();
1049 MOZ_ASSERT(!GetService(), "More than one service!");
1052 RuntimeService::~RuntimeService() {
1053 AssertIsOnMainThread();
1055 // gRuntimeService can be null if Init() fails.
1056 MOZ_ASSERT(!GetService() || GetService() == this, "More than one service!");
1058 gRuntimeService = nullptr;
1061 // static
1062 RuntimeService* RuntimeService::GetOrCreateService() {
1063 AssertIsOnMainThread();
1065 if (!gRuntimeService) {
1066 // The observer service now owns us until shutdown.
1067 gRuntimeService = new RuntimeService();
1068 if (NS_FAILED((*gRuntimeService).Init())) {
1069 NS_WARNING("Failed to initialize!");
1070 (*gRuntimeService).Cleanup();
1071 gRuntimeService = nullptr;
1072 return nullptr;
1076 return gRuntimeService;
1079 // static
1080 RuntimeService* RuntimeService::GetService() { return gRuntimeService; }
1082 bool RuntimeService::RegisterWorker(WorkerPrivate& aWorkerPrivate) {
1083 aWorkerPrivate.AssertIsOnParentThread();
1085 WorkerPrivate* parent = aWorkerPrivate.GetParent();
1086 if (!parent) {
1087 AssertIsOnMainThread();
1089 if (mShuttingDown) {
1090 return false;
1094 const bool isServiceWorker = aWorkerPrivate.IsServiceWorker();
1095 const bool isSharedWorker = aWorkerPrivate.IsSharedWorker();
1096 const bool isDedicatedWorker = aWorkerPrivate.IsDedicatedWorker();
1097 if (isServiceWorker) {
1098 AssertIsOnMainThread();
1099 Telemetry::Accumulate(Telemetry::SERVICE_WORKER_SPAWN_ATTEMPTS, 1);
1102 nsCString sharedWorkerScriptSpec;
1103 if (isSharedWorker) {
1104 AssertIsOnMainThread();
1106 nsCOMPtr<nsIURI> scriptURI = aWorkerPrivate.GetResolvedScriptURI();
1107 NS_ASSERTION(scriptURI, "Null script URI!");
1109 nsresult rv = scriptURI->GetSpec(sharedWorkerScriptSpec);
1110 if (NS_FAILED(rv)) {
1111 NS_WARNING("GetSpec failed?!");
1112 return false;
1115 NS_ASSERTION(!sharedWorkerScriptSpec.IsEmpty(), "Empty spec!");
1118 bool exemptFromPerDomainMax = false;
1119 if (isServiceWorker) {
1120 AssertIsOnMainThread();
1121 exemptFromPerDomainMax = Preferences::GetBool(
1122 "dom.serviceWorkers.exemptFromPerDomainMax", false);
1125 const nsCString& domain = aWorkerPrivate.Domain();
1127 bool queued = false;
1129 MutexAutoLock lock(mMutex);
1131 auto* const domainInfo =
1132 mDomainMap
1133 .LookupOrInsertWith(
1134 domain,
1135 [&domain, parent] {
1136 NS_ASSERTION(!parent, "Shouldn't have a parent here!");
1137 Unused << parent; // silence clang -Wunused-lambda-capture in
1138 // opt builds
1139 auto wdi = MakeUnique<WorkerDomainInfo>();
1140 wdi->mDomain = domain;
1141 return wdi;
1143 .get();
1145 queued = gMaxWorkersPerDomain &&
1146 domainInfo->ActiveWorkerCount() >= gMaxWorkersPerDomain &&
1147 !domain.IsEmpty() && !exemptFromPerDomainMax;
1149 if (queued) {
1150 domainInfo->mQueuedWorkers.AppendElement(&aWorkerPrivate);
1152 // Worker spawn gets queued due to hitting max workers per domain
1153 // limit so let's log a warning.
1154 WorkerPrivate::ReportErrorToConsole("HittingMaxWorkersPerDomain2");
1156 if (isServiceWorker) {
1157 Telemetry::Accumulate(Telemetry::SERVICE_WORKER_SPAWN_GETS_QUEUED, 1);
1158 } else if (isSharedWorker) {
1159 Telemetry::Accumulate(Telemetry::SHARED_WORKER_SPAWN_GETS_QUEUED, 1);
1160 } else if (isDedicatedWorker) {
1161 Telemetry::Accumulate(Telemetry::DEDICATED_WORKER_SPAWN_GETS_QUEUED, 1);
1163 } else if (parent) {
1164 domainInfo->mChildWorkerCount++;
1165 } else if (isServiceWorker) {
1166 domainInfo->mActiveServiceWorkers.AppendElement(&aWorkerPrivate);
1167 } else {
1168 domainInfo->mActiveWorkers.AppendElement(&aWorkerPrivate);
1172 // From here on out we must call UnregisterWorker if something fails!
1173 if (parent) {
1174 if (!parent->AddChildWorker(aWorkerPrivate)) {
1175 UnregisterWorker(aWorkerPrivate);
1176 return false;
1178 } else {
1179 if (!mNavigatorPropertiesLoaded) {
1180 if (NS_FAILED(Navigator::GetAppVersion(
1181 mNavigatorProperties.mAppVersion, aWorkerPrivate.GetDocument(),
1182 false /* aUsePrefOverriddenValue */)) ||
1183 NS_FAILED(Navigator::GetPlatform(
1184 mNavigatorProperties.mPlatform, aWorkerPrivate.GetDocument(),
1185 false /* aUsePrefOverriddenValue */))) {
1186 UnregisterWorker(aWorkerPrivate);
1187 return false;
1190 // The navigator overridden properties should have already been read.
1192 Navigator::GetAcceptLanguages(mNavigatorProperties.mLanguages);
1193 mNavigatorPropertiesLoaded = true;
1196 nsPIDOMWindowInner* window = aWorkerPrivate.GetWindow();
1198 if (!isServiceWorker) {
1199 // Service workers are excluded since their lifetime is separate from
1200 // that of dom windows.
1201 if (auto* const windowArray = mWindowMap.GetOrInsertNew(window, 1);
1202 !windowArray->Contains(&aWorkerPrivate)) {
1203 windowArray->AppendElement(&aWorkerPrivate);
1204 } else {
1205 MOZ_ASSERT(aWorkerPrivate.IsSharedWorker());
1210 if (!queued && !ScheduleWorker(aWorkerPrivate)) {
1211 return false;
1214 if (isServiceWorker) {
1215 AssertIsOnMainThread();
1216 Telemetry::Accumulate(Telemetry::SERVICE_WORKER_WAS_SPAWNED, 1);
1218 return true;
1221 void RuntimeService::UnregisterWorker(WorkerPrivate& aWorkerPrivate) {
1222 aWorkerPrivate.AssertIsOnParentThread();
1224 WorkerPrivate* parent = aWorkerPrivate.GetParent();
1225 if (!parent) {
1226 AssertIsOnMainThread();
1229 const nsCString& domain = aWorkerPrivate.Domain();
1231 WorkerPrivate* queuedWorker = nullptr;
1233 MutexAutoLock lock(mMutex);
1235 WorkerDomainInfo* domainInfo;
1236 if (!mDomainMap.Get(domain, &domainInfo)) {
1237 NS_ERROR("Don't have an entry for this domain!");
1240 // Remove old worker from everywhere.
1241 uint32_t index = domainInfo->mQueuedWorkers.IndexOf(&aWorkerPrivate);
1242 if (index != kNoIndex) {
1243 // Was queued, remove from the list.
1244 domainInfo->mQueuedWorkers.RemoveElementAt(index);
1245 } else if (parent) {
1246 MOZ_ASSERT(domainInfo->mChildWorkerCount, "Must be non-zero!");
1247 domainInfo->mChildWorkerCount--;
1248 } else if (aWorkerPrivate.IsServiceWorker()) {
1249 MOZ_ASSERT(domainInfo->mActiveServiceWorkers.Contains(&aWorkerPrivate),
1250 "Don't know about this worker!");
1251 domainInfo->mActiveServiceWorkers.RemoveElement(&aWorkerPrivate);
1252 } else {
1253 MOZ_ASSERT(domainInfo->mActiveWorkers.Contains(&aWorkerPrivate),
1254 "Don't know about this worker!");
1255 domainInfo->mActiveWorkers.RemoveElement(&aWorkerPrivate);
1258 // See if there's a queued worker we can schedule.
1259 if (domainInfo->ActiveWorkerCount() < gMaxWorkersPerDomain &&
1260 !domainInfo->mQueuedWorkers.IsEmpty()) {
1261 queuedWorker = domainInfo->mQueuedWorkers[0];
1262 domainInfo->mQueuedWorkers.RemoveElementAt(0);
1264 if (queuedWorker->GetParent()) {
1265 domainInfo->mChildWorkerCount++;
1266 } else if (queuedWorker->IsServiceWorker()) {
1267 domainInfo->mActiveServiceWorkers.AppendElement(queuedWorker);
1268 } else {
1269 domainInfo->mActiveWorkers.AppendElement(queuedWorker);
1273 if (domainInfo->HasNoWorkers()) {
1274 MOZ_ASSERT(domainInfo->mQueuedWorkers.IsEmpty());
1275 mDomainMap.Remove(domain);
1279 // NB: For Shared Workers we used to call ShutdownOnMainThread on the
1280 // RemoteWorkerController; however, that was redundant because
1281 // RemoteWorkerChild uses a WeakWorkerRef which notifies at about the
1282 // same time as us calling into the code here and would race with us.
1284 if (parent) {
1285 parent->RemoveChildWorker(aWorkerPrivate);
1286 } else if (aWorkerPrivate.IsSharedWorker()) {
1287 AssertIsOnMainThread();
1289 mWindowMap.RemoveIf([&aWorkerPrivate](const auto& iter) {
1290 const auto& workers = iter.Data();
1291 MOZ_ASSERT(workers);
1293 if (workers->RemoveElement(&aWorkerPrivate)) {
1294 MOZ_ASSERT(!workers->Contains(&aWorkerPrivate),
1295 "Added worker more than once!");
1297 return workers->IsEmpty();
1300 return false;
1302 } else if (aWorkerPrivate.IsDedicatedWorker()) {
1303 // May be null.
1304 nsPIDOMWindowInner* window = aWorkerPrivate.GetWindow();
1305 if (auto entry = mWindowMap.Lookup(window)) {
1306 MOZ_ALWAYS_TRUE(entry.Data()->RemoveElement(&aWorkerPrivate));
1307 if (entry.Data()->IsEmpty()) {
1308 entry.Remove();
1310 } else {
1311 MOZ_ASSERT_UNREACHABLE("window is not in mWindowMap");
1315 if (queuedWorker && !ScheduleWorker(*queuedWorker)) {
1316 UnregisterWorker(*queuedWorker);
1320 bool RuntimeService::ScheduleWorker(WorkerPrivate& aWorkerPrivate) {
1321 if (!aWorkerPrivate.Start()) {
1322 // This is ok, means that we didn't need to make a thread for this worker.
1323 return true;
1326 const WorkerThreadFriendKey friendKey;
1328 SafeRefPtr<WorkerThread> thread = WorkerThread::Create(friendKey);
1329 if (!thread) {
1330 UnregisterWorker(aWorkerPrivate);
1331 return false;
1334 if (NS_FAILED(thread->SetPriority(nsISupportsPriority::PRIORITY_NORMAL))) {
1335 NS_WARNING("Could not set the thread's priority!");
1338 aWorkerPrivate.SetThread(thread.unsafeGetRawPtr());
1339 JSContext* cx = CycleCollectedJSContext::Get()->Context();
1340 nsCOMPtr<nsIRunnable> runnable = new WorkerThreadPrimaryRunnable(
1341 &aWorkerPrivate, thread.clonePtr(), JS_GetParentRuntime(cx));
1342 if (NS_FAILED(
1343 thread->DispatchPrimaryRunnable(friendKey, runnable.forget()))) {
1344 UnregisterWorker(aWorkerPrivate);
1345 return false;
1348 return true;
1351 nsresult RuntimeService::Init() {
1352 AssertIsOnMainThread();
1354 nsLayoutStatics::AddRef();
1356 // Initialize JSSettings.
1357 sDefaultJSSettings = new JSSettings();
1358 SetDefaultJSGCSettings(JSGC_MAX_BYTES, Some(WORKER_DEFAULT_RUNTIME_HEAPSIZE));
1359 SetDefaultJSGCSettings(JSGC_ALLOCATION_THRESHOLD,
1360 Some(WORKER_DEFAULT_ALLOCATION_THRESHOLD));
1362 // nsIStreamTransportService is thread-safe but it must be initialized on the
1363 // main-thread. FileReader needs it, so, let's initialize it now.
1364 nsresult rv;
1365 nsCOMPtr<nsIStreamTransportService> sts =
1366 do_GetService(kStreamTransportServiceCID, &rv);
1367 NS_ENSURE_TRUE(sts, NS_ERROR_FAILURE);
1369 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
1370 NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE);
1372 rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, false);
1373 NS_ENSURE_SUCCESS(rv, rv);
1375 rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
1376 NS_ENSURE_SUCCESS(rv, rv);
1378 mObserved = true;
1380 if (NS_FAILED(obs->AddObserver(this, GC_REQUEST_OBSERVER_TOPIC, false))) {
1381 NS_WARNING("Failed to register for GC request notifications!");
1384 if (NS_FAILED(obs->AddObserver(this, CC_REQUEST_OBSERVER_TOPIC, false))) {
1385 NS_WARNING("Failed to register for CC request notifications!");
1388 if (NS_FAILED(
1389 obs->AddObserver(this, MEMORY_PRESSURE_OBSERVER_TOPIC, false))) {
1390 NS_WARNING("Failed to register for memory pressure notifications!");
1393 if (NS_FAILED(
1394 obs->AddObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC, false))) {
1395 NS_WARNING("Failed to register for offline notification event!");
1398 MOZ_ASSERT(!gRuntimeServiceDuringInit, "This should be false!");
1399 gRuntimeServiceDuringInit = true;
1401 #define WORKER_PREF(name, callback) \
1402 NS_FAILED(Preferences::RegisterCallbackAndCall(callback, name))
1404 if (NS_FAILED(Preferences::RegisterPrefixCallbackAndCall(
1405 LoadJSGCMemoryOptions,
1406 PREF_JS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX)) ||
1407 #ifdef JS_GC_ZEAL
1408 NS_FAILED(Preferences::RegisterCallback(
1409 LoadGCZealOptions, PREF_JS_OPTIONS_PREFIX PREF_GCZEAL)) ||
1410 #endif
1411 WORKER_PREF("intl.accept_languages", PrefLanguagesChanged) ||
1412 WORKER_PREF("general.appversion.override", AppVersionOverrideChanged) ||
1413 WORKER_PREF("general.platform.override", PlatformOverrideChanged) ||
1414 NS_FAILED(Preferences::RegisterPrefixCallbackAndCall(
1415 LoadContextOptions, PREF_JS_OPTIONS_PREFIX))) {
1416 NS_WARNING("Failed to register pref callbacks!");
1419 #undef WORKER_PREF
1421 MOZ_ASSERT(gRuntimeServiceDuringInit, "Should be true!");
1422 gRuntimeServiceDuringInit = false;
1424 int32_t maxPerDomain =
1425 Preferences::GetInt(PREF_WORKERS_MAX_PER_DOMAIN, MAX_WORKERS_PER_DOMAIN);
1426 gMaxWorkersPerDomain = std::max(0, maxPerDomain);
1428 IndexedDatabaseManager* idm = IndexedDatabaseManager::GetOrCreate();
1429 if (NS_WARN_IF(!idm)) {
1430 return NS_ERROR_UNEXPECTED;
1433 rv = idm->EnsureLocale();
1434 if (NS_WARN_IF(NS_FAILED(rv))) {
1435 return rv;
1438 // PerformanceService must be initialized on the main-thread.
1439 PerformanceService::GetOrCreate();
1441 return NS_OK;
1444 void RuntimeService::Shutdown() {
1445 AssertIsOnMainThread();
1447 MOZ_ASSERT(!mShuttingDown);
1448 // That's it, no more workers.
1449 mShuttingDown = true;
1451 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
1452 NS_WARNING_ASSERTION(obs, "Failed to get observer service?!");
1454 // Tell anyone that cares that they're about to lose worker support.
1455 if (obs && NS_FAILED(obs->NotifyObservers(nullptr, WORKERS_SHUTDOWN_TOPIC,
1456 nullptr))) {
1457 NS_WARNING("NotifyObservers failed!");
1461 AutoTArray<WorkerPrivate*, 100> workers;
1464 MutexAutoLock lock(mMutex);
1466 AddAllTopLevelWorkersToArray(workers);
1469 // Cancel all top-level workers.
1470 for (const auto& worker : workers) {
1471 if (!worker->Cancel()) {
1472 NS_WARNING("Failed to cancel worker!");
1477 sDefaultJSSettings = nullptr;
1480 namespace {
1482 class DumpCrashInfoRunnable final : public WorkerControlRunnable {
1483 public:
1484 explicit DumpCrashInfoRunnable(WorkerPrivate* aWorkerPrivate)
1485 : WorkerControlRunnable(aWorkerPrivate, "DumpCrashInfoRunnable",
1486 WorkerThread),
1487 mMonitor("DumpCrashInfoRunnable::mMonitor") {}
1489 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
1490 MonitorAutoLock lock(mMonitor);
1491 if (!mHasMsg) {
1492 aWorkerPrivate->DumpCrashInformation(mMsg);
1493 mHasMsg.Flip();
1495 lock.Notify();
1496 return true;
1499 nsresult Cancel() override {
1500 MonitorAutoLock lock(mMonitor);
1501 if (!mHasMsg) {
1502 mMsg.Assign("Canceled");
1503 mHasMsg.Flip();
1505 lock.Notify();
1507 return NS_OK;
1510 bool DispatchAndWait() {
1511 MonitorAutoLock lock(mMonitor);
1513 if (!Dispatch()) {
1514 // The worker is already dead but the main thread still didn't remove it
1515 // from RuntimeService's registry.
1516 return false;
1519 // To avoid any possibility of process hangs we never receive reports on
1520 // we give the worker 1sec to react.
1521 lock.Wait(TimeDuration::FromMilliseconds(1000));
1522 if (!mHasMsg) {
1523 mMsg.Append("NoResponse");
1524 mHasMsg.Flip();
1526 return true;
1529 const nsCString& MsgData() const { return mMsg; }
1531 private:
1532 bool PreDispatch(WorkerPrivate* aWorkerPrivate) override { return true; }
1534 void PostDispatch(WorkerPrivate* aWorkerPrivate,
1535 bool aDispatchResult) override {}
1537 Monitor mMonitor MOZ_UNANNOTATED;
1538 nsCString mMsg;
1539 FlippedOnce<false> mHasMsg;
1542 struct ActiveWorkerStats {
1543 template <uint32_t ActiveWorkerStats::*Category>
1544 void Update(const nsTArray<WorkerPrivate*>& aWorkers) {
1545 for (const auto worker : aWorkers) {
1546 RefPtr<DumpCrashInfoRunnable> runnable =
1547 new DumpCrashInfoRunnable(worker);
1548 if (runnable->DispatchAndWait()) {
1549 ++(this->*Category);
1550 mMessage.Append(runnable->MsgData());
1555 uint32_t mWorkers = 0;
1556 uint32_t mServiceWorkers = 0;
1557 nsCString mMessage;
1560 } // namespace
1562 void RuntimeService::CrashIfHanging() {
1563 MutexAutoLock lock(mMutex);
1565 // If we never wanted to shut down we cannot hang.
1566 if (!mShuttingDown) {
1567 return;
1570 ActiveWorkerStats activeStats;
1571 uint32_t inactiveWorkers = 0;
1573 for (const auto& aData : mDomainMap.Values()) {
1574 activeStats.Update<&ActiveWorkerStats::mWorkers>(aData->mActiveWorkers);
1575 activeStats.Update<&ActiveWorkerStats::mServiceWorkers>(
1576 aData->mActiveServiceWorkers);
1578 // These might not be top-level workers...
1579 inactiveWorkers += std::count_if(
1580 aData->mQueuedWorkers.begin(), aData->mQueuedWorkers.end(),
1581 [](const auto* const worker) { return !worker->GetParent(); });
1584 if (activeStats.mWorkers + activeStats.mServiceWorkers + inactiveWorkers ==
1585 0) {
1586 return;
1589 nsCString msg;
1591 // A: active Workers | S: active ServiceWorkers | Q: queued Workers
1592 msg.AppendPrintf("Workers Hanging - %d|A:%d|S:%d|Q:%d", mShuttingDown ? 1 : 0,
1593 activeStats.mWorkers, activeStats.mServiceWorkers,
1594 inactiveWorkers);
1595 msg.Append(activeStats.mMessage);
1597 // This string will be leaked.
1598 MOZ_CRASH_UNSAFE(strdup(msg.BeginReading()));
1601 // This spins the event loop until all workers are finished and their threads
1602 // have been joined.
1603 void RuntimeService::Cleanup() {
1604 AssertIsOnMainThread();
1606 if (!mShuttingDown) {
1607 Shutdown();
1610 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
1611 NS_WARNING_ASSERTION(obs, "Failed to get observer service?!");
1614 MutexAutoLock lock(mMutex);
1616 AutoTArray<WorkerPrivate*, 100> workers;
1617 AddAllTopLevelWorkersToArray(workers);
1619 if (!workers.IsEmpty()) {
1620 nsIThread* currentThread = NS_GetCurrentThread();
1621 NS_ASSERTION(currentThread, "This should never be null!");
1623 // If the loop below takes too long, we probably have a problematic
1624 // worker. MOZ_LOG some info before the parent process forcibly
1625 // terminates us so that in the event we are a content process, the log
1626 // output can provide useful context about the workers that did not
1627 // cleanly shut down.
1628 nsCOMPtr<nsITimer> timer;
1629 RefPtr<RuntimeService> self = this;
1630 nsresult rv = NS_NewTimerWithCallback(
1631 getter_AddRefs(timer),
1632 [self](nsITimer*) { self->DumpRunningWorkers(); },
1633 TimeDuration::FromSeconds(1), nsITimer::TYPE_ONE_SHOT,
1634 "RuntimeService::WorkerShutdownDump");
1635 Unused << NS_WARN_IF(NS_FAILED(rv));
1637 // And make sure all their final messages have run and all their threads
1638 // have joined.
1639 while (mDomainMap.Count()) {
1640 MutexAutoUnlock unlock(mMutex);
1642 if (!NS_ProcessNextEvent(currentThread)) {
1643 NS_WARNING("Something bad happened!");
1644 break;
1648 if (NS_SUCCEEDED(rv)) {
1649 timer->Cancel();
1654 NS_ASSERTION(!mWindowMap.Count(), "All windows should have been released!");
1656 #define WORKER_PREF(name, callback) \
1657 NS_FAILED(Preferences::UnregisterCallback(callback, name))
1659 if (mObserved) {
1660 if (NS_FAILED(Preferences::UnregisterPrefixCallback(
1661 LoadContextOptions, PREF_JS_OPTIONS_PREFIX)) ||
1662 WORKER_PREF("intl.accept_languages", PrefLanguagesChanged) ||
1663 WORKER_PREF("general.appversion.override", AppVersionOverrideChanged) ||
1664 WORKER_PREF("general.platform.override", PlatformOverrideChanged) ||
1665 #ifdef JS_GC_ZEAL
1666 NS_FAILED(Preferences::UnregisterCallback(
1667 LoadGCZealOptions, PREF_JS_OPTIONS_PREFIX PREF_GCZEAL)) ||
1668 #endif
1669 NS_FAILED(Preferences::UnregisterPrefixCallback(
1670 LoadJSGCMemoryOptions,
1671 PREF_JS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX))) {
1672 NS_WARNING("Failed to unregister pref callbacks!");
1675 #undef WORKER_PREF
1677 if (obs) {
1678 if (NS_FAILED(obs->RemoveObserver(this, GC_REQUEST_OBSERVER_TOPIC))) {
1679 NS_WARNING("Failed to unregister for GC request notifications!");
1682 if (NS_FAILED(obs->RemoveObserver(this, CC_REQUEST_OBSERVER_TOPIC))) {
1683 NS_WARNING("Failed to unregister for CC request notifications!");
1686 if (NS_FAILED(
1687 obs->RemoveObserver(this, MEMORY_PRESSURE_OBSERVER_TOPIC))) {
1688 NS_WARNING("Failed to unregister for memory pressure notifications!");
1691 if (NS_FAILED(
1692 obs->RemoveObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC))) {
1693 NS_WARNING("Failed to unregister for offline notification event!");
1695 obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID);
1696 obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
1697 mObserved = false;
1701 nsLayoutStatics::Release();
1704 void RuntimeService::AddAllTopLevelWorkersToArray(
1705 nsTArray<WorkerPrivate*>& aWorkers) {
1706 for (const auto& aData : mDomainMap.Values()) {
1707 #ifdef DEBUG
1708 for (const auto& activeWorker : aData->mActiveWorkers) {
1709 MOZ_ASSERT(!activeWorker->GetParent(),
1710 "Shouldn't have a parent in this list!");
1712 for (const auto& activeServiceWorker : aData->mActiveServiceWorkers) {
1713 MOZ_ASSERT(!activeServiceWorker->GetParent(),
1714 "Shouldn't have a parent in this list!");
1716 #endif
1718 aWorkers.AppendElements(aData->mActiveWorkers);
1719 aWorkers.AppendElements(aData->mActiveServiceWorkers);
1721 // These might not be top-level workers...
1722 std::copy_if(aData->mQueuedWorkers.begin(), aData->mQueuedWorkers.end(),
1723 MakeBackInserter(aWorkers),
1724 [](const auto& worker) { return !worker->GetParent(); });
1728 nsTArray<WorkerPrivate*> RuntimeService::GetWorkersForWindow(
1729 const nsPIDOMWindowInner& aWindow) const {
1730 AssertIsOnMainThread();
1732 nsTArray<WorkerPrivate*> result;
1733 if (nsTArray<WorkerPrivate*>* const workers = mWindowMap.Get(&aWindow)) {
1734 NS_ASSERTION(!workers->IsEmpty(), "Should have been removed!");
1735 result.AppendElements(*workers);
1737 return result;
1740 void RuntimeService::CancelWorkersForWindow(const nsPIDOMWindowInner& aWindow) {
1741 AssertIsOnMainThread();
1743 for (WorkerPrivate* const worker : GetWorkersForWindow(aWindow)) {
1744 MOZ_ASSERT(!worker->IsSharedWorker());
1745 worker->Cancel();
1749 void RuntimeService::FreezeWorkersForWindow(const nsPIDOMWindowInner& aWindow) {
1750 AssertIsOnMainThread();
1752 for (WorkerPrivate* const worker : GetWorkersForWindow(aWindow)) {
1753 MOZ_ASSERT(!worker->IsSharedWorker());
1754 worker->Freeze(&aWindow);
1758 void RuntimeService::ThawWorkersForWindow(const nsPIDOMWindowInner& aWindow) {
1759 AssertIsOnMainThread();
1761 for (WorkerPrivate* const worker : GetWorkersForWindow(aWindow)) {
1762 MOZ_ASSERT(!worker->IsSharedWorker());
1763 worker->Thaw(&aWindow);
1767 void RuntimeService::SuspendWorkersForWindow(
1768 const nsPIDOMWindowInner& aWindow) {
1769 AssertIsOnMainThread();
1771 for (WorkerPrivate* const worker : GetWorkersForWindow(aWindow)) {
1772 MOZ_ASSERT(!worker->IsSharedWorker());
1773 worker->ParentWindowPaused();
1777 void RuntimeService::ResumeWorkersForWindow(const nsPIDOMWindowInner& aWindow) {
1778 AssertIsOnMainThread();
1780 for (WorkerPrivate* const worker : GetWorkersForWindow(aWindow)) {
1781 MOZ_ASSERT(!worker->IsSharedWorker());
1782 worker->ParentWindowResumed();
1786 void RuntimeService::PropagateStorageAccessPermissionGranted(
1787 const nsPIDOMWindowInner& aWindow) {
1788 AssertIsOnMainThread();
1789 MOZ_ASSERT_IF(aWindow.GetExtantDoc(), aWindow.GetExtantDoc()
1790 ->CookieJarSettings()
1791 ->GetRejectThirdPartyContexts());
1793 for (WorkerPrivate* const worker : GetWorkersForWindow(aWindow)) {
1794 worker->PropagateStorageAccessPermissionGranted();
1798 template <typename Func>
1799 void RuntimeService::BroadcastAllWorkers(const Func& aFunc) {
1800 AssertIsOnMainThread();
1802 AutoTArray<WorkerPrivate*, 100> workers;
1804 MutexAutoLock lock(mMutex);
1806 AddAllTopLevelWorkersToArray(workers);
1809 for (const auto& worker : workers) {
1810 aFunc(*worker);
1814 void RuntimeService::UpdateAllWorkerContextOptions() {
1815 BroadcastAllWorkers([](auto& worker) {
1816 worker.UpdateContextOptions(sDefaultJSSettings->contextOptions);
1820 void RuntimeService::UpdateAppVersionOverridePreference(
1821 const nsAString& aValue) {
1822 AssertIsOnMainThread();
1823 mNavigatorProperties.mAppVersionOverridden = aValue;
1826 void RuntimeService::UpdatePlatformOverridePreference(const nsAString& aValue) {
1827 AssertIsOnMainThread();
1828 mNavigatorProperties.mPlatformOverridden = aValue;
1831 void RuntimeService::UpdateAllWorkerLanguages(
1832 const nsTArray<nsString>& aLanguages) {
1833 MOZ_ASSERT(NS_IsMainThread());
1835 mNavigatorProperties.mLanguages = aLanguages.Clone();
1836 BroadcastAllWorkers(
1837 [&aLanguages](auto& worker) { worker.UpdateLanguages(aLanguages); });
1840 void RuntimeService::UpdateAllWorkerMemoryParameter(JSGCParamKey aKey,
1841 Maybe<uint32_t> aValue) {
1842 BroadcastAllWorkers([aKey, aValue](auto& worker) {
1843 worker.UpdateJSWorkerMemoryParameter(aKey, aValue);
1847 #ifdef JS_GC_ZEAL
1848 void RuntimeService::UpdateAllWorkerGCZeal() {
1849 BroadcastAllWorkers([](auto& worker) {
1850 worker.UpdateGCZeal(sDefaultJSSettings->gcZeal,
1851 sDefaultJSSettings->gcZealFrequency);
1854 #endif
1856 void RuntimeService::SetLowMemoryStateAllWorkers(bool aState) {
1857 BroadcastAllWorkers(
1858 [aState](auto& worker) { worker.SetLowMemoryState(aState); });
1861 void RuntimeService::GarbageCollectAllWorkers(bool aShrinking) {
1862 BroadcastAllWorkers(
1863 [aShrinking](auto& worker) { worker.GarbageCollect(aShrinking); });
1866 void RuntimeService::CycleCollectAllWorkers() {
1867 BroadcastAllWorkers([](auto& worker) { worker.CycleCollect(); });
1870 void RuntimeService::SendOfflineStatusChangeEventToAllWorkers(bool aIsOffline) {
1871 BroadcastAllWorkers([aIsOffline](auto& worker) {
1872 worker.OfflineStatusChangeEvent(aIsOffline);
1876 void RuntimeService::MemoryPressureAllWorkers() {
1877 BroadcastAllWorkers([](auto& worker) { worker.MemoryPressure(); });
1880 uint32_t RuntimeService::ClampedHardwareConcurrency(
1881 bool aShouldResistFingerprinting) const {
1882 // The Firefox Hardware Report says 70% of Firefox users have exactly 2 cores.
1883 // When the resistFingerprinting pref is set, we want to blend into the crowd
1884 // so spoof navigator.hardwareConcurrency = 2 to reduce user uniqueness.
1885 if (MOZ_UNLIKELY(aShouldResistFingerprinting)) {
1886 return 2;
1889 // This needs to be atomic, because multiple workers, and even mainthread,
1890 // could race to initialize it at once.
1891 static Atomic<uint32_t> unclampedHardwareConcurrency;
1893 // No need to loop here: if compareExchange fails, that just means that some
1894 // other worker has initialized numberOfProcessors, so we're good to go.
1895 if (!unclampedHardwareConcurrency) {
1896 int32_t numberOfProcessors = 0;
1897 #if defined(XP_MACOSX)
1898 if (nsMacUtilsImpl::IsTCSMAvailable()) {
1899 // On failure, zero is returned from GetPhysicalCPUCount()
1900 // and we fallback to PR_GetNumberOfProcessors below.
1901 numberOfProcessors = nsMacUtilsImpl::GetPhysicalCPUCount();
1903 #endif
1904 if (numberOfProcessors == 0) {
1905 numberOfProcessors = PR_GetNumberOfProcessors();
1907 if (numberOfProcessors <= 0) {
1908 numberOfProcessors = 1; // Must be one there somewhere
1910 Unused << unclampedHardwareConcurrency.compareExchange(0,
1911 numberOfProcessors);
1914 return std::min(uint32_t(unclampedHardwareConcurrency),
1915 StaticPrefs::dom_maxHardwareConcurrency());
1918 // nsISupports
1919 NS_IMPL_ISUPPORTS(RuntimeService, nsIObserver)
1921 // nsIObserver
1922 NS_IMETHODIMP
1923 RuntimeService::Observe(nsISupports* aSubject, const char* aTopic,
1924 const char16_t* aData) {
1925 AssertIsOnMainThread();
1927 if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
1928 Shutdown();
1929 return NS_OK;
1931 if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID)) {
1932 Cleanup();
1933 return NS_OK;
1935 if (!strcmp(aTopic, GC_REQUEST_OBSERVER_TOPIC)) {
1936 GarbageCollectAllWorkers(/* shrinking = */ false);
1937 return NS_OK;
1939 if (!strcmp(aTopic, CC_REQUEST_OBSERVER_TOPIC)) {
1940 CycleCollectAllWorkers();
1941 return NS_OK;
1943 if (!strcmp(aTopic, MEMORY_PRESSURE_OBSERVER_TOPIC)) {
1944 nsDependentString data(aData);
1945 // Don't continue to GC/CC if we are in an ongoing low-memory state since
1946 // its very slow and it likely won't help us anyway.
1947 if (data.EqualsLiteral(LOW_MEMORY_ONGOING_DATA)) {
1948 return NS_OK;
1950 if (data.EqualsLiteral(LOW_MEMORY_DATA)) {
1951 SetLowMemoryStateAllWorkers(true);
1953 GarbageCollectAllWorkers(/* shrinking = */ true);
1954 CycleCollectAllWorkers();
1955 MemoryPressureAllWorkers();
1956 return NS_OK;
1958 if (!strcmp(aTopic, MEMORY_PRESSURE_STOP_OBSERVER_TOPIC)) {
1959 SetLowMemoryStateAllWorkers(false);
1960 return NS_OK;
1962 if (!strcmp(aTopic, NS_IOSERVICE_OFFLINE_STATUS_TOPIC)) {
1963 SendOfflineStatusChangeEventToAllWorkers(NS_IsOffline());
1964 return NS_OK;
1967 MOZ_ASSERT_UNREACHABLE("Unknown observer topic!");
1968 return NS_OK;
1971 namespace {
1972 const char* WorkerKindToString(WorkerKind kind) {
1973 switch (kind) {
1974 case WorkerKindDedicated:
1975 return "dedicated";
1976 case WorkerKindShared:
1977 return "shared";
1978 case WorkerKindService:
1979 return "service";
1980 default:
1981 NS_WARNING("Unknown worker type");
1982 return "unknown worker type";
1986 void LogWorker(WorkerPrivate* worker, const char* category) {
1987 AssertIsOnMainThread();
1989 SHUTDOWN_LOG(("Found %s (%s): %s", category,
1990 WorkerKindToString(worker->Kind()),
1991 NS_ConvertUTF16toUTF8(worker->ScriptURL()).get()));
1993 if (worker->Kind() == WorkerKindService) {
1994 SHUTDOWN_LOG(("Scope: %s", worker->ServiceWorkerScope().get()));
1997 nsCString origin;
1998 worker->GetPrincipal()->GetOrigin(origin);
1999 SHUTDOWN_LOG(("Principal: %s", origin.get()));
2001 nsCString loadingOrigin;
2002 worker->GetLoadingPrincipal()->GetOrigin(loadingOrigin);
2003 SHUTDOWN_LOG(("LoadingPrincipal: %s", loadingOrigin.get()));
2005 RefPtr<DumpCrashInfoRunnable> runnable = new DumpCrashInfoRunnable(worker);
2006 if (runnable->DispatchAndWait()) {
2007 SHUTDOWN_LOG(("CrashInfo: %s", runnable->MsgData().get()));
2008 } else {
2009 SHUTDOWN_LOG(("CrashInfo: dispatch failed"));
2012 } // namespace
2014 void RuntimeService::DumpRunningWorkers() {
2015 // Temporarily set the LogLevel high enough to be certain the messages are
2016 // visible.
2017 LogModule* module = gWorkerShutdownDumpLog;
2018 LogLevel prevLevel = module->Level();
2020 const auto cleanup =
2021 MakeScopeExit([module, prevLevel] { module->SetLevel(prevLevel); });
2023 if (prevLevel < LogLevel::Debug) {
2024 module->SetLevel(LogLevel::Debug);
2027 MutexAutoLock lock(mMutex);
2029 for (const auto& info : mDomainMap.Values()) {
2030 for (WorkerPrivate* worker : info->mActiveWorkers) {
2031 LogWorker(worker, "ActiveWorker");
2034 for (WorkerPrivate* worker : info->mActiveServiceWorkers) {
2035 LogWorker(worker, "ActiveServiceWorker");
2038 for (WorkerPrivate* worker : info->mQueuedWorkers) {
2039 LogWorker(worker, "QueuedWorker");
2044 bool LogViolationDetailsRunnable::MainThreadRun() {
2045 AssertIsOnMainThread();
2047 nsIContentSecurityPolicy* csp = mWorkerPrivate->GetCsp();
2048 if (csp) {
2049 csp->LogViolationDetails(mViolationType,
2050 nullptr, // triggering element
2051 mWorkerPrivate->CSPEventListener(), mFileName,
2052 mScriptSample, mLineNum, mColumnNum, u""_ns,
2053 u""_ns);
2056 return true;
2059 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
2060 // bug 1535398.
2061 MOZ_CAN_RUN_SCRIPT_BOUNDARY
2062 NS_IMETHODIMP
2063 WorkerThreadPrimaryRunnable::Run() {
2064 NS_ConvertUTF16toUTF8 url(mWorkerPrivate->ScriptURL());
2065 AUTO_PROFILER_LABEL_DYNAMIC_CSTR("WorkerThreadPrimaryRunnable::Run", OTHER,
2066 url.get());
2068 using mozilla::ipc::BackgroundChild;
2071 auto failureCleanup = MakeScopeExit([&]() {
2072 // The creation of threadHelper above is the point at which a worker is
2073 // considered to have run, because the `mPreStartRunnables` are all
2074 // re-dispatched after `mThread` is set. We need to let the WorkerPrivate
2075 // know so it can clean up the various event loops and delete the worker.
2076 mWorkerPrivate->RunLoopNeverRan();
2079 mWorkerPrivate->SetWorkerPrivateInWorkerThread(mThread.unsafeGetRawPtr());
2081 const auto threadCleanup = MakeScopeExit([&] {
2082 // This must be called before ScheduleDeletion, which is either called
2083 // from failureCleanup leaving scope, or from the outer scope.
2084 mWorkerPrivate->ResetWorkerPrivateInWorkerThread();
2087 mWorkerPrivate->AssertIsOnWorkerThread();
2089 // This needs to be initialized on the worker thread before being used on
2090 // the main thread and calling BackgroundChild::GetOrCreateForCurrentThread
2091 // exposes it to the main thread.
2092 mWorkerPrivate->EnsurePerformanceStorage();
2094 if (NS_WARN_IF(!BackgroundChild::GetOrCreateForCurrentThread())) {
2095 return NS_ERROR_FAILURE;
2098 nsWeakPtr globalScopeSentinel;
2099 nsWeakPtr debuggerScopeSentinel;
2100 // Never use the following pointers without checking their corresponding
2101 // nsWeakPtr sentinel, defined above and initialized after DoRunLoop ends.
2102 WorkerGlobalScopeBase* globalScopeRawPtr = nullptr;
2103 WorkerGlobalScopeBase* debuggerScopeRawPtr = nullptr;
2105 nsCycleCollector_startup();
2107 auto context = MakeUnique<WorkerJSContext>(mWorkerPrivate);
2108 nsresult rv = context->Initialize(mParentRuntime);
2109 if (NS_WARN_IF(NS_FAILED(rv))) {
2110 return rv;
2113 JSContext* cx = context->Context();
2115 if (!InitJSContextForWorker(mWorkerPrivate, cx)) {
2116 return NS_ERROR_FAILURE;
2119 failureCleanup.release();
2122 PROFILER_SET_JS_CONTEXT(cx);
2125 // We're on the worker thread here, and WorkerPrivate's refcounting is
2126 // non-threadsafe: you can only do it on the parent thread. What that
2127 // means in practice is that we're relying on it being kept alive
2128 // while we run. Hopefully.
2129 MOZ_KnownLive(mWorkerPrivate)->DoRunLoop(cx);
2130 // The AutoJSAPI in DoRunLoop should have reported any exceptions left
2131 // on cx.
2132 MOZ_ASSERT(!JS_IsExceptionPending(cx));
2135 mWorkerPrivate->ShutdownModuleLoader();
2137 mWorkerPrivate->RunShutdownTasks();
2139 BackgroundChild::CloseForCurrentThread();
2141 PROFILER_CLEAR_JS_CONTEXT();
2144 // There may still be runnables on the debugger event queue that hold a
2145 // strong reference to the debugger global scope. These runnables are not
2146 // visible to the cycle collector, so we need to make sure to clear the
2147 // debugger event queue before we try to destroy the context. If we don't,
2148 // the garbage collector will crash.
2149 // Note that this just releases the runnables and does not execute them.
2150 mWorkerPrivate->ClearDebuggerEventQueue();
2152 // Before shutting down the cycle collector we need to do one more pass
2153 // through the event loop to clean up any C++ objects that need deferred
2154 // cleanup.
2155 NS_ProcessPendingEvents(nullptr);
2157 // At this point we expect the scopes to be alive if they were ever
2158 // created successfully, keep weak references and set up the sentinels.
2159 globalScopeRawPtr = mWorkerPrivate->GlobalScope();
2160 if (globalScopeRawPtr) {
2161 globalScopeSentinel = do_GetWeakReference(globalScopeRawPtr);
2163 MOZ_ASSERT(!globalScopeRawPtr || globalScopeSentinel);
2164 debuggerScopeRawPtr = mWorkerPrivate->DebuggerGlobalScope();
2165 if (debuggerScopeRawPtr) {
2166 debuggerScopeSentinel = do_GetWeakReference(debuggerScopeRawPtr);
2168 MOZ_ASSERT(!debuggerScopeRawPtr || debuggerScopeSentinel);
2170 // To our best knowledge nobody should need a reference to our globals
2171 // now (NS_ProcessPendingEvents is the last expected potential usage)
2172 // and we can unroot them.
2173 mWorkerPrivate->UnrootGlobalScopes();
2175 // Perform a full GC until we collect the main worker global and CC,
2176 // which should break all cycles that touch JS.
2177 bool repeatGCCC = true;
2178 while (repeatGCCC) {
2179 JS::PrepareForFullGC(cx);
2180 JS::NonIncrementalGC(cx, JS::GCOptions::Shutdown,
2181 JS::GCReason::WORKER_SHUTDOWN);
2183 // If we CCed something or got new events as a side effect, repeat.
2184 repeatGCCC = mWorkerPrivate->isLastCCCollectedAnything() ||
2185 NS_HasPendingEvents(nullptr);
2186 NS_ProcessPendingEvents(nullptr);
2189 // The worker global should be unrooted and the shutdown of cycle
2190 // collection should break all the remaining cycles.
2191 nsCycleCollector_shutdown();
2193 // If ever the CC shutdown run caused side effects, process them.
2194 NS_ProcessPendingEvents(nullptr);
2196 // Now WorkerJSContext goes out of scope. Do not use any cycle
2197 // collectable objects nor JS after this point!
2200 // Check sentinels if we actually removed all global scope references.
2201 // In case use the earlier set-aside raw pointers to not mess with the
2202 // ref counting after the cycle collector has gone away.
2203 if (globalScopeSentinel) {
2204 MOZ_ASSERT(!globalScopeSentinel->IsAlive());
2205 if (NS_WARN_IF(globalScopeSentinel->IsAlive())) {
2206 globalScopeRawPtr->NoteWorkerTerminated();
2207 globalScopeRawPtr = nullptr;
2210 if (debuggerScopeSentinel) {
2211 MOZ_ASSERT(!debuggerScopeSentinel->IsAlive());
2212 if (NS_WARN_IF(debuggerScopeSentinel->IsAlive())) {
2213 debuggerScopeRawPtr->NoteWorkerTerminated();
2214 debuggerScopeRawPtr = nullptr;
2219 mWorkerPrivate->ScheduleDeletion(WorkerPrivate::WorkerRan);
2221 // It is no longer safe to touch mWorkerPrivate.
2222 mWorkerPrivate = nullptr;
2224 // Now recycle this thread.
2225 nsCOMPtr<nsIEventTarget> mainTarget = GetMainThreadSerialEventTarget();
2226 MOZ_ASSERT(mainTarget);
2228 RefPtr<FinishedRunnable> finishedRunnable =
2229 new FinishedRunnable(std::move(mThread));
2230 MOZ_ALWAYS_SUCCEEDS(
2231 mainTarget->Dispatch(finishedRunnable, NS_DISPATCH_NORMAL));
2233 return NS_OK;
2236 NS_IMETHODIMP
2237 WorkerThreadPrimaryRunnable::FinishedRunnable::Run() {
2238 AssertIsOnMainThread();
2240 SafeRefPtr<WorkerThread> thread = std::move(mThread);
2241 if (thread->ShutdownRequired()) {
2242 MOZ_ALWAYS_SUCCEEDS(thread->Shutdown());
2245 return NS_OK;
2248 } // namespace workerinternals
2250 // This is mostly for invoking within a debugger.
2251 void DumpRunningWorkers() {
2252 RuntimeService* runtimeService = RuntimeService::GetService();
2253 if (runtimeService) {
2254 runtimeService->DumpRunningWorkers();
2255 } else {
2256 NS_WARNING("RuntimeService not found");
2260 void CancelWorkersForWindow(const nsPIDOMWindowInner& aWindow) {
2261 AssertIsOnMainThread();
2262 RuntimeService* runtime = RuntimeService::GetService();
2263 if (runtime) {
2264 runtime->CancelWorkersForWindow(aWindow);
2268 void FreezeWorkersForWindow(const nsPIDOMWindowInner& aWindow) {
2269 AssertIsOnMainThread();
2270 RuntimeService* runtime = RuntimeService::GetService();
2271 if (runtime) {
2272 runtime->FreezeWorkersForWindow(aWindow);
2276 void ThawWorkersForWindow(const nsPIDOMWindowInner& aWindow) {
2277 AssertIsOnMainThread();
2278 RuntimeService* runtime = RuntimeService::GetService();
2279 if (runtime) {
2280 runtime->ThawWorkersForWindow(aWindow);
2284 void SuspendWorkersForWindow(const nsPIDOMWindowInner& aWindow) {
2285 AssertIsOnMainThread();
2286 RuntimeService* runtime = RuntimeService::GetService();
2287 if (runtime) {
2288 runtime->SuspendWorkersForWindow(aWindow);
2292 void ResumeWorkersForWindow(const nsPIDOMWindowInner& aWindow) {
2293 AssertIsOnMainThread();
2294 RuntimeService* runtime = RuntimeService::GetService();
2295 if (runtime) {
2296 runtime->ResumeWorkersForWindow(aWindow);
2300 void PropagateStorageAccessPermissionGrantedToWorkers(
2301 const nsPIDOMWindowInner& aWindow) {
2302 AssertIsOnMainThread();
2303 MOZ_ASSERT_IF(aWindow.GetExtantDoc(), aWindow.GetExtantDoc()
2304 ->CookieJarSettings()
2305 ->GetRejectThirdPartyContexts());
2307 RuntimeService* runtime = RuntimeService::GetService();
2308 if (runtime) {
2309 runtime->PropagateStorageAccessPermissionGranted(aWindow);
2313 WorkerPrivate* GetWorkerPrivateFromContext(JSContext* aCx) {
2314 MOZ_ASSERT(!NS_IsMainThread());
2315 MOZ_ASSERT(aCx);
2317 CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::GetFor(aCx);
2318 if (!ccjscx) {
2319 return nullptr;
2322 WorkerJSContext* workerjscx = ccjscx->GetAsWorkerJSContext();
2323 // GetWorkerPrivateFromContext is called only for worker contexts. The
2324 // context private is cleared early in ~CycleCollectedJSContext() and so
2325 // GetFor() returns null above if called after ccjscx is no longer a
2326 // WorkerJSContext.
2327 MOZ_ASSERT(workerjscx);
2328 return workerjscx->GetWorkerPrivate();
2331 WorkerPrivate* GetCurrentThreadWorkerPrivate() {
2332 if (NS_IsMainThread()) {
2333 return nullptr;
2336 CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get();
2337 if (!ccjscx) {
2338 return nullptr;
2341 WorkerJSContext* workerjscx = ccjscx->GetAsWorkerJSContext();
2342 // Even when GetCurrentThreadWorkerPrivate() is called on worker
2343 // threads, the ccjscx will no longer be a WorkerJSContext if called from
2344 // stable state events during ~CycleCollectedJSContext().
2345 if (!workerjscx) {
2346 return nullptr;
2349 return workerjscx->GetWorkerPrivate();
2352 bool IsCurrentThreadRunningWorker() {
2353 return !NS_IsMainThread() && !!GetCurrentThreadWorkerPrivate();
2356 bool IsCurrentThreadRunningChromeWorker() {
2357 WorkerPrivate* wp = GetCurrentThreadWorkerPrivate();
2358 return wp && wp->UsesSystemPrincipal();
2361 JSContext* GetCurrentWorkerThreadJSContext() {
2362 WorkerPrivate* wp = GetCurrentThreadWorkerPrivate();
2363 if (!wp) {
2364 return nullptr;
2366 return wp->GetJSContext();
2369 JSObject* GetCurrentThreadWorkerGlobal() {
2370 WorkerPrivate* wp = GetCurrentThreadWorkerPrivate();
2371 if (!wp) {
2372 return nullptr;
2374 WorkerGlobalScope* scope = wp->GlobalScope();
2375 if (!scope) {
2376 return nullptr;
2378 return scope->GetGlobalJSObject();
2381 JSObject* GetCurrentThreadWorkerDebuggerGlobal() {
2382 WorkerPrivate* wp = GetCurrentThreadWorkerPrivate();
2383 if (!wp) {
2384 return nullptr;
2386 WorkerDebuggerGlobalScope* scope = wp->DebuggerGlobalScope();
2387 if (!scope) {
2388 return nullptr;
2390 return scope->GetGlobalJSObject();
2393 } // namespace dom
2394 } // namespace mozilla