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