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