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