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"
18 #include "nsIXULRuntime.h"
19 #include "nsPIDOMWindow.h"
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"
66 #include "nsXPCOMPrivate.h"
67 #include "xpcpublic.h"
68 #include "XPCSelfHostedShmem.h"
70 #if defined(XP_MACOSX)
71 # include "nsMacUtilsImpl.h"
74 #include "WorkerDebuggerManager.h"
75 #include "WorkerError.h"
76 #include "WorkerLoadInfo.h"
77 #include "WorkerRunnable.h"
78 #include "WorkerScope.h"
79 #include "WorkerThread.h"
83 # include "nsICookieJarSettings.h"
86 #define WORKERS_SHUTDOWN_TOPIC "web-workers-shutdown"
88 static mozilla::LazyLogModule
gWorkerShutdownDumpLog("WorkerShutdownDump");
93 #define SHUTDOWN_LOG(msg) MOZ_LOG(gWorkerShutdownDumpLog, LogLevel::Debug, msg);
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
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
);
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
{
153 void RebindLiteral(const char (&aStr
)[N
]) {
158 template <typename T
>
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
;
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
>;
200 if (PrefHelper::Exists(aFullPref
)) {
201 result
= PrefHelper::Get(aFullPref
);
213 void LoadContextOptions(const char* aPrefName
, void* /* aClosure */) {
214 AssertIsOnMainThread();
216 RuntimeService
* rts
= RuntimeService::GetService();
218 // May be shutting down, just bail.
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(
228 nsLiteralCString(PREF_JS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX
))) {
233 if (prefName
.EqualsLiteral(PREF_JS_OPTIONS_PREFIX PREF_GCZEAL
)) {
238 JS::ContextOptions contextOptions
;
239 xpc::SetPrefableContextOptions(contextOptions
);
241 nsCOMPtr
<nsIXULRuntime
> xr
= do_GetService("@mozilla.org/xre/runtime;1");
243 bool safeMode
= false;
244 xr
->GetInSafeMode(&safeMode
);
246 contextOptions
.disableOptionsForSafeMode();
250 RuntimeService::SetDefaultContextOptions(contextOptions
);
253 rts
->UpdateAllWorkerContextOptions();
258 void LoadGCZealOptions(const char* /* aPrefName */, void* /* aClosure */) {
259 AssertIsOnMainThread();
261 RuntimeService
* rts
= RuntimeService::GetService();
263 // May be shutting down, just bail.
267 int32_t gczeal
= GetPref
<int32_t>(PREF_JS_OPTIONS_PREFIX PREF_GCZEAL
, -1);
273 GetPref
<int32_t>(PREF_JS_OPTIONS_PREFIX PREF_GCZEAL
".frequency", -1);
275 frequency
= JS_DEFAULT_ZEAL_FREQ
;
278 RuntimeService::SetDefaultGCZeal(uint8_t(gczeal
), uint32_t(frequency
));
281 rts
->UpdateAllWorkerGCZeal();
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)
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();
320 // May be shutting down, just bail.
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
330 nsDependentCSubstring memPrefName
;
331 if (StringBeginsWith(fullPrefName
, memPrefix
)) {
332 memPrefName
.Rebind(fullPrefName
, memPrefix
.Length());
334 NS_ERROR("Unknown pref name!");
338 struct WorkerGCPref
{
339 nsLiteralCString memName
;
340 const char* fullName
;
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
),
375 auto pref
= kWorkerPrefs
;
376 auto end
= kWorkerPrefs
+ ArrayLength(kWorkerPrefs
);
378 if (gRuntimeServiceDuringInit
) {
379 // During init, we want to update every pref in kWorkerPrefs.
380 MOZ_ASSERT(memPrefName
.IsEmpty(),
381 "Pref branch prefix only expected during init");
383 // Otherwise, find the single pref that changed.
384 while (pref
!= end
) {
385 if (pref
->memName
== memPrefName
) {
393 nsAutoCString
message("Workers don't support the '");
394 message
.Append(memPrefName
);
395 message
.AppendLiteral("' preference!");
396 NS_WARNING(message
.get());
401 while (pref
!= end
) {
403 case JSGC_MAX_BYTES
: {
404 int32_t prefValue
= GetPref(pref
->fullName
, -1);
405 Maybe
<uint32_t> value
= (prefValue
<= 0 || prefValue
>= 0x1000)
407 : Some(uint32_t(prefValue
) * 1024 * 1024);
408 UpdateOtherJSGCMemoryOption(rts
, pref
->key
, value
);
411 case JSGC_SLICE_TIME_BUDGET_MS
: {
412 int32_t prefValue
= GetPref(pref
->fullName
, -1);
413 Maybe
<uint32_t> value
= (prefValue
<= 0 || prefValue
>= 100000)
415 : Some(uint32_t(prefValue
));
416 UpdateOtherJSGCMemoryOption(rts
, pref
->key
, value
);
419 case JSGC_COMPACTING_ENABLED
:
420 case JSGC_PARALLEL_MARKING_ENABLED
:
421 case JSGC_BALANCED_HEAP_LIMITS_ENABLED
: {
423 bool prefValue
= GetPref(pref
->fullName
, false, &present
);
424 Maybe
<uint32_t> value
= present
? Some(prefValue
? 1 : 0) : Nothing();
425 UpdateOtherJSGCMemoryOption(rts
, pref
->key
, value
);
428 case JSGC_HIGH_FREQUENCY_TIME_LIMIT
:
429 case JSGC_LOW_FREQUENCY_HEAP_GROWTH
:
430 case JSGC_HIGH_FREQUENCY_LARGE_HEAP_GROWTH
:
431 case JSGC_HIGH_FREQUENCY_SMALL_HEAP_GROWTH
:
432 case JSGC_SMALL_HEAP_SIZE_MAX
:
433 case JSGC_LARGE_HEAP_SIZE_MIN
:
434 case JSGC_ALLOCATION_THRESHOLD
:
435 case JSGC_MALLOC_THRESHOLD_BASE
:
436 case JSGC_SMALL_HEAP_INCREMENTAL_LIMIT
:
437 case JSGC_LARGE_HEAP_INCREMENTAL_LIMIT
:
438 case JSGC_URGENT_THRESHOLD_MB
:
439 case JSGC_MIN_EMPTY_CHUNK_COUNT
:
440 case JSGC_MAX_EMPTY_CHUNK_COUNT
:
441 case JSGC_HEAP_GROWTH_FACTOR
:
442 UpdateCommonJSGCMemoryOption(rts
, pref
->fullName
, pref
->key
);
445 MOZ_ASSERT_UNREACHABLE("Unknown JSGCParamKey value");
452 bool InterruptCallback(JSContext
* aCx
) {
453 WorkerPrivate
* worker
= GetWorkerPrivateFromContext(aCx
);
456 // Now is a good time to turn on profiling if it's pending.
457 PROFILER_JS_INTERRUPT_CALLBACK();
459 return worker
->InterruptCallback(aCx
);
462 class LogViolationDetailsRunnable final
: public WorkerMainThreadRunnable
{
463 uint16_t mViolationType
;
467 nsString mScriptSample
;
470 LogViolationDetailsRunnable(WorkerPrivate
* aWorker
, uint16_t aViolationType
,
471 const nsString
& aFileName
, uint32_t aLineNum
,
473 const nsAString
& aScriptSample
)
474 : WorkerMainThreadRunnable(aWorker
,
475 "RuntimeService :: LogViolationDetails"_ns
),
476 mViolationType(aViolationType
),
477 mFileName(aFileName
),
479 mColumnNum(aColumnNum
),
480 mScriptSample(aScriptSample
) {
484 virtual bool MainThreadRun() override
;
487 ~LogViolationDetailsRunnable() = default;
490 bool ContentSecurityPolicyAllows(JSContext
* aCx
, JS::RuntimeCode aKind
,
491 JS::Handle
<JSString
*> aCode
) {
492 WorkerPrivate
* worker
= GetWorkerPrivateFromContext(aCx
);
493 worker
->AssertIsOnWorkerThread();
496 bool reportViolation
;
497 uint16_t violationType
;
498 nsAutoJSString scriptSample
;
499 if (aKind
== JS::RuntimeCode::JS
) {
500 if (NS_WARN_IF(!scriptSample
.init(aCx
, aCode
))) {
501 JS_ClearPendingException(aCx
);
505 if (!nsContentSecurityUtils::IsEvalAllowed(
506 aCx
, worker
->UsesSystemPrincipal(), scriptSample
)) {
510 evalOK
= worker
->IsEvalAllowed();
511 reportViolation
= worker
->GetReportEvalCSPViolations();
512 violationType
= nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL
;
514 evalOK
= worker
->IsWasmEvalAllowed();
515 reportViolation
= worker
->GetReportWasmEvalCSPViolations();
516 violationType
= nsIContentSecurityPolicy::VIOLATION_TYPE_WASM_EVAL
;
519 if (reportViolation
) {
521 uint32_t lineNum
= 0;
522 JS::ColumnNumberOneOrigin columnNum
;
524 JS::AutoFilename file
;
525 if (JS::DescribeScriptedCaller(aCx
, &file
, &lineNum
, &columnNum
) &&
527 CopyUTF8toUTF16(MakeStringSpan(file
.get()), fileName
);
529 MOZ_ASSERT(!JS_IsExceptionPending(aCx
));
532 RefPtr
<LogViolationDetailsRunnable
> runnable
=
533 new LogViolationDetailsRunnable(worker
, violationType
, fileName
,
534 lineNum
, columnNum
.oneOriginValue(),
538 runnable
->Dispatch(Killing
, rv
);
539 if (NS_WARN_IF(rv
.Failed())) {
540 rv
.SuppressException();
547 void CTypesActivityCallback(JSContext
* aCx
, JS::CTypesActivityType aType
) {
548 WorkerPrivate
* worker
= GetWorkerPrivateFromContext(aCx
);
549 worker
->AssertIsOnWorkerThread();
552 case JS::CTypesActivityType::BeginCall
:
553 worker
->BeginCTypesCall();
556 case JS::CTypesActivityType::EndCall
:
557 worker
->EndCTypesCall();
560 case JS::CTypesActivityType::BeginCallback
:
561 worker
->BeginCTypesCallback();
564 case JS::CTypesActivityType::EndCallback
:
565 worker
->EndCTypesCallback();
569 MOZ_CRASH("Unknown type flag!");
573 // JSDispatchableRunnables are WorkerRunnables used to dispatch JS::Dispatchable
574 // back to their worker thread. A WorkerRunnable is used for two reasons:
576 // 1. The JS::Dispatchable::run() callback may run JS so we cannot use a control
577 // runnable since they use async interrupts and break JS run-to-completion.
579 // 2. The DispatchToEventLoopCallback interface is *required* to fail during
580 // shutdown (see jsapi.h) which is exactly what WorkerRunnable::Dispatch() will
581 // do. Moreover, JS_DestroyContext() does *not* block on JS::Dispatchable::run
582 // being called, DispatchToEventLoopCallback failure is expected to happen
584 class JSDispatchableRunnable final
: public WorkerRunnable
{
585 JS::Dispatchable
* mDispatchable
;
587 ~JSDispatchableRunnable() { MOZ_ASSERT(!mDispatchable
); }
589 // Disable the usual pre/post-dispatch thread assertions since we are
590 // dispatching from some random JS engine internal thread:
592 bool PreDispatch(WorkerPrivate
* aWorkerPrivate
) override
{ return true; }
594 void PostDispatch(WorkerPrivate
* aWorkerPrivate
,
595 bool aDispatchResult
) override
{
596 // For the benefit of the destructor assert.
597 if (!aDispatchResult
) {
598 mDispatchable
= nullptr;
603 JSDispatchableRunnable(WorkerPrivate
* aWorkerPrivate
,
604 JS::Dispatchable
* aDispatchable
)
605 : WorkerRunnable(aWorkerPrivate
, "JSDispatchableRunnable",
606 WorkerRunnable::WorkerThread
),
607 mDispatchable(aDispatchable
) {
608 MOZ_ASSERT(mDispatchable
);
611 bool WorkerRun(JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
) override
{
612 MOZ_ASSERT(aWorkerPrivate
== mWorkerPrivate
);
613 MOZ_ASSERT(aCx
== mWorkerPrivate
->GetJSContext());
614 MOZ_ASSERT(mDispatchable
);
619 mDispatchable
->run(mWorkerPrivate
->GetJSContext(),
620 JS::Dispatchable::NotShuttingDown
);
621 mDispatchable
= nullptr; // mDispatchable may delete itself
626 nsresult
Cancel() override
{
627 MOZ_ASSERT(mDispatchable
);
632 mDispatchable
->run(mWorkerPrivate
->GetJSContext(),
633 JS::Dispatchable::ShuttingDown
);
634 mDispatchable
= nullptr; // mDispatchable may delete itself
640 static bool DispatchToEventLoop(void* aClosure
,
641 JS::Dispatchable
* aDispatchable
) {
642 // This callback may execute either on the worker thread or a random
643 // JS-internal helper thread.
645 // See comment at JS::InitDispatchToEventLoop() below for how we know the
646 // WorkerPrivate is alive.
647 WorkerPrivate
* workerPrivate
= reinterpret_cast<WorkerPrivate
*>(aClosure
);
649 // Dispatch is expected to fail during shutdown for the reasons outlined in
650 // the JSDispatchableRunnable comment above.
651 RefPtr
<JSDispatchableRunnable
> r
=
652 new JSDispatchableRunnable(workerPrivate
, aDispatchable
);
653 return r
->Dispatch();
656 static bool ConsumeStream(JSContext
* aCx
, JS::Handle
<JSObject
*> aObj
,
657 JS::MimeType aMimeType
,
658 JS::StreamConsumer
* aConsumer
) {
659 WorkerPrivate
* worker
= GetWorkerPrivateFromContext(aCx
);
661 JS_ReportErrorNumberASCII(aCx
, js::GetErrorMessage
, nullptr,
662 JSMSG_WASM_ERROR_CONSUMING_RESPONSE
);
666 return FetchUtil::StreamResponseToJS(aCx
, aObj
, aMimeType
, aConsumer
, worker
);
669 bool InitJSContextForWorker(WorkerPrivate
* aWorkerPrivate
,
670 JSContext
* aWorkerCx
) {
671 aWorkerPrivate
->AssertIsOnWorkerThread();
672 NS_ASSERTION(!aWorkerPrivate
->GetJSContext(), "Already has a context!");
675 aWorkerPrivate
->CopyJSSettings(settings
);
677 JS::ContextOptionsRef(aWorkerCx
) = settings
.contextOptions
;
679 // This is the real place where we set the max memory for the runtime.
680 for (const auto& setting
: settings
.gcSettings
) {
682 JS_SetGCParameter(aWorkerCx
, setting
.key
, *setting
.value
);
684 JS_ResetGCParameter(aWorkerCx
, setting
.key
);
688 JS_SetNativeStackQuota(aWorkerCx
, WORKER_CONTEXT_NATIVE_STACK_LIMIT
);
691 static const JSSecurityCallbacks securityCallbacks
= {
692 ContentSecurityPolicyAllows
};
693 JS_SetSecurityCallbacks(aWorkerCx
, &securityCallbacks
);
695 // A WorkerPrivate lives strictly longer than its JSRuntime so we can safely
696 // store a raw pointer as the callback's closure argument on the JSRuntime.
697 JS::InitDispatchToEventLoop(aWorkerCx
, DispatchToEventLoop
,
698 (void*)aWorkerPrivate
);
700 JS::InitConsumeStreamCallback(aWorkerCx
, ConsumeStream
,
701 FetchUtil::ReportJSStreamError
);
703 // When available, set the self-hosted shared memory to be read, so that we
704 // can decode the self-hosted content instead of parsing it.
705 auto& shm
= xpc::SelfHostedShmem::GetSingleton();
706 JS::SelfHostedCache selfHostedContent
= shm
.Content();
708 if (!JS::InitSelfHostedCode(aWorkerCx
, selfHostedContent
)) {
709 NS_WARNING("Could not init self-hosted code!");
713 JS_AddInterruptCallback(aWorkerCx
, InterruptCallback
);
715 JS::SetCTypesActivityCallback(aWorkerCx
, CTypesActivityCallback
);
718 JS_SetGCZeal(aWorkerCx
, settings
.gcZeal
, settings
.gcZealFrequency
);
724 static bool PreserveWrapper(JSContext
* cx
, JS::Handle
<JSObject
*> obj
) {
727 MOZ_ASSERT(mozilla::dom::IsDOMObject(obj
));
729 return mozilla::dom::TryPreserveWrapper(obj
);
732 static bool IsWorkerDebuggerGlobalOrSandbox(JS::Handle
<JSObject
*> aGlobal
) {
733 return IsWorkerDebuggerGlobal(aGlobal
) || IsWorkerDebuggerSandbox(aGlobal
);
736 JSObject
* Wrap(JSContext
* cx
, JS::Handle
<JSObject
*> existing
,
737 JS::Handle
<JSObject
*> obj
) {
738 JS::Rooted
<JSObject
*> targetGlobal(cx
, JS::CurrentGlobalOrNull(cx
));
740 // Note: the JS engine unwraps CCWs before calling this callback.
741 JS::Rooted
<JSObject
*> originGlobal(cx
, JS::GetNonCCWObjectGlobal(obj
));
743 const js::Wrapper
* wrapper
= nullptr;
744 if (IsWorkerDebuggerGlobalOrSandbox(targetGlobal
) &&
745 IsWorkerDebuggerGlobalOrSandbox(originGlobal
)) {
746 wrapper
= &js::CrossCompartmentWrapper::singleton
;
748 wrapper
= &js::OpaqueCrossCompartmentWrapper::singleton
;
752 js::Wrapper::Renew(existing
, obj
, wrapper
);
754 return js::Wrapper::New(cx
, obj
, wrapper
);
757 static const JSWrapObjectCallbacks WrapObjectCallbacks
= {
762 class WorkerJSRuntime final
: public mozilla::CycleCollectedJSRuntime
{
764 // The heap size passed here doesn't matter, we will change it later in the
765 // call to JS_SetGCParameter inside InitJSContextForWorker.
766 explicit WorkerJSRuntime(JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
)
767 : CycleCollectedJSRuntime(aCx
), mWorkerPrivate(aWorkerPrivate
) {
768 MOZ_COUNT_CTOR_INHERITED(WorkerJSRuntime
, CycleCollectedJSRuntime
);
769 MOZ_ASSERT(aWorkerPrivate
);
772 JS::UniqueChars defaultLocale
= aWorkerPrivate
->AdoptDefaultLocale();
773 MOZ_ASSERT(defaultLocale
,
774 "failure of a WorkerPrivate to have a default locale should "
775 "have made the worker fail to spawn");
777 if (!JS_SetDefaultLocale(Runtime(), defaultLocale
.get())) {
778 NS_WARNING("failed to set workerCx's default locale");
783 void Shutdown(JSContext
* cx
) override
{
784 // The CC is shut down, and the superclass destructor will GC, so make sure
785 // we don't try to CC again.
786 mWorkerPrivate
= nullptr;
788 CycleCollectedJSRuntime::Shutdown(cx
);
792 MOZ_COUNT_DTOR_INHERITED(WorkerJSRuntime
, CycleCollectedJSRuntime
);
795 virtual void PrepareForForgetSkippable() override
{}
797 virtual void BeginCycleCollectionCallback(
798 mozilla::CCReason aReason
) override
{}
800 virtual void EndCycleCollectionCallback(
801 CycleCollectorResults
& aResults
) override
{}
803 void DispatchDeferredDeletion(bool aContinuation
, bool aPurge
) override
{
804 MOZ_ASSERT(!aContinuation
);
806 // Do it immediately, no need for asynchronous behavior here.
807 nsCycleCollector_doDeferredDeletion();
810 virtual void CustomGCCallback(JSGCStatus aStatus
) override
{
811 if (!mWorkerPrivate
) {
812 // We're shutting down, no need to do anything.
816 mWorkerPrivate
->AssertIsOnWorkerThread();
818 if (aStatus
== JSGC_END
) {
819 bool collectedAnything
=
820 nsCycleCollector_collect(CCReason::GC_FINISHED
, nullptr);
821 mWorkerPrivate
->SetCCCollectedAnything(collectedAnything
);
826 WorkerPrivate
* mWorkerPrivate
;
829 } // anonymous namespace
831 } // namespace workerinternals
833 class WorkerJSContext final
: public mozilla::CycleCollectedJSContext
{
835 // The heap size passed here doesn't matter, we will change it later in the
836 // call to JS_SetGCParameter inside InitJSContextForWorker.
837 explicit WorkerJSContext(WorkerPrivate
* aWorkerPrivate
)
838 : mWorkerPrivate(aWorkerPrivate
) {
839 MOZ_COUNT_CTOR_INHERITED(WorkerJSContext
, CycleCollectedJSContext
);
840 MOZ_ASSERT(aWorkerPrivate
);
841 // Magical number 2. Workers have the base recursion depth 1, and normal
842 // runnables run at level 2, and we don't want to process microtasks
843 // at any other level.
844 SetTargetedMicroTaskRecursionDepth(2);
847 // MOZ_CAN_RUN_SCRIPT_BOUNDARY because otherwise we have to annotate the
848 // SpiderMonkey JS::JobQueue's destructor as MOZ_CAN_RUN_SCRIPT, which is a
850 MOZ_CAN_RUN_SCRIPT_BOUNDARY
~WorkerJSContext() {
851 MOZ_COUNT_DTOR_INHERITED(WorkerJSContext
, CycleCollectedJSContext
);
852 JSContext
* cx
= MaybeContext();
854 return; // Initialize() must have failed
857 // We expect to come here with the cycle collector already shut down.
858 // The superclass destructor will run the GC one final time and finalize any
859 // JSObjects that were participating in cycles that were broken during CC
861 // Make sure we don't try to CC again.
862 mWorkerPrivate
= nullptr;
865 WorkerJSContext
* GetAsWorkerJSContext() override
{ return this; }
867 CycleCollectedJSRuntime
* CreateRuntime(JSContext
* aCx
) override
{
868 return new WorkerJSRuntime(aCx
, mWorkerPrivate
);
871 nsresult
Initialize(JSRuntime
* aParentRuntime
) {
872 nsresult rv
= CycleCollectedJSContext::Initialize(
873 aParentRuntime
, WORKER_DEFAULT_RUNTIME_HEAPSIZE
);
874 if (NS_WARN_IF(NS_FAILED(rv
))) {
878 JSContext
* cx
= Context();
880 js::SetPreserveWrapperCallbacks(cx
, PreserveWrapper
, HasReleasedWrapper
);
881 JS_InitDestroyPrincipalsCallback(cx
, nsJSPrincipals::Destroy
);
882 JS_InitReadPrincipalsCallback(cx
, nsJSPrincipals::ReadPrincipals
);
883 JS_SetWrapObjectCallbacks(cx
, &WrapObjectCallbacks
);
884 if (mWorkerPrivate
->IsDedicatedWorker()) {
885 JS_SetFutexCanWait(cx
);
891 virtual void DispatchToMicroTask(
892 already_AddRefed
<MicroTaskRunnable
> aRunnable
) override
{
893 RefPtr
<MicroTaskRunnable
> runnable(aRunnable
);
895 MOZ_ASSERT(!NS_IsMainThread());
896 MOZ_ASSERT(runnable
);
898 std::deque
<RefPtr
<MicroTaskRunnable
>>* microTaskQueue
= nullptr;
900 JSContext
* cx
= Context();
901 NS_ASSERTION(cx
, "This should never be null!");
903 JS::Rooted
<JSObject
*> global(cx
, JS::CurrentGlobalOrNull(cx
));
904 NS_ASSERTION(global
, "This should never be null!");
906 // On worker threads, if the current global is the worker global or
907 // ShadowRealm global, we use the main micro task queue. Otherwise, the
908 // current global must be either the debugger global or a debugger sandbox,
909 // and we use the debugger micro task queue instead.
910 if (IsWorkerGlobal(global
) || IsShadowRealmGlobal(global
)) {
911 microTaskQueue
= &GetMicroTaskQueue();
913 MOZ_ASSERT(IsWorkerDebuggerGlobal(global
) ||
914 IsWorkerDebuggerSandbox(global
));
916 microTaskQueue
= &GetDebuggerMicroTaskQueue();
919 JS::JobQueueMayNotBeEmpty(cx
);
920 microTaskQueue
->push_back(std::move(runnable
));
923 bool IsSystemCaller() const override
{
924 return mWorkerPrivate
->UsesSystemPrincipal();
927 void ReportError(JSErrorReport
* aReport
,
928 JS::ConstUTF8CharsZ aToStringResult
) override
{
929 mWorkerPrivate
->ReportError(Context(), aToStringResult
, aReport
);
932 WorkerPrivate
* GetWorkerPrivate() const { return mWorkerPrivate
; }
935 WorkerPrivate
* mWorkerPrivate
;
938 namespace workerinternals
{
942 class WorkerThreadPrimaryRunnable final
: public Runnable
{
943 WorkerPrivate
* mWorkerPrivate
;
944 SafeRefPtr
<WorkerThread
> mThread
;
945 JSRuntime
* mParentRuntime
;
947 class FinishedRunnable final
: public Runnable
{
948 SafeRefPtr
<WorkerThread
> mThread
;
951 explicit FinishedRunnable(SafeRefPtr
<WorkerThread
> aThread
)
952 : Runnable("WorkerThreadPrimaryRunnable::FinishedRunnable"),
953 mThread(std::move(aThread
)) {
957 NS_INLINE_DECL_REFCOUNTING_INHERITED(FinishedRunnable
, Runnable
)
960 ~FinishedRunnable() = default;
966 WorkerThreadPrimaryRunnable(WorkerPrivate
* aWorkerPrivate
,
967 SafeRefPtr
<WorkerThread
> aThread
,
968 JSRuntime
* aParentRuntime
)
969 : mozilla::Runnable("WorkerThreadPrimaryRunnable"),
970 mWorkerPrivate(aWorkerPrivate
),
971 mThread(std::move(aThread
)),
972 mParentRuntime(aParentRuntime
) {
973 MOZ_ASSERT(aWorkerPrivate
);
977 NS_INLINE_DECL_REFCOUNTING_INHERITED(WorkerThreadPrimaryRunnable
, Runnable
)
980 ~WorkerThreadPrimaryRunnable() = default;
985 void PrefLanguagesChanged(const char* /* aPrefName */, void* /* aClosure */) {
986 AssertIsOnMainThread();
988 nsTArray
<nsString
> languages
;
989 Navigator::GetAcceptLanguages(languages
);
991 RuntimeService
* runtime
= RuntimeService::GetService();
993 runtime
->UpdateAllWorkerLanguages(languages
);
997 void AppVersionOverrideChanged(const char* /* aPrefName */,
998 void* /* aClosure */) {
999 AssertIsOnMainThread();
1001 nsAutoString override
;
1002 Preferences::GetString("general.appversion.override", override
);
1004 RuntimeService
* runtime
= RuntimeService::GetService();
1006 runtime
->UpdateAppVersionOverridePreference(override
);
1010 void PlatformOverrideChanged(const char* /* aPrefName */,
1011 void* /* aClosure */) {
1012 AssertIsOnMainThread();
1014 nsAutoString override
;
1015 Preferences::GetString("general.platform.override", override
);
1017 RuntimeService
* runtime
= RuntimeService::GetService();
1019 runtime
->UpdatePlatformOverridePreference(override
);
1023 } /* anonymous namespace */
1025 // This is only touched on the main thread. Initialized in Init() below.
1026 StaticAutoPtr
<JSSettings
> RuntimeService::sDefaultJSSettings
;
1028 RuntimeService::RuntimeService()
1029 : mMutex("RuntimeService::mMutex"),
1031 mShuttingDown(false),
1032 mNavigatorPropertiesLoaded(false) {
1033 AssertIsOnMainThread();
1034 MOZ_ASSERT(!GetService(), "More than one service!");
1037 RuntimeService::~RuntimeService() {
1038 AssertIsOnMainThread();
1040 // gRuntimeService can be null if Init() fails.
1041 MOZ_ASSERT(!GetService() || GetService() == this, "More than one service!");
1043 gRuntimeService
= nullptr;
1047 RuntimeService
* RuntimeService::GetOrCreateService() {
1048 AssertIsOnMainThread();
1050 if (!gRuntimeService
) {
1051 // The observer service now owns us until shutdown.
1052 gRuntimeService
= new RuntimeService();
1053 if (NS_FAILED((*gRuntimeService
).Init())) {
1054 NS_WARNING("Failed to initialize!");
1055 (*gRuntimeService
).Cleanup();
1056 gRuntimeService
= nullptr;
1061 return gRuntimeService
;
1065 RuntimeService
* RuntimeService::GetService() { return gRuntimeService
; }
1067 bool RuntimeService::RegisterWorker(WorkerPrivate
& aWorkerPrivate
) {
1068 aWorkerPrivate
.AssertIsOnParentThread();
1070 WorkerPrivate
* parent
= aWorkerPrivate
.GetParent();
1072 AssertIsOnMainThread();
1074 if (mShuttingDown
) {
1079 const bool isServiceWorker
= aWorkerPrivate
.IsServiceWorker();
1080 const bool isSharedWorker
= aWorkerPrivate
.IsSharedWorker();
1081 const bool isDedicatedWorker
= aWorkerPrivate
.IsDedicatedWorker();
1082 if (isServiceWorker
) {
1083 AssertIsOnMainThread();
1084 Telemetry::Accumulate(Telemetry::SERVICE_WORKER_SPAWN_ATTEMPTS
, 1);
1087 nsCString sharedWorkerScriptSpec
;
1088 if (isSharedWorker
) {
1089 AssertIsOnMainThread();
1091 nsCOMPtr
<nsIURI
> scriptURI
= aWorkerPrivate
.GetResolvedScriptURI();
1092 NS_ASSERTION(scriptURI
, "Null script URI!");
1094 nsresult rv
= scriptURI
->GetSpec(sharedWorkerScriptSpec
);
1095 if (NS_FAILED(rv
)) {
1096 NS_WARNING("GetSpec failed?!");
1100 NS_ASSERTION(!sharedWorkerScriptSpec
.IsEmpty(), "Empty spec!");
1103 bool exemptFromPerDomainMax
= false;
1104 if (isServiceWorker
) {
1105 AssertIsOnMainThread();
1106 exemptFromPerDomainMax
= Preferences::GetBool(
1107 "dom.serviceWorkers.exemptFromPerDomainMax", false);
1110 const nsCString
& domain
= aWorkerPrivate
.Domain();
1112 bool queued
= false;
1114 MutexAutoLock
lock(mMutex
);
1116 auto* const domainInfo
=
1118 .LookupOrInsertWith(
1121 NS_ASSERTION(!parent
, "Shouldn't have a parent here!");
1122 Unused
<< parent
; // silence clang -Wunused-lambda-capture in
1124 auto wdi
= MakeUnique
<WorkerDomainInfo
>();
1125 wdi
->mDomain
= domain
;
1130 queued
= gMaxWorkersPerDomain
&&
1131 domainInfo
->ActiveWorkerCount() >= gMaxWorkersPerDomain
&&
1132 !domain
.IsEmpty() && !exemptFromPerDomainMax
;
1135 domainInfo
->mQueuedWorkers
.AppendElement(&aWorkerPrivate
);
1137 // Worker spawn gets queued due to hitting max workers per domain
1138 // limit so let's log a warning.
1139 WorkerPrivate::ReportErrorToConsole("HittingMaxWorkersPerDomain2");
1141 if (isServiceWorker
) {
1142 Telemetry::Accumulate(Telemetry::SERVICE_WORKER_SPAWN_GETS_QUEUED
, 1);
1143 } else if (isSharedWorker
) {
1144 Telemetry::Accumulate(Telemetry::SHARED_WORKER_SPAWN_GETS_QUEUED
, 1);
1145 } else if (isDedicatedWorker
) {
1146 Telemetry::Accumulate(Telemetry::DEDICATED_WORKER_SPAWN_GETS_QUEUED
, 1);
1148 } else if (parent
) {
1149 domainInfo
->mChildWorkerCount
++;
1150 } else if (isServiceWorker
) {
1151 domainInfo
->mActiveServiceWorkers
.AppendElement(&aWorkerPrivate
);
1153 domainInfo
->mActiveWorkers
.AppendElement(&aWorkerPrivate
);
1157 // From here on out we must call UnregisterWorker if something fails!
1159 if (!parent
->AddChildWorker(aWorkerPrivate
)) {
1160 UnregisterWorker(aWorkerPrivate
);
1164 if (!mNavigatorPropertiesLoaded
) {
1165 if (NS_FAILED(Navigator::GetAppVersion(
1166 mNavigatorProperties
.mAppVersion
, aWorkerPrivate
.GetDocument(),
1167 false /* aUsePrefOverriddenValue */)) ||
1168 NS_FAILED(Navigator::GetPlatform(
1169 mNavigatorProperties
.mPlatform
, aWorkerPrivate
.GetDocument(),
1170 false /* aUsePrefOverriddenValue */))) {
1171 UnregisterWorker(aWorkerPrivate
);
1175 // The navigator overridden properties should have already been read.
1177 Navigator::GetAcceptLanguages(mNavigatorProperties
.mLanguages
);
1178 mNavigatorPropertiesLoaded
= true;
1181 nsPIDOMWindowInner
* window
= aWorkerPrivate
.GetWindow();
1183 if (!isServiceWorker
) {
1184 // Service workers are excluded since their lifetime is separate from
1185 // that of dom windows.
1186 if (auto* const windowArray
= mWindowMap
.GetOrInsertNew(window
, 1);
1187 !windowArray
->Contains(&aWorkerPrivate
)) {
1188 windowArray
->AppendElement(&aWorkerPrivate
);
1190 MOZ_ASSERT(aWorkerPrivate
.IsSharedWorker());
1195 if (!queued
&& !ScheduleWorker(aWorkerPrivate
)) {
1199 if (isServiceWorker
) {
1200 AssertIsOnMainThread();
1201 Telemetry::Accumulate(Telemetry::SERVICE_WORKER_WAS_SPAWNED
, 1);
1206 void RuntimeService::UnregisterWorker(WorkerPrivate
& aWorkerPrivate
) {
1207 aWorkerPrivate
.AssertIsOnParentThread();
1209 WorkerPrivate
* parent
= aWorkerPrivate
.GetParent();
1211 AssertIsOnMainThread();
1214 const nsCString
& domain
= aWorkerPrivate
.Domain();
1216 WorkerPrivate
* queuedWorker
= nullptr;
1218 MutexAutoLock
lock(mMutex
);
1220 WorkerDomainInfo
* domainInfo
;
1221 if (!mDomainMap
.Get(domain
, &domainInfo
)) {
1222 NS_ERROR("Don't have an entry for this domain!");
1225 // Remove old worker from everywhere.
1226 uint32_t index
= domainInfo
->mQueuedWorkers
.IndexOf(&aWorkerPrivate
);
1227 if (index
!= kNoIndex
) {
1228 // Was queued, remove from the list.
1229 domainInfo
->mQueuedWorkers
.RemoveElementAt(index
);
1230 } else if (parent
) {
1231 MOZ_ASSERT(domainInfo
->mChildWorkerCount
, "Must be non-zero!");
1232 domainInfo
->mChildWorkerCount
--;
1233 } else if (aWorkerPrivate
.IsServiceWorker()) {
1234 MOZ_ASSERT(domainInfo
->mActiveServiceWorkers
.Contains(&aWorkerPrivate
),
1235 "Don't know about this worker!");
1236 domainInfo
->mActiveServiceWorkers
.RemoveElement(&aWorkerPrivate
);
1238 MOZ_ASSERT(domainInfo
->mActiveWorkers
.Contains(&aWorkerPrivate
),
1239 "Don't know about this worker!");
1240 domainInfo
->mActiveWorkers
.RemoveElement(&aWorkerPrivate
);
1243 // See if there's a queued worker we can schedule.
1244 if (domainInfo
->ActiveWorkerCount() < gMaxWorkersPerDomain
&&
1245 !domainInfo
->mQueuedWorkers
.IsEmpty()) {
1246 queuedWorker
= domainInfo
->mQueuedWorkers
[0];
1247 domainInfo
->mQueuedWorkers
.RemoveElementAt(0);
1249 if (queuedWorker
->GetParent()) {
1250 domainInfo
->mChildWorkerCount
++;
1251 } else if (queuedWorker
->IsServiceWorker()) {
1252 domainInfo
->mActiveServiceWorkers
.AppendElement(queuedWorker
);
1254 domainInfo
->mActiveWorkers
.AppendElement(queuedWorker
);
1258 if (domainInfo
->HasNoWorkers()) {
1259 MOZ_ASSERT(domainInfo
->mQueuedWorkers
.IsEmpty());
1260 mDomainMap
.Remove(domain
);
1264 // NB: For Shared Workers we used to call ShutdownOnMainThread on the
1265 // RemoteWorkerController; however, that was redundant because
1266 // RemoteWorkerChild uses a WeakWorkerRef which notifies at about the
1267 // same time as us calling into the code here and would race with us.
1270 parent
->RemoveChildWorker(aWorkerPrivate
);
1271 } else if (aWorkerPrivate
.IsSharedWorker()) {
1272 AssertIsOnMainThread();
1274 mWindowMap
.RemoveIf([&aWorkerPrivate
](const auto& iter
) {
1275 const auto& workers
= iter
.Data();
1276 MOZ_ASSERT(workers
);
1278 if (workers
->RemoveElement(&aWorkerPrivate
)) {
1279 MOZ_ASSERT(!workers
->Contains(&aWorkerPrivate
),
1280 "Added worker more than once!");
1282 return workers
->IsEmpty();
1287 } else if (aWorkerPrivate
.IsDedicatedWorker()) {
1289 nsPIDOMWindowInner
* window
= aWorkerPrivate
.GetWindow();
1290 if (auto entry
= mWindowMap
.Lookup(window
)) {
1291 MOZ_ALWAYS_TRUE(entry
.Data()->RemoveElement(&aWorkerPrivate
));
1292 if (entry
.Data()->IsEmpty()) {
1296 MOZ_ASSERT_UNREACHABLE("window is not in mWindowMap");
1300 if (queuedWorker
&& !ScheduleWorker(*queuedWorker
)) {
1301 UnregisterWorker(*queuedWorker
);
1305 bool RuntimeService::ScheduleWorker(WorkerPrivate
& aWorkerPrivate
) {
1306 if (!aWorkerPrivate
.Start()) {
1307 // This is ok, means that we didn't need to make a thread for this worker.
1311 const WorkerThreadFriendKey friendKey
;
1313 SafeRefPtr
<WorkerThread
> thread
= WorkerThread::Create(friendKey
);
1315 UnregisterWorker(aWorkerPrivate
);
1319 if (NS_FAILED(thread
->SetPriority(nsISupportsPriority::PRIORITY_NORMAL
))) {
1320 NS_WARNING("Could not set the thread's priority!");
1323 aWorkerPrivate
.SetThread(thread
.unsafeGetRawPtr());
1324 JSContext
* cx
= CycleCollectedJSContext::Get()->Context();
1325 nsCOMPtr
<nsIRunnable
> runnable
= new WorkerThreadPrimaryRunnable(
1326 &aWorkerPrivate
, thread
.clonePtr(), JS_GetParentRuntime(cx
));
1328 thread
->DispatchPrimaryRunnable(friendKey
, runnable
.forget()))) {
1329 UnregisterWorker(aWorkerPrivate
);
1336 nsresult
RuntimeService::Init() {
1337 AssertIsOnMainThread();
1339 nsLayoutStatics::AddRef();
1341 // Initialize JSSettings.
1342 sDefaultJSSettings
= new JSSettings();
1343 SetDefaultJSGCSettings(JSGC_MAX_BYTES
, Some(WORKER_DEFAULT_RUNTIME_HEAPSIZE
));
1344 SetDefaultJSGCSettings(JSGC_ALLOCATION_THRESHOLD
,
1345 Some(WORKER_DEFAULT_ALLOCATION_THRESHOLD
));
1347 // nsIStreamTransportService is thread-safe but it must be initialized on the
1348 // main-thread. FileReader needs it, so, let's initialize it now.
1350 nsCOMPtr
<nsIStreamTransportService
> sts
=
1351 do_GetService(kStreamTransportServiceCID
, &rv
);
1352 NS_ENSURE_TRUE(sts
, NS_ERROR_FAILURE
);
1354 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
1355 NS_ENSURE_TRUE(obs
, NS_ERROR_FAILURE
);
1357 rv
= obs
->AddObserver(this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID
, false);
1358 NS_ENSURE_SUCCESS(rv
, rv
);
1360 rv
= obs
->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
, false);
1361 NS_ENSURE_SUCCESS(rv
, rv
);
1365 if (NS_FAILED(obs
->AddObserver(this, GC_REQUEST_OBSERVER_TOPIC
, false))) {
1366 NS_WARNING("Failed to register for GC request notifications!");
1369 if (NS_FAILED(obs
->AddObserver(this, CC_REQUEST_OBSERVER_TOPIC
, false))) {
1370 NS_WARNING("Failed to register for CC request notifications!");
1374 obs
->AddObserver(this, MEMORY_PRESSURE_OBSERVER_TOPIC
, false))) {
1375 NS_WARNING("Failed to register for memory pressure notifications!");
1379 obs
->AddObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC
, false))) {
1380 NS_WARNING("Failed to register for offline notification event!");
1383 MOZ_ASSERT(!gRuntimeServiceDuringInit
, "This should be false!");
1384 gRuntimeServiceDuringInit
= true;
1386 #define WORKER_PREF(name, callback) \
1387 NS_FAILED(Preferences::RegisterCallbackAndCall(callback, name))
1389 if (NS_FAILED(Preferences::RegisterPrefixCallbackAndCall(
1390 LoadJSGCMemoryOptions
,
1391 PREF_JS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX
)) ||
1393 NS_FAILED(Preferences::RegisterCallback(
1394 LoadGCZealOptions
, PREF_JS_OPTIONS_PREFIX PREF_GCZEAL
)) ||
1396 WORKER_PREF("intl.accept_languages", PrefLanguagesChanged
) ||
1397 WORKER_PREF("general.appversion.override", AppVersionOverrideChanged
) ||
1398 WORKER_PREF("general.platform.override", PlatformOverrideChanged
) ||
1399 NS_FAILED(Preferences::RegisterPrefixCallbackAndCall(
1400 LoadContextOptions
, PREF_JS_OPTIONS_PREFIX
))) {
1401 NS_WARNING("Failed to register pref callbacks!");
1406 MOZ_ASSERT(gRuntimeServiceDuringInit
, "Should be true!");
1407 gRuntimeServiceDuringInit
= false;
1409 int32_t maxPerDomain
=
1410 Preferences::GetInt(PREF_WORKERS_MAX_PER_DOMAIN
, MAX_WORKERS_PER_DOMAIN
);
1411 gMaxWorkersPerDomain
= std::max(0, maxPerDomain
);
1413 if (NS_WARN_IF(!IndexedDatabaseManager::GetOrCreate())) {
1414 return NS_ERROR_UNEXPECTED
;
1417 // PerformanceService must be initialized on the main-thread.
1418 PerformanceService::GetOrCreate();
1423 void RuntimeService::Shutdown() {
1424 AssertIsOnMainThread();
1426 MOZ_ASSERT(!mShuttingDown
);
1427 // That's it, no more workers.
1428 mShuttingDown
= true;
1430 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
1431 NS_WARNING_ASSERTION(obs
, "Failed to get observer service?!");
1433 // Tell anyone that cares that they're about to lose worker support.
1434 if (obs
&& NS_FAILED(obs
->NotifyObservers(nullptr, WORKERS_SHUTDOWN_TOPIC
,
1436 NS_WARNING("NotifyObservers failed!");
1440 AutoTArray
<WorkerPrivate
*, 100> workers
;
1443 MutexAutoLock
lock(mMutex
);
1445 AddAllTopLevelWorkersToArray(workers
);
1448 // Cancel all top-level workers.
1449 for (const auto& worker
: workers
) {
1450 if (!worker
->Cancel()) {
1451 NS_WARNING("Failed to cancel worker!");
1456 sDefaultJSSettings
= nullptr;
1461 class DumpCrashInfoRunnable final
: public WorkerControlRunnable
{
1463 explicit DumpCrashInfoRunnable(WorkerPrivate
* aWorkerPrivate
)
1464 : WorkerControlRunnable(aWorkerPrivate
, "DumpCrashInfoRunnable",
1466 mMonitor("DumpCrashInfoRunnable::mMonitor") {}
1468 bool WorkerRun(JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
) override
{
1469 MonitorAutoLock
lock(mMonitor
);
1471 aWorkerPrivate
->DumpCrashInformation(mMsg
);
1478 nsresult
Cancel() override
{
1479 MonitorAutoLock
lock(mMonitor
);
1481 mMsg
.Assign("Canceled");
1489 bool DispatchAndWait() {
1490 MonitorAutoLock
lock(mMonitor
);
1493 // The worker is already dead but the main thread still didn't remove it
1494 // from RuntimeService's registry.
1498 // To avoid any possibility of process hangs we never receive reports on
1499 // we give the worker 1sec to react.
1500 lock
.Wait(TimeDuration::FromMilliseconds(1000));
1502 mMsg
.Append("NoResponse");
1508 const nsCString
& MsgData() const { return mMsg
; }
1511 bool PreDispatch(WorkerPrivate
* aWorkerPrivate
) override
{ return true; }
1513 void PostDispatch(WorkerPrivate
* aWorkerPrivate
,
1514 bool aDispatchResult
) override
{}
1516 Monitor mMonitor MOZ_UNANNOTATED
;
1518 FlippedOnce
<false> mHasMsg
;
1521 struct ActiveWorkerStats
{
1522 template <uint32_t ActiveWorkerStats::*Category
>
1523 void Update(const nsTArray
<WorkerPrivate
*>& aWorkers
) {
1524 for (const auto worker
: aWorkers
) {
1525 RefPtr
<DumpCrashInfoRunnable
> runnable
=
1526 new DumpCrashInfoRunnable(worker
);
1527 if (runnable
->DispatchAndWait()) {
1528 ++(this->*Category
);
1529 mMessage
.Append(runnable
->MsgData());
1534 uint32_t mWorkers
= 0;
1535 uint32_t mServiceWorkers
= 0;
1541 void RuntimeService::CrashIfHanging() {
1542 MutexAutoLock
lock(mMutex
);
1544 // If we never wanted to shut down we cannot hang.
1545 if (!mShuttingDown
) {
1549 ActiveWorkerStats activeStats
;
1550 uint32_t inactiveWorkers
= 0;
1552 for (const auto& aData
: mDomainMap
.Values()) {
1553 activeStats
.Update
<&ActiveWorkerStats::mWorkers
>(aData
->mActiveWorkers
);
1554 activeStats
.Update
<&ActiveWorkerStats::mServiceWorkers
>(
1555 aData
->mActiveServiceWorkers
);
1557 // These might not be top-level workers...
1558 inactiveWorkers
+= std::count_if(
1559 aData
->mQueuedWorkers
.begin(), aData
->mQueuedWorkers
.end(),
1560 [](const auto* const worker
) { return !worker
->GetParent(); });
1563 if (activeStats
.mWorkers
+ activeStats
.mServiceWorkers
+ inactiveWorkers
==
1570 // A: active Workers | S: active ServiceWorkers | Q: queued Workers
1571 msg
.AppendPrintf("Workers Hanging - %d|A:%d|S:%d|Q:%d", mShuttingDown
? 1 : 0,
1572 activeStats
.mWorkers
, activeStats
.mServiceWorkers
,
1574 msg
.Append(activeStats
.mMessage
);
1576 // This string will be leaked.
1577 MOZ_CRASH_UNSAFE(strdup(msg
.BeginReading()));
1580 // This spins the event loop until all workers are finished and their threads
1581 // have been joined.
1582 void RuntimeService::Cleanup() {
1583 AssertIsOnMainThread();
1585 if (!mShuttingDown
) {
1589 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
1590 NS_WARNING_ASSERTION(obs
, "Failed to get observer service?!");
1593 MutexAutoLock
lock(mMutex
);
1595 AutoTArray
<WorkerPrivate
*, 100> workers
;
1596 AddAllTopLevelWorkersToArray(workers
);
1598 if (!workers
.IsEmpty()) {
1599 nsIThread
* currentThread
= NS_GetCurrentThread();
1600 NS_ASSERTION(currentThread
, "This should never be null!");
1602 // If the loop below takes too long, we probably have a problematic
1603 // worker. MOZ_LOG some info before the parent process forcibly
1604 // terminates us so that in the event we are a content process, the log
1605 // output can provide useful context about the workers that did not
1606 // cleanly shut down.
1607 nsCOMPtr
<nsITimer
> timer
;
1608 RefPtr
<RuntimeService
> self
= this;
1609 nsresult rv
= NS_NewTimerWithCallback(
1610 getter_AddRefs(timer
),
1611 [self
](nsITimer
*) { self
->DumpRunningWorkers(); },
1612 TimeDuration::FromSeconds(1), nsITimer::TYPE_ONE_SHOT
,
1613 "RuntimeService::WorkerShutdownDump");
1614 Unused
<< NS_WARN_IF(NS_FAILED(rv
));
1616 // And make sure all their final messages have run and all their threads
1618 while (mDomainMap
.Count()) {
1619 MutexAutoUnlock
unlock(mMutex
);
1621 if (!NS_ProcessNextEvent(currentThread
)) {
1622 NS_WARNING("Something bad happened!");
1627 if (NS_SUCCEEDED(rv
)) {
1633 NS_ASSERTION(!mWindowMap
.Count(), "All windows should have been released!");
1635 #define WORKER_PREF(name, callback) \
1636 NS_FAILED(Preferences::UnregisterCallback(callback, name))
1639 if (NS_FAILED(Preferences::UnregisterPrefixCallback(
1640 LoadContextOptions
, PREF_JS_OPTIONS_PREFIX
)) ||
1641 WORKER_PREF("intl.accept_languages", PrefLanguagesChanged
) ||
1642 WORKER_PREF("general.appversion.override", AppVersionOverrideChanged
) ||
1643 WORKER_PREF("general.platform.override", PlatformOverrideChanged
) ||
1645 NS_FAILED(Preferences::UnregisterCallback(
1646 LoadGCZealOptions
, PREF_JS_OPTIONS_PREFIX PREF_GCZEAL
)) ||
1648 NS_FAILED(Preferences::UnregisterPrefixCallback(
1649 LoadJSGCMemoryOptions
,
1650 PREF_JS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX
))) {
1651 NS_WARNING("Failed to unregister pref callbacks!");
1657 if (NS_FAILED(obs
->RemoveObserver(this, GC_REQUEST_OBSERVER_TOPIC
))) {
1658 NS_WARNING("Failed to unregister for GC request notifications!");
1661 if (NS_FAILED(obs
->RemoveObserver(this, CC_REQUEST_OBSERVER_TOPIC
))) {
1662 NS_WARNING("Failed to unregister for CC request notifications!");
1666 obs
->RemoveObserver(this, MEMORY_PRESSURE_OBSERVER_TOPIC
))) {
1667 NS_WARNING("Failed to unregister for memory pressure notifications!");
1671 obs
->RemoveObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC
))) {
1672 NS_WARNING("Failed to unregister for offline notification event!");
1674 obs
->RemoveObserver(this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID
);
1675 obs
->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
);
1680 nsLayoutStatics::Release();
1683 void RuntimeService::AddAllTopLevelWorkersToArray(
1684 nsTArray
<WorkerPrivate
*>& aWorkers
) {
1685 for (const auto& aData
: mDomainMap
.Values()) {
1687 for (const auto& activeWorker
: aData
->mActiveWorkers
) {
1688 MOZ_ASSERT(!activeWorker
->GetParent(),
1689 "Shouldn't have a parent in this list!");
1691 for (const auto& activeServiceWorker
: aData
->mActiveServiceWorkers
) {
1692 MOZ_ASSERT(!activeServiceWorker
->GetParent(),
1693 "Shouldn't have a parent in this list!");
1697 aWorkers
.AppendElements(aData
->mActiveWorkers
);
1698 aWorkers
.AppendElements(aData
->mActiveServiceWorkers
);
1700 // These might not be top-level workers...
1701 std::copy_if(aData
->mQueuedWorkers
.begin(), aData
->mQueuedWorkers
.end(),
1702 MakeBackInserter(aWorkers
),
1703 [](const auto& worker
) { return !worker
->GetParent(); });
1707 nsTArray
<WorkerPrivate
*> RuntimeService::GetWorkersForWindow(
1708 const nsPIDOMWindowInner
& aWindow
) const {
1709 AssertIsOnMainThread();
1711 nsTArray
<WorkerPrivate
*> result
;
1712 if (nsTArray
<WorkerPrivate
*>* const workers
= mWindowMap
.Get(&aWindow
)) {
1713 NS_ASSERTION(!workers
->IsEmpty(), "Should have been removed!");
1714 result
.AppendElements(*workers
);
1719 void RuntimeService::CancelWorkersForWindow(const nsPIDOMWindowInner
& aWindow
) {
1720 AssertIsOnMainThread();
1722 for (WorkerPrivate
* const worker
: GetWorkersForWindow(aWindow
)) {
1723 MOZ_ASSERT(!worker
->IsSharedWorker());
1728 void RuntimeService::FreezeWorkersForWindow(const nsPIDOMWindowInner
& aWindow
) {
1729 AssertIsOnMainThread();
1731 for (WorkerPrivate
* const worker
: GetWorkersForWindow(aWindow
)) {
1732 MOZ_ASSERT(!worker
->IsSharedWorker());
1733 worker
->Freeze(&aWindow
);
1737 void RuntimeService::ThawWorkersForWindow(const nsPIDOMWindowInner
& aWindow
) {
1738 AssertIsOnMainThread();
1740 for (WorkerPrivate
* const worker
: GetWorkersForWindow(aWindow
)) {
1741 MOZ_ASSERT(!worker
->IsSharedWorker());
1742 worker
->Thaw(&aWindow
);
1746 void RuntimeService::SuspendWorkersForWindow(
1747 const nsPIDOMWindowInner
& aWindow
) {
1748 AssertIsOnMainThread();
1750 for (WorkerPrivate
* const worker
: GetWorkersForWindow(aWindow
)) {
1751 MOZ_ASSERT(!worker
->IsSharedWorker());
1752 worker
->ParentWindowPaused();
1756 void RuntimeService::ResumeWorkersForWindow(const nsPIDOMWindowInner
& aWindow
) {
1757 AssertIsOnMainThread();
1759 for (WorkerPrivate
* const worker
: GetWorkersForWindow(aWindow
)) {
1760 MOZ_ASSERT(!worker
->IsSharedWorker());
1761 worker
->ParentWindowResumed();
1765 void RuntimeService::PropagateStorageAccessPermissionGranted(
1766 const nsPIDOMWindowInner
& aWindow
) {
1767 AssertIsOnMainThread();
1768 MOZ_ASSERT_IF(aWindow
.GetExtantDoc(), aWindow
.GetExtantDoc()
1769 ->CookieJarSettings()
1770 ->GetRejectThirdPartyContexts());
1772 for (WorkerPrivate
* const worker
: GetWorkersForWindow(aWindow
)) {
1773 worker
->PropagateStorageAccessPermissionGranted();
1777 template <typename Func
>
1778 void RuntimeService::BroadcastAllWorkers(const Func
& aFunc
) {
1779 AssertIsOnMainThread();
1781 AutoTArray
<WorkerPrivate
*, 100> workers
;
1783 MutexAutoLock
lock(mMutex
);
1785 AddAllTopLevelWorkersToArray(workers
);
1788 for (const auto& worker
: workers
) {
1793 void RuntimeService::UpdateAllWorkerContextOptions() {
1794 BroadcastAllWorkers([](auto& worker
) {
1795 worker
.UpdateContextOptions(sDefaultJSSettings
->contextOptions
);
1799 void RuntimeService::UpdateAppVersionOverridePreference(
1800 const nsAString
& aValue
) {
1801 AssertIsOnMainThread();
1802 mNavigatorProperties
.mAppVersionOverridden
= aValue
;
1805 void RuntimeService::UpdatePlatformOverridePreference(const nsAString
& aValue
) {
1806 AssertIsOnMainThread();
1807 mNavigatorProperties
.mPlatformOverridden
= aValue
;
1810 void RuntimeService::UpdateAllWorkerLanguages(
1811 const nsTArray
<nsString
>& aLanguages
) {
1812 MOZ_ASSERT(NS_IsMainThread());
1814 mNavigatorProperties
.mLanguages
= aLanguages
.Clone();
1815 BroadcastAllWorkers(
1816 [&aLanguages
](auto& worker
) { worker
.UpdateLanguages(aLanguages
); });
1819 void RuntimeService::UpdateAllWorkerMemoryParameter(JSGCParamKey aKey
,
1820 Maybe
<uint32_t> aValue
) {
1821 BroadcastAllWorkers([aKey
, aValue
](auto& worker
) {
1822 worker
.UpdateJSWorkerMemoryParameter(aKey
, aValue
);
1827 void RuntimeService::UpdateAllWorkerGCZeal() {
1828 BroadcastAllWorkers([](auto& worker
) {
1829 worker
.UpdateGCZeal(sDefaultJSSettings
->gcZeal
,
1830 sDefaultJSSettings
->gcZealFrequency
);
1835 void RuntimeService::SetLowMemoryStateAllWorkers(bool aState
) {
1836 BroadcastAllWorkers(
1837 [aState
](auto& worker
) { worker
.SetLowMemoryState(aState
); });
1840 void RuntimeService::GarbageCollectAllWorkers(bool aShrinking
) {
1841 BroadcastAllWorkers(
1842 [aShrinking
](auto& worker
) { worker
.GarbageCollect(aShrinking
); });
1845 void RuntimeService::CycleCollectAllWorkers() {
1846 BroadcastAllWorkers([](auto& worker
) { worker
.CycleCollect(); });
1849 void RuntimeService::SendOfflineStatusChangeEventToAllWorkers(bool aIsOffline
) {
1850 BroadcastAllWorkers([aIsOffline
](auto& worker
) {
1851 worker
.OfflineStatusChangeEvent(aIsOffline
);
1855 void RuntimeService::MemoryPressureAllWorkers() {
1856 BroadcastAllWorkers([](auto& worker
) { worker
.MemoryPressure(); });
1859 uint32_t RuntimeService::ClampedHardwareConcurrency(
1860 bool aShouldResistFingerprinting
) const {
1861 // The Firefox Hardware Report says 70% of Firefox users have exactly 2 cores.
1862 // When the resistFingerprinting pref is set, we want to blend into the crowd
1863 // so spoof navigator.hardwareConcurrency = 2 to reduce user uniqueness.
1864 if (MOZ_UNLIKELY(aShouldResistFingerprinting
)) {
1868 // This needs to be atomic, because multiple workers, and even mainthread,
1869 // could race to initialize it at once.
1870 static Atomic
<uint32_t> unclampedHardwareConcurrency
;
1872 // No need to loop here: if compareExchange fails, that just means that some
1873 // other worker has initialized numberOfProcessors, so we're good to go.
1874 if (!unclampedHardwareConcurrency
) {
1875 int32_t numberOfProcessors
= 0;
1876 #if defined(XP_MACOSX)
1877 if (nsMacUtilsImpl::IsTCSMAvailable()) {
1878 // On failure, zero is returned from GetPhysicalCPUCount()
1879 // and we fallback to PR_GetNumberOfProcessors below.
1880 numberOfProcessors
= nsMacUtilsImpl::GetPhysicalCPUCount();
1883 if (numberOfProcessors
== 0) {
1884 numberOfProcessors
= PR_GetNumberOfProcessors();
1886 if (numberOfProcessors
<= 0) {
1887 numberOfProcessors
= 1; // Must be one there somewhere
1889 Unused
<< unclampedHardwareConcurrency
.compareExchange(0,
1890 numberOfProcessors
);
1893 return std::min(uint32_t(unclampedHardwareConcurrency
),
1894 StaticPrefs::dom_maxHardwareConcurrency());
1898 NS_IMPL_ISUPPORTS(RuntimeService
, nsIObserver
)
1902 RuntimeService::Observe(nsISupports
* aSubject
, const char* aTopic
,
1903 const char16_t
* aData
) {
1904 AssertIsOnMainThread();
1906 if (!strcmp(aTopic
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
)) {
1910 if (!strcmp(aTopic
, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID
)) {
1914 if (!strcmp(aTopic
, GC_REQUEST_OBSERVER_TOPIC
)) {
1915 GarbageCollectAllWorkers(/* shrinking = */ false);
1918 if (!strcmp(aTopic
, CC_REQUEST_OBSERVER_TOPIC
)) {
1919 CycleCollectAllWorkers();
1922 if (!strcmp(aTopic
, MEMORY_PRESSURE_OBSERVER_TOPIC
)) {
1923 nsDependentString
data(aData
);
1924 // Don't continue to GC/CC if we are in an ongoing low-memory state since
1925 // its very slow and it likely won't help us anyway.
1926 if (data
.EqualsLiteral(LOW_MEMORY_ONGOING_DATA
)) {
1929 if (data
.EqualsLiteral(LOW_MEMORY_DATA
)) {
1930 SetLowMemoryStateAllWorkers(true);
1932 GarbageCollectAllWorkers(/* shrinking = */ true);
1933 CycleCollectAllWorkers();
1934 MemoryPressureAllWorkers();
1937 if (!strcmp(aTopic
, MEMORY_PRESSURE_STOP_OBSERVER_TOPIC
)) {
1938 SetLowMemoryStateAllWorkers(false);
1941 if (!strcmp(aTopic
, NS_IOSERVICE_OFFLINE_STATUS_TOPIC
)) {
1942 SendOfflineStatusChangeEventToAllWorkers(NS_IsOffline());
1946 MOZ_ASSERT_UNREACHABLE("Unknown observer topic!");
1951 const char* WorkerKindToString(WorkerKind kind
) {
1953 case WorkerKindDedicated
:
1955 case WorkerKindShared
:
1957 case WorkerKindService
:
1960 NS_WARNING("Unknown worker type");
1961 return "unknown worker type";
1965 void LogWorker(WorkerPrivate
* worker
, const char* category
) {
1966 AssertIsOnMainThread();
1968 SHUTDOWN_LOG(("Found %s (%s): %s", category
,
1969 WorkerKindToString(worker
->Kind()),
1970 NS_ConvertUTF16toUTF8(worker
->ScriptURL()).get()));
1972 if (worker
->Kind() == WorkerKindService
) {
1973 SHUTDOWN_LOG(("Scope: %s", worker
->ServiceWorkerScope().get()));
1977 worker
->GetPrincipal()->GetOrigin(origin
);
1978 SHUTDOWN_LOG(("Principal: %s", origin
.get()));
1980 nsCString loadingOrigin
;
1981 worker
->GetLoadingPrincipal()->GetOrigin(loadingOrigin
);
1982 SHUTDOWN_LOG(("LoadingPrincipal: %s", loadingOrigin
.get()));
1984 RefPtr
<DumpCrashInfoRunnable
> runnable
= new DumpCrashInfoRunnable(worker
);
1985 if (runnable
->DispatchAndWait()) {
1986 SHUTDOWN_LOG(("CrashInfo: %s", runnable
->MsgData().get()));
1988 SHUTDOWN_LOG(("CrashInfo: dispatch failed"));
1993 void RuntimeService::DumpRunningWorkers() {
1994 // Temporarily set the LogLevel high enough to be certain the messages are
1996 LogModule
* module
= gWorkerShutdownDumpLog
;
1997 LogLevel prevLevel
= module
->Level();
1999 const auto cleanup
=
2000 MakeScopeExit([module
, prevLevel
] { module
->SetLevel(prevLevel
); });
2002 if (prevLevel
< LogLevel::Debug
) {
2003 module
->SetLevel(LogLevel::Debug
);
2006 MutexAutoLock
lock(mMutex
);
2008 for (const auto& info
: mDomainMap
.Values()) {
2009 for (WorkerPrivate
* worker
: info
->mActiveWorkers
) {
2010 LogWorker(worker
, "ActiveWorker");
2013 for (WorkerPrivate
* worker
: info
->mActiveServiceWorkers
) {
2014 LogWorker(worker
, "ActiveServiceWorker");
2017 for (WorkerPrivate
* worker
: info
->mQueuedWorkers
) {
2018 LogWorker(worker
, "QueuedWorker");
2023 bool LogViolationDetailsRunnable::MainThreadRun() {
2024 AssertIsOnMainThread();
2026 nsIContentSecurityPolicy
* csp
= mWorkerPrivate
->GetCsp();
2028 csp
->LogViolationDetails(mViolationType
,
2029 nullptr, // triggering element
2030 mWorkerPrivate
->CSPEventListener(), mFileName
,
2031 mScriptSample
, mLineNum
, mColumnNum
, u
""_ns
,
2038 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
2040 MOZ_CAN_RUN_SCRIPT_BOUNDARY
2042 WorkerThreadPrimaryRunnable::Run() {
2043 NS_ConvertUTF16toUTF8
url(mWorkerPrivate
->ScriptURL());
2044 AUTO_PROFILER_LABEL_DYNAMIC_CSTR("WorkerThreadPrimaryRunnable::Run", OTHER
,
2047 using mozilla::ipc::BackgroundChild
;
2050 auto failureCleanup
= MakeScopeExit([&]() {
2051 // The creation of threadHelper above is the point at which a worker is
2052 // considered to have run, because the `mPreStartRunnables` are all
2053 // re-dispatched after `mThread` is set. We need to let the WorkerPrivate
2054 // know so it can clean up the various event loops and delete the worker.
2055 mWorkerPrivate
->RunLoopNeverRan();
2058 mWorkerPrivate
->SetWorkerPrivateInWorkerThread(mThread
.unsafeGetRawPtr());
2060 const auto threadCleanup
= MakeScopeExit([&] {
2061 // This must be called before ScheduleDeletion, which is either called
2062 // from failureCleanup leaving scope, or from the outer scope.
2063 mWorkerPrivate
->ResetWorkerPrivateInWorkerThread();
2066 mWorkerPrivate
->AssertIsOnWorkerThread();
2068 // This needs to be initialized on the worker thread before being used on
2069 // the main thread and calling BackgroundChild::GetOrCreateForCurrentThread
2070 // exposes it to the main thread.
2071 mWorkerPrivate
->EnsurePerformanceStorage();
2073 if (NS_WARN_IF(!BackgroundChild::GetOrCreateForCurrentThread())) {
2074 return NS_ERROR_FAILURE
;
2077 nsWeakPtr globalScopeSentinel
;
2078 nsWeakPtr debuggerScopeSentinel
;
2079 // Never use the following pointers without checking their corresponding
2080 // nsWeakPtr sentinel, defined above and initialized after DoRunLoop ends.
2081 WorkerGlobalScopeBase
* globalScopeRawPtr
= nullptr;
2082 WorkerGlobalScopeBase
* debuggerScopeRawPtr
= nullptr;
2084 nsCycleCollector_startup();
2086 auto context
= MakeUnique
<WorkerJSContext
>(mWorkerPrivate
);
2087 nsresult rv
= context
->Initialize(mParentRuntime
);
2088 if (NS_WARN_IF(NS_FAILED(rv
))) {
2092 JSContext
* cx
= context
->Context();
2094 if (!InitJSContextForWorker(mWorkerPrivate
, cx
)) {
2095 return NS_ERROR_FAILURE
;
2098 failureCleanup
.release();
2101 PROFILER_SET_JS_CONTEXT(cx
);
2104 // We're on the worker thread here, and WorkerPrivate's refcounting is
2105 // non-threadsafe: you can only do it on the parent thread. What that
2106 // means in practice is that we're relying on it being kept alive
2107 // while we run. Hopefully.
2108 MOZ_KnownLive(mWorkerPrivate
)->DoRunLoop(cx
);
2109 // The AutoJSAPI in DoRunLoop should have reported any exceptions left
2111 MOZ_ASSERT(!JS_IsExceptionPending(cx
));
2114 mWorkerPrivate
->ShutdownModuleLoader();
2116 mWorkerPrivate
->RunShutdownTasks();
2118 BackgroundChild::CloseForCurrentThread();
2120 PROFILER_CLEAR_JS_CONTEXT();
2123 // There may still be runnables on the debugger event queue that hold a
2124 // strong reference to the debugger global scope. These runnables are not
2125 // visible to the cycle collector, so we need to make sure to clear the
2126 // debugger event queue before we try to destroy the context. If we don't,
2127 // the garbage collector will crash.
2128 // Note that this just releases the runnables and does not execute them.
2129 mWorkerPrivate
->ClearDebuggerEventQueue();
2131 // Before shutting down the cycle collector we need to do one more pass
2132 // through the event loop to clean up any C++ objects that need deferred
2134 NS_ProcessPendingEvents(nullptr);
2136 // At this point we expect the scopes to be alive if they were ever
2137 // created successfully, keep weak references and set up the sentinels.
2138 globalScopeRawPtr
= mWorkerPrivate
->GlobalScope();
2139 if (globalScopeRawPtr
) {
2140 globalScopeSentinel
= do_GetWeakReference(globalScopeRawPtr
);
2142 MOZ_ASSERT(!globalScopeRawPtr
|| globalScopeSentinel
);
2143 debuggerScopeRawPtr
= mWorkerPrivate
->DebuggerGlobalScope();
2144 if (debuggerScopeRawPtr
) {
2145 debuggerScopeSentinel
= do_GetWeakReference(debuggerScopeRawPtr
);
2147 MOZ_ASSERT(!debuggerScopeRawPtr
|| debuggerScopeSentinel
);
2149 // To our best knowledge nobody should need a reference to our globals
2150 // now (NS_ProcessPendingEvents is the last expected potential usage)
2151 // and we can unroot them.
2152 mWorkerPrivate
->UnrootGlobalScopes();
2154 // Perform a full GC until we collect the main worker global and CC,
2155 // which should break all cycles that touch JS.
2156 bool repeatGCCC
= true;
2157 while (repeatGCCC
) {
2158 JS::PrepareForFullGC(cx
);
2159 JS::NonIncrementalGC(cx
, JS::GCOptions::Shutdown
,
2160 JS::GCReason::WORKER_SHUTDOWN
);
2162 // If we CCed something or got new events as a side effect, repeat.
2163 repeatGCCC
= mWorkerPrivate
->isLastCCCollectedAnything() ||
2164 NS_HasPendingEvents(nullptr);
2165 NS_ProcessPendingEvents(nullptr);
2168 // The worker global should be unrooted and the shutdown of cycle
2169 // collection should break all the remaining cycles.
2170 nsCycleCollector_shutdown();
2172 // If ever the CC shutdown run caused side effects, process them.
2173 NS_ProcessPendingEvents(nullptr);
2175 // Now WorkerJSContext goes out of scope. Do not use any cycle
2176 // collectable objects nor JS after this point!
2179 // Check sentinels if we actually removed all global scope references.
2180 // In case use the earlier set-aside raw pointers to not mess with the
2181 // ref counting after the cycle collector has gone away.
2182 if (globalScopeSentinel
) {
2183 MOZ_ASSERT(!globalScopeSentinel
->IsAlive());
2184 if (NS_WARN_IF(globalScopeSentinel
->IsAlive())) {
2185 globalScopeRawPtr
->NoteWorkerTerminated();
2186 globalScopeRawPtr
= nullptr;
2189 if (debuggerScopeSentinel
) {
2190 MOZ_ASSERT(!debuggerScopeSentinel
->IsAlive());
2191 if (NS_WARN_IF(debuggerScopeSentinel
->IsAlive())) {
2192 debuggerScopeRawPtr
->NoteWorkerTerminated();
2193 debuggerScopeRawPtr
= nullptr;
2198 mWorkerPrivate
->ScheduleDeletion(WorkerPrivate::WorkerRan
);
2200 // It is no longer safe to touch mWorkerPrivate.
2201 mWorkerPrivate
= nullptr;
2203 // Now recycle this thread.
2204 nsCOMPtr
<nsIEventTarget
> mainTarget
= GetMainThreadSerialEventTarget();
2205 MOZ_ASSERT(mainTarget
);
2207 RefPtr
<FinishedRunnable
> finishedRunnable
=
2208 new FinishedRunnable(std::move(mThread
));
2209 MOZ_ALWAYS_SUCCEEDS(
2210 mainTarget
->Dispatch(finishedRunnable
, NS_DISPATCH_NORMAL
));
2216 WorkerThreadPrimaryRunnable::FinishedRunnable::Run() {
2217 AssertIsOnMainThread();
2219 SafeRefPtr
<WorkerThread
> thread
= std::move(mThread
);
2220 if (thread
->ShutdownRequired()) {
2221 MOZ_ALWAYS_SUCCEEDS(thread
->Shutdown());
2227 } // namespace workerinternals
2229 // This is mostly for invoking within a debugger.
2230 void DumpRunningWorkers() {
2231 RuntimeService
* runtimeService
= RuntimeService::GetService();
2232 if (runtimeService
) {
2233 runtimeService
->DumpRunningWorkers();
2235 NS_WARNING("RuntimeService not found");
2239 void CancelWorkersForWindow(const nsPIDOMWindowInner
& aWindow
) {
2240 AssertIsOnMainThread();
2241 RuntimeService
* runtime
= RuntimeService::GetService();
2243 runtime
->CancelWorkersForWindow(aWindow
);
2247 void FreezeWorkersForWindow(const nsPIDOMWindowInner
& aWindow
) {
2248 AssertIsOnMainThread();
2249 RuntimeService
* runtime
= RuntimeService::GetService();
2251 runtime
->FreezeWorkersForWindow(aWindow
);
2255 void ThawWorkersForWindow(const nsPIDOMWindowInner
& aWindow
) {
2256 AssertIsOnMainThread();
2257 RuntimeService
* runtime
= RuntimeService::GetService();
2259 runtime
->ThawWorkersForWindow(aWindow
);
2263 void SuspendWorkersForWindow(const nsPIDOMWindowInner
& aWindow
) {
2264 AssertIsOnMainThread();
2265 RuntimeService
* runtime
= RuntimeService::GetService();
2267 runtime
->SuspendWorkersForWindow(aWindow
);
2271 void ResumeWorkersForWindow(const nsPIDOMWindowInner
& aWindow
) {
2272 AssertIsOnMainThread();
2273 RuntimeService
* runtime
= RuntimeService::GetService();
2275 runtime
->ResumeWorkersForWindow(aWindow
);
2279 void PropagateStorageAccessPermissionGrantedToWorkers(
2280 const nsPIDOMWindowInner
& aWindow
) {
2281 AssertIsOnMainThread();
2282 MOZ_ASSERT_IF(aWindow
.GetExtantDoc(), aWindow
.GetExtantDoc()
2283 ->CookieJarSettings()
2284 ->GetRejectThirdPartyContexts());
2286 RuntimeService
* runtime
= RuntimeService::GetService();
2288 runtime
->PropagateStorageAccessPermissionGranted(aWindow
);
2292 WorkerPrivate
* GetWorkerPrivateFromContext(JSContext
* aCx
) {
2293 MOZ_ASSERT(!NS_IsMainThread());
2296 CycleCollectedJSContext
* ccjscx
= CycleCollectedJSContext::GetFor(aCx
);
2301 WorkerJSContext
* workerjscx
= ccjscx
->GetAsWorkerJSContext();
2302 // GetWorkerPrivateFromContext is called only for worker contexts. The
2303 // context private is cleared early in ~CycleCollectedJSContext() and so
2304 // GetFor() returns null above if called after ccjscx is no longer a
2306 MOZ_ASSERT(workerjscx
);
2307 return workerjscx
->GetWorkerPrivate();
2310 WorkerPrivate
* GetCurrentThreadWorkerPrivate() {
2311 if (NS_IsMainThread()) {
2315 CycleCollectedJSContext
* ccjscx
= CycleCollectedJSContext::Get();
2320 WorkerJSContext
* workerjscx
= ccjscx
->GetAsWorkerJSContext();
2321 // Even when GetCurrentThreadWorkerPrivate() is called on worker
2322 // threads, the ccjscx will no longer be a WorkerJSContext if called from
2323 // stable state events during ~CycleCollectedJSContext().
2328 return workerjscx
->GetWorkerPrivate();
2331 bool IsCurrentThreadRunningWorker() {
2332 return !NS_IsMainThread() && !!GetCurrentThreadWorkerPrivate();
2335 bool IsCurrentThreadRunningChromeWorker() {
2336 WorkerPrivate
* wp
= GetCurrentThreadWorkerPrivate();
2337 return wp
&& wp
->UsesSystemPrincipal();
2340 JSContext
* GetCurrentWorkerThreadJSContext() {
2341 WorkerPrivate
* wp
= GetCurrentThreadWorkerPrivate();
2345 return wp
->GetJSContext();
2348 JSObject
* GetCurrentThreadWorkerGlobal() {
2349 WorkerPrivate
* wp
= GetCurrentThreadWorkerPrivate();
2353 WorkerGlobalScope
* scope
= wp
->GlobalScope();
2357 return scope
->GetGlobalJSObject();
2360 JSObject
* GetCurrentThreadWorkerDebuggerGlobal() {
2361 WorkerPrivate
* wp
= GetCurrentThreadWorkerPrivate();
2365 WorkerDebuggerGlobalScope
* scope
= wp
->DebuggerGlobalScope();
2369 return scope
->GetGlobalJSObject();
2373 } // namespace mozilla