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 "nsAutoPtr.h"
10 #include "nsIChannel.h"
11 #include "nsIContentSecurityPolicy.h"
12 #include "nsIDocument.h"
13 #include "nsIDOMChromeWindow.h"
14 #include "nsIEffectiveTLDService.h"
15 #include "nsIObserverService.h"
16 #include "nsIPrincipal.h"
17 #include "nsIScriptContext.h"
18 #include "nsIScriptError.h"
19 #include "nsIScriptSecurityManager.h"
20 #include "nsIStreamTransportService.h"
21 #include "nsISupportsPriority.h"
22 #include "nsITimer.h"
23 #include "nsIURI.h"
24 #include "nsIXULRuntime.h"
25 #include "nsPIDOMWindow.h"
26
27 #include <algorithm>
28 #include "mozilla/ipc/BackgroundChild.h"
29 #include "GeckoProfiler.h"
30 #include "jsfriendapi.h"
31 #include "mozilla/AbstractThread.h"
32 #include "mozilla/ArrayUtils.h"
33 #include "mozilla/AsyncEventDispatcher.h"
34 #include "mozilla/Atomics.h"
35 #include "mozilla/CycleCollectedJSContext.h"
36 #include "mozilla/CycleCollectedJSRuntime.h"
37 #include "mozilla/Telemetry.h"
38 #include "mozilla/TimeStamp.h"
39 #include "mozilla/dom/asmjscache/AsmJSCache.h"
40 #include "mozilla/dom/AtomList.h"
41 #include "mozilla/dom/BindingUtils.h"
42 #include "mozilla/dom/ErrorEventBinding.h"
43 #include "mozilla/dom/EventTargetBinding.h"
44 #include "mozilla/dom/FetchUtil.h"
45 #include "mozilla/dom/MessageChannel.h"
46 #include "mozilla/dom/MessageEventBinding.h"
47 #include "mozilla/dom/PerformanceService.h"
48 #include "mozilla/dom/WorkerBinding.h"
49 #include "mozilla/dom/ScriptSettings.h"
50 #include "mozilla/dom/IndexedDatabaseManager.h"
51 #include "mozilla/ipc/BackgroundChild.h"
52 #include "mozilla/DebugOnly.h"
53 #include "mozilla/Preferences.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
69 #include "Principal.h"
70 #include "SharedWorker.h"
71 #include "WorkerDebuggerManager.h"
72 #include "WorkerLoadInfo.h"
73 #include "WorkerPrivate.h"
74 #include "WorkerRunnable.h"
75 #include "WorkerScope.h"
76 #include "WorkerThread.h"
77 #include "prsystem.h"
78
79 #define WORKERS_SHUTDOWN_TOPIC "web-workers-shutdown"
80
81 namespace mozilla {
82
83 using namespace ipc;
84
85 namespace dom {
86
87 using namespace workerinternals;
88
89 namespace workerinternals {
90
91 // The size of the worker runtime heaps in bytes. May be changed via pref.
92 #define WORKER_DEFAULT_RUNTIME_HEAPSIZE 32 * 1024 * 1024
93
94 // The size of the generational GC nursery for workers, in bytes.
95 #define WORKER_DEFAULT_NURSERY_SIZE 1 * 1024 * 1024
96
97 // The size of the worker JS allocation threshold in MB. May be changed via
98 // pref.
99 #define WORKER_DEFAULT_ALLOCATION_THRESHOLD 30
100
101 // Half the size of the actual C stack, to be safe.
102 #define WORKER_CONTEXT_NATIVE_STACK_LIMIT 128 * sizeof(size_t) * 1024
103
104 // The maximum number of hardware concurrency, overridable via pref.
105 #define MAX_HARDWARE_CONCURRENCY 8
106
107 // The maximum number of threads to use for workers, overridable via pref.
108 #define MAX_WORKERS_PER_DOMAIN 512
109
110 static_assert(MAX_WORKERS_PER_DOMAIN >= 1,
111 "We should allow at least one worker per domain.");
112
113 // The default number of seconds that close handlers will be allowed to run for
114 // content workers.
115 #define MAX_SCRIPT_RUN_TIME_SEC 10
116
117 // The number of seconds that idle threads can hang around before being killed.
118 #define IDLE_THREAD_TIMEOUT_SEC 30
119
120 // The maximum number of threads that can be idle at one time.
121 #define MAX_IDLE_THREADS 20
122
123 #define PREF_WORKERS_PREFIX "dom.workers."
124 #define PREF_WORKERS_MAX_PER_DOMAIN PREF_WORKERS_PREFIX "maxPerDomain"
125 #define PREF_WORKERS_MAX_HARDWARE_CONCURRENCY "dom.maxHardwareConcurrency"
126
127 #define PREF_MAX_SCRIPT_RUN_TIME_CONTENT "dom.max_script_run_time"
128 #define PREF_MAX_SCRIPT_RUN_TIME_CHROME "dom.max_chrome_script_run_time"
129
130 #define GC_REQUEST_OBSERVER_TOPIC "child-gc-request"
131 #define CC_REQUEST_OBSERVER_TOPIC "child-cc-request"
132 #define MEMORY_PRESSURE_OBSERVER_TOPIC "memory-pressure"
133
134 #define BROADCAST_ALL_WORKERS(_func, ...) \
135 PR_BEGIN_MACRO \
136 AssertIsOnMainThread(); \
137 \
138 AutoTArray<WorkerPrivate*, 100> workers; \
139 { \
140 MutexAutoLock lock(mMutex); \
141 \
142 AddAllTopLevelWorkersToArray(workers); \
143 } \
144 \
145 if (!workers.IsEmpty()) { \
146 for (uint32_t index = 0; index < workers.Length(); index++) { \
147 workers[index]->_func(__VA_ARGS__); \
148 } \
149 } \
150 PR_END_MACRO
151
152 // Prefixes for observing preference changes.
153 #define PREF_JS_OPTIONS_PREFIX "javascript.options."
154 #define PREF_WORKERS_OPTIONS_PREFIX PREF_WORKERS_PREFIX "options."
155 #define PREF_MEM_OPTIONS_PREFIX "mem."
156 #define PREF_GCZEAL "gcZeal"
157
158 static NS_DEFINE_CID(kStreamTransportServiceCID, NS_STREAMTRANSPORTSERVICE_CID);
159
160 namespace {
161
162 const uint32_t kNoIndex = uint32_t(-1);
163
164 uint32_t gMaxWorkersPerDomain = MAX_WORKERS_PER_DOMAIN;
165 uint32_t gMaxHardwareConcurrency = MAX_HARDWARE_CONCURRENCY;
166
167 // Does not hold an owning reference.
168 RuntimeService* gRuntimeService = nullptr;
169
170 // Only true during the call to Init.
171 bool gRuntimeServiceDuringInit = false;
172
173 class LiteralRebindingCString : public nsDependentCString {
174 public:
175 template <int N>
RebindLiteral(const char (& aStr)[N])176 void RebindLiteral(const char (&aStr)[N]) {
177 Rebind(aStr, N - 1);
178 }
179 };
180
181 template <typename T>
182 struct PrefTraits;
183
184 template <>
185 struct PrefTraits<bool> {
186 typedef bool PrefValueType;
187
188 static const PrefValueType kDefaultValue = false;
189
Getmozilla::dom::workerinternals::__anonca25b2750111::PrefTraits190 static inline PrefValueType Get(const char* aPref) {
191 AssertIsOnMainThread();
192 return Preferences::GetBool(aPref);
193 }
194
Existsmozilla::dom::workerinternals::__anonca25b2750111::PrefTraits195 static inline bool Exists(const char* aPref) {
196 AssertIsOnMainThread();
197 return Preferences::GetType(aPref) == nsIPrefBranch::PREF_BOOL;
198 }
199 };
200
201 template <>
202 struct PrefTraits<int32_t> {
203 typedef int32_t PrefValueType;
204
Getmozilla::dom::workerinternals::__anonca25b2750111::PrefTraits205 static inline PrefValueType Get(const char* aPref) {
206 AssertIsOnMainThread();
207 return Preferences::GetInt(aPref);
208 }
209
Existsmozilla::dom::workerinternals::__anonca25b2750111::PrefTraits210 static inline bool Exists(const char* aPref) {
211 AssertIsOnMainThread();
212 return Preferences::GetType(aPref) == nsIPrefBranch::PREF_INT;
213 }
214 };
215
216 template <typename T>
GetWorkerPref(const nsACString & aPref,const T aDefault=PrefTraits<T>::kDefaultValue)217 T GetWorkerPref(const nsACString& aPref,
218 const T aDefault = PrefTraits<T>::kDefaultValue) {
219 AssertIsOnMainThread();
220
221 typedef PrefTraits<T> PrefHelper;
222
223 T result;
224
225 nsAutoCString prefName;
226 prefName.AssignLiteral(PREF_WORKERS_OPTIONS_PREFIX);
227 prefName.Append(aPref);
228
229 if (PrefHelper::Exists(prefName.get())) {
230 result = PrefHelper::Get(prefName.get());
231 } else {
232 prefName.AssignLiteral(PREF_JS_OPTIONS_PREFIX);
233 prefName.Append(aPref);
234
235 if (PrefHelper::Exists(prefName.get())) {
236 result = PrefHelper::Get(prefName.get());
237 } else {
238 result = aDefault;
239 }
240 }
241
242 return result;
243 }
244
LoadContextOptions(const char * aPrefName,void *)245 void LoadContextOptions(const char* aPrefName, void* /* aClosure */) {
246 AssertIsOnMainThread();
247
248 RuntimeService* rts = RuntimeService::GetService();
249 if (!rts) {
250 // May be shutting down, just bail.
251 return;
252 }
253
254 const nsDependentCString prefName(aPrefName);
255
256 // Several other pref branches will get included here so bail out if there is
257 // another callback that will handle this change.
258 if (StringBeginsWith(
259 prefName,
260 NS_LITERAL_CSTRING(PREF_JS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX)) ||
261 StringBeginsWith(
262 prefName, NS_LITERAL_CSTRING(
263 PREF_WORKERS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX))) {
264 return;
265 }
266
267 #ifdef JS_GC_ZEAL
268 if (prefName.EqualsLiteral(PREF_JS_OPTIONS_PREFIX PREF_GCZEAL) ||
269 prefName.EqualsLiteral(PREF_WORKERS_OPTIONS_PREFIX PREF_GCZEAL)) {
270 return;
271 }
272 #endif
273
274 // Context options.
275 JS::ContextOptions contextOptions;
276 contextOptions.setAsmJS(GetWorkerPref<bool>(NS_LITERAL_CSTRING("asmjs")))
277 .setWasm(GetWorkerPref<bool>(NS_LITERAL_CSTRING("wasm")))
278 .setWasmBaseline(
279 GetWorkerPref<bool>(NS_LITERAL_CSTRING("wasm_baselinejit")))
280 .setWasmIon(GetWorkerPref<bool>(NS_LITERAL_CSTRING("wasm_ionjit")))
281 .setThrowOnAsmJSValidationFailure(GetWorkerPref<bool>(
282 NS_LITERAL_CSTRING("throw_on_asmjs_validation_failure")))
283 .setBaseline(GetWorkerPref<bool>(NS_LITERAL_CSTRING("baselinejit")))
284 .setIon(GetWorkerPref<bool>(NS_LITERAL_CSTRING("ion")))
285 .setNativeRegExp(GetWorkerPref<bool>(NS_LITERAL_CSTRING("native_regexp")))
286 .setAsyncStack(GetWorkerPref<bool>(NS_LITERAL_CSTRING("asyncstack")))
287 .setWerror(GetWorkerPref<bool>(NS_LITERAL_CSTRING("werror")))
288 #ifdef FUZZING
289 .setFuzzing(GetWorkerPref<bool>(NS_LITERAL_CSTRING("fuzzing.enabled")))
290 #endif
291 .setStreams(GetWorkerPref<bool>(NS_LITERAL_CSTRING("streams")))
292 .setExtraWarnings(GetWorkerPref<bool>(NS_LITERAL_CSTRING("strict")))
293 .setArrayProtoValues(
294 GetWorkerPref<bool>(NS_LITERAL_CSTRING("array_prototype_values")));
295
296 nsCOMPtr<nsIXULRuntime> xr = do_GetService("@mozilla.org/xre/runtime;1");
297 if (xr) {
298 bool safeMode = false;
299 xr->GetInSafeMode(&safeMode);
300 if (safeMode) {
301 contextOptions.disableOptionsForSafeMode();
302 }
303 }
304
305 RuntimeService::SetDefaultContextOptions(contextOptions);
306
307 if (rts) {
308 rts->UpdateAllWorkerContextOptions();
309 }
310 }
311
312 #ifdef JS_GC_ZEAL
LoadGCZealOptions(const char *,void *)313 void LoadGCZealOptions(const char* /* aPrefName */, void* /* aClosure */) {
314 AssertIsOnMainThread();
315
316 RuntimeService* rts = RuntimeService::GetService();
317 if (!rts) {
318 // May be shutting down, just bail.
319 return;
320 }
321
322 int32_t gczeal = GetWorkerPref<int32_t>(NS_LITERAL_CSTRING(PREF_GCZEAL), -1);
323 if (gczeal < 0) {
324 gczeal = 0;
325 }
326
327 int32_t frequency =
328 GetWorkerPref<int32_t>(NS_LITERAL_CSTRING("gcZeal.frequency"), -1);
329 if (frequency < 0) {
330 frequency = JS_DEFAULT_ZEAL_FREQ;
331 }
332
333 RuntimeService::SetDefaultGCZeal(uint8_t(gczeal), uint32_t(frequency));
334
335 if (rts) {
336 rts->UpdateAllWorkerGCZeal();
337 }
338 }
339 #endif
340
UpdateCommonJSGCMemoryOption(RuntimeService * aRuntimeService,const nsACString & aPrefName,JSGCParamKey aKey)341 void UpdateCommonJSGCMemoryOption(RuntimeService* aRuntimeService,
342 const nsACString& aPrefName,
343 JSGCParamKey aKey) {
344 AssertIsOnMainThread();
345 NS_ASSERTION(!aPrefName.IsEmpty(), "Empty pref name!");
346
347 int32_t prefValue = GetWorkerPref(aPrefName, -1);
348 uint32_t value =
349 (prefValue < 0 || prefValue >= 10000) ? 0 : uint32_t(prefValue);
350
351 RuntimeService::SetDefaultJSGCSettings(aKey, value);
352
353 if (aRuntimeService) {
354 aRuntimeService->UpdateAllWorkerMemoryParameter(aKey, value);
355 }
356 }
357
UpdateOtherJSGCMemoryOption(RuntimeService * aRuntimeService,JSGCParamKey aKey,uint32_t aValue)358 void UpdateOtherJSGCMemoryOption(RuntimeService* aRuntimeService,
359 JSGCParamKey aKey, uint32_t aValue) {
360 AssertIsOnMainThread();
361
362 RuntimeService::SetDefaultJSGCSettings(aKey, aValue);
363
364 if (aRuntimeService) {
365 aRuntimeService->UpdateAllWorkerMemoryParameter(aKey, aValue);
366 }
367 }
368
LoadJSGCMemoryOptions(const char * aPrefName,void *)369 void LoadJSGCMemoryOptions(const char* aPrefName, void* /* aClosure */) {
370 AssertIsOnMainThread();
371
372 RuntimeService* rts = RuntimeService::GetService();
373
374 if (!rts) {
375 // May be shutting down, just bail.
376 return;
377 }
378
379 NS_NAMED_LITERAL_CSTRING(jsPrefix, PREF_JS_OPTIONS_PREFIX);
380 NS_NAMED_LITERAL_CSTRING(workersPrefix, PREF_WORKERS_OPTIONS_PREFIX);
381
382 const nsDependentCString fullPrefName(aPrefName);
383
384 // Pull out the string that actually distinguishes the parameter we need to
385 // change.
386 nsDependentCSubstring memPrefName;
387 if (StringBeginsWith(fullPrefName, jsPrefix)) {
388 memPrefName.Rebind(fullPrefName, jsPrefix.Length());
389 } else if (StringBeginsWith(fullPrefName, workersPrefix)) {
390 memPrefName.Rebind(fullPrefName, workersPrefix.Length());
391 } else {
392 NS_ERROR("Unknown pref name!");
393 return;
394 }
395
396 #ifdef DEBUG
397 // During Init() we get called back with a branch string here, so there should
398 // be no just a "mem." pref here.
399 if (!rts) {
400 NS_ASSERTION(memPrefName.EqualsLiteral(PREF_MEM_OPTIONS_PREFIX), "Huh?!");
401 }
402 #endif
403
404 // If we're running in Init() then do this for every pref we care about.
405 // Otherwise we just want to update the parameter that changed.
406 for (uint32_t index = !gRuntimeServiceDuringInit
407 ? JSSettings::kGCSettingsArraySize - 1
408 : 0;
409 index < JSSettings::kGCSettingsArraySize; index++) {
410 LiteralRebindingCString matchName;
411
412 matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX "max");
413 if (memPrefName == matchName || (gRuntimeServiceDuringInit && index == 0)) {
414 int32_t prefValue = GetWorkerPref(matchName, -1);
415 uint32_t value = (prefValue <= 0 || prefValue >= 0x1000)
416 ? uint32_t(-1)
417 : uint32_t(prefValue) * 1024 * 1024;
418 UpdateOtherJSGCMemoryOption(rts, JSGC_MAX_BYTES, value);
419 continue;
420 }
421
422 matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX "high_water_mark");
423 if (memPrefName == matchName || (gRuntimeServiceDuringInit && index == 1)) {
424 int32_t prefValue = GetWorkerPref(matchName, 128);
425 UpdateOtherJSGCMemoryOption(rts, JSGC_MAX_MALLOC_BYTES,
426 uint32_t(prefValue) * 1024 * 1024);
427 continue;
428 }
429
430 matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX
431 "gc_high_frequency_time_limit_ms");
432 if (memPrefName == matchName || (gRuntimeServiceDuringInit && index == 2)) {
433 UpdateCommonJSGCMemoryOption(rts, matchName,
434 JSGC_HIGH_FREQUENCY_TIME_LIMIT);
435 continue;
436 }
437
438 matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX
439 "gc_low_frequency_heap_growth");
440 if (memPrefName == matchName || (gRuntimeServiceDuringInit && index == 3)) {
441 UpdateCommonJSGCMemoryOption(rts, matchName,
442 JSGC_LOW_FREQUENCY_HEAP_GROWTH);
443 continue;
444 }
445
446 matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX
447 "gc_high_frequency_heap_growth_min");
448 if (memPrefName == matchName || (gRuntimeServiceDuringInit && index == 4)) {
449 UpdateCommonJSGCMemoryOption(rts, matchName,
450 JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MIN);
451 continue;
452 }
453
454 matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX
455 "gc_high_frequency_heap_growth_max");
456 if (memPrefName == matchName || (gRuntimeServiceDuringInit && index == 5)) {
457 UpdateCommonJSGCMemoryOption(rts, matchName,
458 JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MAX);
459 continue;
460 }
461
462 matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX
463 "gc_high_frequency_low_limit_mb");
464 if (memPrefName == matchName || (gRuntimeServiceDuringInit && index == 6)) {
465 UpdateCommonJSGCMemoryOption(rts, matchName,
466 JSGC_HIGH_FREQUENCY_LOW_LIMIT);
467 continue;
468 }
469
470 matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX
471 "gc_high_frequency_high_limit_mb");
472 if (memPrefName == matchName || (gRuntimeServiceDuringInit && index == 7)) {
473 UpdateCommonJSGCMemoryOption(rts, matchName,
474 JSGC_HIGH_FREQUENCY_HIGH_LIMIT);
475 continue;
476 }
477
478 matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX
479 "gc_allocation_threshold_mb");
480 if (memPrefName == matchName || (gRuntimeServiceDuringInit && index == 8)) {
481 UpdateCommonJSGCMemoryOption(rts, matchName, JSGC_ALLOCATION_THRESHOLD);
482 continue;
483 }
484
485 matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX "gc_incremental_slice_ms");
486 if (memPrefName == matchName || (gRuntimeServiceDuringInit && index == 9)) {
487 int32_t prefValue = GetWorkerPref(matchName, -1);
488 uint32_t value =
489 (prefValue <= 0 || prefValue >= 100000) ? 0 : uint32_t(prefValue);
490 UpdateOtherJSGCMemoryOption(rts, JSGC_SLICE_TIME_BUDGET, value);
491 continue;
492 }
493
494 matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX "gc_dynamic_heap_growth");
495 if (memPrefName == matchName ||
496 (gRuntimeServiceDuringInit && index == 10)) {
497 bool prefValue = GetWorkerPref(matchName, false);
498 UpdateOtherJSGCMemoryOption(rts, JSGC_DYNAMIC_HEAP_GROWTH,
499 prefValue ? 0 : 1);
500 continue;
501 }
502
503 matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX "gc_dynamic_mark_slice");
504 if (memPrefName == matchName ||
505 (gRuntimeServiceDuringInit && index == 11)) {
506 bool prefValue = GetWorkerPref(matchName, false);
507 UpdateOtherJSGCMemoryOption(rts, JSGC_DYNAMIC_MARK_SLICE,
508 prefValue ? 0 : 1);
509 continue;
510 }
511
512 matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX "gc_min_empty_chunk_count");
513 if (memPrefName == matchName ||
514 (gRuntimeServiceDuringInit && index == 12)) {
515 UpdateCommonJSGCMemoryOption(rts, matchName, JSGC_MIN_EMPTY_CHUNK_COUNT);
516 continue;
517 }
518
519 matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX "gc_max_empty_chunk_count");
520 if (memPrefName == matchName ||
521 (gRuntimeServiceDuringInit && index == 13)) {
522 UpdateCommonJSGCMemoryOption(rts, matchName, JSGC_MAX_EMPTY_CHUNK_COUNT);
523 continue;
524 }
525
526 matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX "gc_compacting");
527 if (memPrefName == matchName ||
528 (gRuntimeServiceDuringInit && index == 14)) {
529 bool prefValue = GetWorkerPref(matchName, false);
530 UpdateOtherJSGCMemoryOption(rts, JSGC_COMPACTING_ENABLED,
531 prefValue ? 0 : 1);
532 continue;
533 }
534
535 #ifdef DEBUG
536 nsAutoCString message("Workers don't support the 'mem.");
537 message.Append(memPrefName);
538 message.AppendLiteral("' preference!");
539 NS_WARNING(message.get());
540 #endif
541 }
542 }
543
InterruptCallback(JSContext * aCx)544 bool InterruptCallback(JSContext* aCx) {
545 WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx);
546 MOZ_ASSERT(worker);
547
548 // Now is a good time to turn on profiling if it's pending.
549 PROFILER_JS_INTERRUPT_CALLBACK();
550
551 return worker->InterruptCallback(aCx);
552 }
553
554 class LogViolationDetailsRunnable final : public WorkerMainThreadRunnable {
555 nsString mFileName;
556 uint32_t mLineNum;
557
558 public:
LogViolationDetailsRunnable(WorkerPrivate * aWorker,const nsString & aFileName,uint32_t aLineNum)559 LogViolationDetailsRunnable(WorkerPrivate* aWorker, const nsString& aFileName,
560 uint32_t aLineNum)
561 : WorkerMainThreadRunnable(
562 aWorker,
563 NS_LITERAL_CSTRING("RuntimeService :: LogViolationDetails")),
564 mFileName(aFileName),
565 mLineNum(aLineNum) {
566 MOZ_ASSERT(aWorker);
567 }
568
569 virtual bool MainThreadRun() override;
570
571 private:
~LogViolationDetailsRunnable()572 ~LogViolationDetailsRunnable() {}
573 };
574
ContentSecurityPolicyAllows(JSContext * aCx)575 bool ContentSecurityPolicyAllows(JSContext* aCx) {
576 WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx);
577 worker->AssertIsOnWorkerThread();
578
579 if (worker->GetReportCSPViolations()) {
580 nsString fileName;
581 uint32_t lineNum = 0;
582
583 JS::AutoFilename file;
584 if (JS::DescribeScriptedCaller(aCx, &file, &lineNum) && file.get()) {
585 fileName = NS_ConvertUTF8toUTF16(file.get());
586 } else {
587 MOZ_ASSERT(!JS_IsExceptionPending(aCx));
588 }
589
590 RefPtr<LogViolationDetailsRunnable> runnable =
591 new LogViolationDetailsRunnable(worker, fileName, lineNum);
592
593 ErrorResult rv;
594 runnable->Dispatch(Killing, rv);
595 if (NS_WARN_IF(rv.Failed())) {
596 rv.SuppressException();
597 }
598 }
599
600 return worker->IsEvalAllowed();
601 }
602
CTypesActivityCallback(JSContext * aCx,js::CTypesActivityType aType)603 void CTypesActivityCallback(JSContext* aCx, js::CTypesActivityType aType) {
604 WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx);
605 worker->AssertIsOnWorkerThread();
606
607 switch (aType) {
608 case js::CTYPES_CALL_BEGIN:
609 worker->BeginCTypesCall();
610 break;
611
612 case js::CTYPES_CALL_END:
613 worker->EndCTypesCall();
614 break;
615
616 case js::CTYPES_CALLBACK_BEGIN:
617 worker->BeginCTypesCallback();
618 break;
619
620 case js::CTYPES_CALLBACK_END:
621 worker->EndCTypesCallback();
622 break;
623
624 default:
625 MOZ_CRASH("Unknown type flag!");
626 }
627 }
628
GetPrincipalForAsmJSCacheOp()629 static nsIPrincipal* GetPrincipalForAsmJSCacheOp() {
630 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
631 if (!workerPrivate) {
632 return nullptr;
633 }
634
635 // asmjscache::OpenEntryForX guarnatee to only access the given nsIPrincipal
636 // from the main thread.
637 return workerPrivate->GetPrincipalDontAssertMainThread();
638 }
639
AsmJSCacheOpenEntryForRead(JS::Handle<JSObject * > aGlobal,const char16_t * aBegin,const char16_t * aLimit,size_t * aSize,const uint8_t ** aMemory,intptr_t * aHandle)640 static bool AsmJSCacheOpenEntryForRead(JS::Handle<JSObject*> aGlobal,
641 const char16_t* aBegin,
642 const char16_t* aLimit, size_t* aSize,
643 const uint8_t** aMemory,
644 intptr_t* aHandle) {
645 nsIPrincipal* principal = GetPrincipalForAsmJSCacheOp();
646 if (!principal) {
647 return false;
648 }
649
650 return asmjscache::OpenEntryForRead(principal, aBegin, aLimit, aSize, aMemory,
651 aHandle);
652 }
653
AsmJSCacheOpenEntryForWrite(JS::Handle<JSObject * > aGlobal,const char16_t * aBegin,const char16_t * aEnd,size_t aSize,uint8_t ** aMemory,intptr_t * aHandle)654 static JS::AsmJSCacheResult AsmJSCacheOpenEntryForWrite(
655 JS::Handle<JSObject*> aGlobal, const char16_t* aBegin, const char16_t* aEnd,
656 size_t aSize, uint8_t** aMemory, intptr_t* aHandle) {
657 nsIPrincipal* principal = GetPrincipalForAsmJSCacheOp();
658 if (!principal) {
659 return JS::AsmJSCache_InternalError;
660 }
661
662 return asmjscache::OpenEntryForWrite(principal, aBegin, aEnd, aSize, aMemory,
663 aHandle);
664 }
665
666 // JSDispatchableRunnables are WorkerRunnables used to dispatch JS::Dispatchable
667 // back to their worker thread. A WorkerRunnable is used for two reasons:
668 //
669 // 1. The JS::Dispatchable::run() callback may run JS so we cannot use a control
670 // runnable since they use async interrupts and break JS run-to-completion.
671 //
672 // 2. The DispatchToEventLoopCallback interface is *required* to fail during
673 // shutdown (see jsapi.h) which is exactly what WorkerRunnable::Dispatch() will
674 // do. Moreover, JS_DestroyContext() does *not* block on JS::Dispatchable::run
675 // being called, DispatchToEventLoopCallback failure is expected to happen
676 // during shutdown.
677 class JSDispatchableRunnable final : public WorkerRunnable {
678 JS::Dispatchable* mDispatchable;
679
~JSDispatchableRunnable()680 ~JSDispatchableRunnable() { MOZ_ASSERT(!mDispatchable); }
681
682 // Disable the usual pre/post-dispatch thread assertions since we are
683 // dispatching from some random JS engine internal thread:
684
PreDispatch(WorkerPrivate * aWorkerPrivate)685 bool PreDispatch(WorkerPrivate* aWorkerPrivate) override { return true; }
686
PostDispatch(WorkerPrivate * aWorkerPrivate,bool aDispatchResult)687 void PostDispatch(WorkerPrivate* aWorkerPrivate,
688 bool aDispatchResult) override {
689 // For the benefit of the destructor assert.
690 if (!aDispatchResult) {
691 mDispatchable = nullptr;
692 }
693 }
694
695 public:
JSDispatchableRunnable(WorkerPrivate * aWorkerPrivate,JS::Dispatchable * aDispatchable)696 JSDispatchableRunnable(WorkerPrivate* aWorkerPrivate,
697 JS::Dispatchable* aDispatchable)
698 : WorkerRunnable(aWorkerPrivate,
699 WorkerRunnable::WorkerThreadUnchangedBusyCount),
700 mDispatchable(aDispatchable) {
701 MOZ_ASSERT(mDispatchable);
702 }
703
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)704 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
705 MOZ_ASSERT(aWorkerPrivate == mWorkerPrivate);
706 MOZ_ASSERT(aCx == mWorkerPrivate->GetJSContext());
707 MOZ_ASSERT(mDispatchable);
708
709 AutoJSAPI jsapi;
710 jsapi.Init();
711
712 mDispatchable->run(mWorkerPrivate->GetJSContext(),
713 JS::Dispatchable::NotShuttingDown);
714 mDispatchable = nullptr; // mDispatchable may delete itself
715
716 return true;
717 }
718
Cancel()719 nsresult Cancel() override {
720 MOZ_ASSERT(mDispatchable);
721
722 AutoJSAPI jsapi;
723 jsapi.Init();
724
725 mDispatchable->run(mWorkerPrivate->GetJSContext(),
726 JS::Dispatchable::ShuttingDown);
727 mDispatchable = nullptr; // mDispatchable may delete itself
728
729 return WorkerRunnable::Cancel();
730 }
731 };
732
DispatchToEventLoop(void * aClosure,JS::Dispatchable * aDispatchable)733 static bool DispatchToEventLoop(void* aClosure,
734 JS::Dispatchable* aDispatchable) {
735 // This callback may execute either on the worker thread or a random
736 // JS-internal helper thread.
737
738 // See comment at JS::InitDispatchToEventLoop() below for how we know the
739 // WorkerPrivate is alive.
740 WorkerPrivate* workerPrivate = reinterpret_cast<WorkerPrivate*>(aClosure);
741
742 // Dispatch is expected to fail during shutdown for the reasons outlined in
743 // the JSDispatchableRunnable comment above.
744 RefPtr<JSDispatchableRunnable> r =
745 new JSDispatchableRunnable(workerPrivate, aDispatchable);
746 return r->Dispatch();
747 }
748
ConsumeStream(JSContext * aCx,JS::HandleObject aObj,JS::MimeType aMimeType,JS::StreamConsumer * aConsumer)749 static bool ConsumeStream(JSContext* aCx, JS::HandleObject aObj,
750 JS::MimeType aMimeType,
751 JS::StreamConsumer* aConsumer) {
752 WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx);
753 if (!worker) {
754 JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr,
755 JSMSG_ERROR_CONSUMING_RESPONSE);
756 return false;
757 }
758
759 return FetchUtil::StreamResponseToJS(aCx, aObj, aMimeType, aConsumer, worker);
760 }
761
762 class WorkerJSContext;
763
764 class WorkerThreadContextPrivate : private PerThreadAtomCache {
765 friend class WorkerJSContext;
766
767 WorkerPrivate* mWorkerPrivate;
768
769 public:
770 // This can't return null, but we can't lose the "Get" prefix in the name or
771 // it will be ambiguous with the WorkerPrivate class name.
GetWorkerPrivate() const772 WorkerPrivate* GetWorkerPrivate() const {
773 MOZ_ASSERT(!NS_IsMainThread());
774 MOZ_ASSERT(mWorkerPrivate);
775
776 return mWorkerPrivate;
777 }
778
779 private:
WorkerThreadContextPrivate(WorkerPrivate * aWorkerPrivate)780 explicit WorkerThreadContextPrivate(WorkerPrivate* aWorkerPrivate)
781 : mWorkerPrivate(aWorkerPrivate) {
782 MOZ_ASSERT(!NS_IsMainThread());
783
784 // Zero out the base class members.
785 memset(this, 0, sizeof(PerThreadAtomCache));
786
787 MOZ_ASSERT(mWorkerPrivate);
788 }
789
~WorkerThreadContextPrivate()790 ~WorkerThreadContextPrivate() { MOZ_ASSERT(!NS_IsMainThread()); }
791
792 WorkerThreadContextPrivate(const WorkerThreadContextPrivate&) = delete;
793
794 WorkerThreadContextPrivate& operator=(const WorkerThreadContextPrivate&) =
795 delete;
796 };
797
InitJSContextForWorker(WorkerPrivate * aWorkerPrivate,JSContext * aWorkerCx)798 bool InitJSContextForWorker(WorkerPrivate* aWorkerPrivate,
799 JSContext* aWorkerCx) {
800 aWorkerPrivate->AssertIsOnWorkerThread();
801 NS_ASSERTION(!aWorkerPrivate->GetJSContext(), "Already has a context!");
802
803 JSSettings settings;
804 aWorkerPrivate->CopyJSSettings(settings);
805
806 JS::ContextOptionsRef(aWorkerCx) = settings.contextOptions;
807
808 JSSettings::JSGCSettingsArray& gcSettings = settings.gcSettings;
809
810 // This is the real place where we set the max memory for the runtime.
811 for (uint32_t index = 0; index < ArrayLength(gcSettings); index++) {
812 const JSSettings::JSGCSetting& setting = gcSettings[index];
813 if (setting.key.isSome()) {
814 NS_ASSERTION(setting.value, "Can't handle 0 values!");
815 JS_SetGCParameter(aWorkerCx, *setting.key, setting.value);
816 }
817 }
818
819 JS_SetNativeStackQuota(aWorkerCx, WORKER_CONTEXT_NATIVE_STACK_LIMIT);
820
821 // Security policy:
822 static const JSSecurityCallbacks securityCallbacks = {
823 ContentSecurityPolicyAllows};
824 JS_SetSecurityCallbacks(aWorkerCx, &securityCallbacks);
825
826 // Set up the asm.js cache callbacks
827 static const JS::AsmJSCacheOps asmJSCacheOps = {
828 AsmJSCacheOpenEntryForRead, asmjscache::CloseEntryForRead,
829 AsmJSCacheOpenEntryForWrite, asmjscache::CloseEntryForWrite};
830 JS::SetAsmJSCacheOps(aWorkerCx, &asmJSCacheOps);
831
832 // A WorkerPrivate lives strictly longer than its JSRuntime so we can safely
833 // store a raw pointer as the callback's closure argument on the JSRuntime.
834 JS::InitDispatchToEventLoop(aWorkerCx, DispatchToEventLoop,
835 (void*)aWorkerPrivate);
836
837 JS::InitConsumeStreamCallback(aWorkerCx, ConsumeStream);
838
839 if (!JS::InitSelfHostedCode(aWorkerCx)) {
840 NS_WARNING("Could not init self-hosted code!");
841 return false;
842 }
843
844 JS_AddInterruptCallback(aWorkerCx, InterruptCallback);
845
846 js::SetCTypesActivityCallback(aWorkerCx, CTypesActivityCallback);
847
848 #ifdef JS_GC_ZEAL
849 JS_SetGCZeal(aWorkerCx, settings.gcZeal, settings.gcZealFrequency);
850 #endif
851
852 return true;
853 }
854
PreserveWrapper(JSContext * cx,JSObject * obj)855 static bool PreserveWrapper(JSContext* cx, JSObject* obj) {
856 MOZ_ASSERT(cx);
857 MOZ_ASSERT(obj);
858 MOZ_ASSERT(mozilla::dom::IsDOMObject(obj));
859
860 return mozilla::dom::TryPreserveWrapper(obj);
861 }
862
Wrap(JSContext * cx,JS::HandleObject existing,JS::HandleObject obj)863 JSObject* Wrap(JSContext* cx, JS::HandleObject existing, JS::HandleObject obj) {
864 JSObject* targetGlobal = JS::CurrentGlobalOrNull(cx);
865 if (!IsWorkerDebuggerGlobal(targetGlobal) &&
866 !IsWorkerDebuggerSandbox(targetGlobal)) {
867 MOZ_CRASH("There should be no edges from the debuggee to the debugger.");
868 }
869
870 JSObject* originGlobal = js::GetGlobalForObjectCrossCompartment(obj);
871
872 const js::Wrapper* wrapper = nullptr;
873 if (IsWorkerDebuggerGlobal(originGlobal) ||
874 IsWorkerDebuggerSandbox(originGlobal)) {
875 wrapper = &js::CrossCompartmentWrapper::singleton;
876 } else {
877 wrapper = &js::OpaqueCrossCompartmentWrapper::singleton;
878 }
879
880 if (existing) {
881 js::Wrapper::Renew(existing, obj, wrapper);
882 }
883 return js::Wrapper::New(cx, obj, wrapper);
884 }
885
886 static const JSWrapObjectCallbacks WrapObjectCallbacks = {
887 Wrap,
888 nullptr,
889 };
890
891 class WorkerJSRuntime final : public mozilla::CycleCollectedJSRuntime {
892 public:
893 // The heap size passed here doesn't matter, we will change it later in the
894 // call to JS_SetGCParameter inside InitJSContextForWorker.
WorkerJSRuntime(JSContext * aCx,WorkerPrivate * aWorkerPrivate)895 explicit WorkerJSRuntime(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
896 : CycleCollectedJSRuntime(aCx), mWorkerPrivate(aWorkerPrivate) {
897 MOZ_COUNT_CTOR_INHERITED(WorkerJSRuntime, CycleCollectedJSRuntime);
898 MOZ_ASSERT(aWorkerPrivate);
899
900 {
901 JS::UniqueChars defaultLocale = aWorkerPrivate->AdoptDefaultLocale();
902 MOZ_ASSERT(defaultLocale,
903 "failure of a WorkerPrivate to have a default locale should "
904 "have made the worker fail to spawn");
905
906 if (!JS_SetDefaultLocale(Runtime(), defaultLocale.get())) {
907 NS_WARNING("failed to set workerCx's default locale");
908 }
909 }
910 }
911
Shutdown(JSContext * cx)912 void Shutdown(JSContext* cx) override {
913 // The CC is shut down, and the superclass destructor will GC, so make sure
914 // we don't try to CC again.
915 mWorkerPrivate = nullptr;
916
917 CycleCollectedJSRuntime::Shutdown(cx);
918 }
919
~WorkerJSRuntime()920 ~WorkerJSRuntime() {
921 MOZ_COUNT_DTOR_INHERITED(WorkerJSRuntime, CycleCollectedJSRuntime);
922 }
923
PrepareForForgetSkippable()924 virtual void PrepareForForgetSkippable() override {}
925
BeginCycleCollectionCallback()926 virtual void BeginCycleCollectionCallback() override {}
927
EndCycleCollectionCallback(CycleCollectorResults & aResults)928 virtual void EndCycleCollectionCallback(
929 CycleCollectorResults& aResults) override {}
930
DispatchDeferredDeletion(bool aContinuation,bool aPurge)931 void DispatchDeferredDeletion(bool aContinuation, bool aPurge) override {
932 MOZ_ASSERT(!aContinuation);
933
934 // Do it immediately, no need for asynchronous behavior here.
935 nsCycleCollector_doDeferredDeletion();
936 }
937
CustomGCCallback(JSGCStatus aStatus)938 virtual void CustomGCCallback(JSGCStatus aStatus) override {
939 if (!mWorkerPrivate) {
940 // We're shutting down, no need to do anything.
941 return;
942 }
943
944 mWorkerPrivate->AssertIsOnWorkerThread();
945
946 if (aStatus == JSGC_END) {
947 nsCycleCollector_collect(nullptr);
948 }
949 }
950
951 private:
952 WorkerPrivate* mWorkerPrivate;
953 };
954
955 class MOZ_STACK_CLASS WorkerJSContext final
956 : public mozilla::CycleCollectedJSContext {
957 public:
958 // The heap size passed here doesn't matter, we will change it later in the
959 // call to JS_SetGCParameter inside InitJSContextForWorker.
WorkerJSContext(WorkerPrivate * aWorkerPrivate)960 explicit WorkerJSContext(WorkerPrivate* aWorkerPrivate)
961 : mWorkerPrivate(aWorkerPrivate) {
962 MOZ_COUNT_CTOR_INHERITED(WorkerJSContext, CycleCollectedJSContext);
963 MOZ_ASSERT(aWorkerPrivate);
964 // Magical number 2. Workers have the base recursion depth 1, and normal
965 // runnables run at level 2, and we don't want to process microtasks
966 // at any other level.
967 SetTargetedMicroTaskRecursionDepth(2);
968 }
969
~WorkerJSContext()970 ~WorkerJSContext() {
971 MOZ_COUNT_DTOR_INHERITED(WorkerJSContext, CycleCollectedJSContext);
972 JSContext* cx = MaybeContext();
973 if (!cx) {
974 return; // Initialize() must have failed
975 }
976
977 delete static_cast<WorkerThreadContextPrivate*>(JS_GetContextPrivate(cx));
978 JS_SetContextPrivate(cx, nullptr);
979
980 // The worker global should be unrooted and the shutdown cycle collection
981 // should break all remaining cycles. The superclass destructor will run
982 // the GC one final time and finalize any JSObjects that were participating
983 // in cycles that were broken during CC shutdown.
984 nsCycleCollector_shutdown();
985
986 // The CC is shut down, and the superclass destructor will GC, so make sure
987 // we don't try to CC again.
988 mWorkerPrivate = nullptr;
989 }
990
CreateRuntime(JSContext * aCx)991 CycleCollectedJSRuntime* CreateRuntime(JSContext* aCx) override {
992 return new WorkerJSRuntime(aCx, mWorkerPrivate);
993 }
994
Initialize(JSRuntime * aParentRuntime)995 nsresult Initialize(JSRuntime* aParentRuntime) {
996 nsresult rv = CycleCollectedJSContext::Initialize(
997 aParentRuntime, WORKER_DEFAULT_RUNTIME_HEAPSIZE,
998 WORKER_DEFAULT_NURSERY_SIZE);
999 if (NS_WARN_IF(NS_FAILED(rv))) {
1000 return rv;
1001 }
1002
1003 JSContext* cx = Context();
1004
1005 JS_SetContextPrivate(cx, new WorkerThreadContextPrivate(mWorkerPrivate));
1006
1007 js::SetPreserveWrapperCallback(cx, PreserveWrapper);
1008 JS_InitDestroyPrincipalsCallback(cx, DestroyWorkerPrincipals);
1009 JS_SetWrapObjectCallbacks(cx, &WrapObjectCallbacks);
1010 if (mWorkerPrivate->IsDedicatedWorker()) {
1011 JS_SetFutexCanWait(cx);
1012 }
1013
1014 return NS_OK;
1015 }
1016
DispatchToMicroTask(already_AddRefed<MicroTaskRunnable> aRunnable)1017 virtual void DispatchToMicroTask(
1018 already_AddRefed<MicroTaskRunnable> aRunnable) override {
1019 RefPtr<MicroTaskRunnable> runnable(aRunnable);
1020
1021 MOZ_ASSERT(!NS_IsMainThread());
1022 MOZ_ASSERT(runnable);
1023
1024 std::queue<RefPtr<MicroTaskRunnable>>* microTaskQueue = nullptr;
1025
1026 JSContext* cx = GetCurrentWorkerThreadJSContext();
1027 NS_ASSERTION(cx, "This should never be null!");
1028
1029 JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
1030 NS_ASSERTION(global, "This should never be null!");
1031
1032 // On worker threads, if the current global is the worker global, we use the
1033 // main micro task queue. Otherwise, the current global must be
1034 // either the debugger global or a debugger sandbox, and we use the debugger
1035 // micro task queue instead.
1036 if (IsWorkerGlobal(global)) {
1037 microTaskQueue = &GetMicroTaskQueue();
1038 } else {
1039 MOZ_ASSERT(IsWorkerDebuggerGlobal(global) ||
1040 IsWorkerDebuggerSandbox(global));
1041
1042 microTaskQueue = &GetDebuggerMicroTaskQueue();
1043 }
1044
1045 microTaskQueue->push(runnable.forget());
1046 }
1047
1048 private:
1049 WorkerPrivate* mWorkerPrivate;
1050 };
1051
1052 class WorkerThreadPrimaryRunnable final : public Runnable {
1053 WorkerPrivate* mWorkerPrivate;
1054 RefPtr<WorkerThread> mThread;
1055 JSRuntime* mParentRuntime;
1056
1057 class FinishedRunnable final : public Runnable {
1058 RefPtr<WorkerThread> mThread;
1059
1060 public:
FinishedRunnable(already_AddRefed<WorkerThread> aThread)1061 explicit FinishedRunnable(already_AddRefed<WorkerThread> aThread)
1062 : Runnable("WorkerThreadPrimaryRunnable::FinishedRunnable"),
1063 mThread(aThread) {
1064 MOZ_ASSERT(mThread);
1065 }
1066
1067 NS_INLINE_DECL_REFCOUNTING_INHERITED(FinishedRunnable, Runnable)
1068
1069 private:
~FinishedRunnable()1070 ~FinishedRunnable() {}
1071
1072 NS_DECL_NSIRUNNABLE
1073 };
1074
1075 public:
WorkerThreadPrimaryRunnable(WorkerPrivate * aWorkerPrivate,WorkerThread * aThread,JSRuntime * aParentRuntime)1076 WorkerThreadPrimaryRunnable(WorkerPrivate* aWorkerPrivate,
1077 WorkerThread* aThread, JSRuntime* aParentRuntime)
1078 : mozilla::Runnable("WorkerThreadPrimaryRunnable"),
1079 mWorkerPrivate(aWorkerPrivate),
1080 mThread(aThread),
1081 mParentRuntime(aParentRuntime) {
1082 MOZ_ASSERT(aWorkerPrivate);
1083 MOZ_ASSERT(aThread);
1084 }
1085
1086 NS_INLINE_DECL_REFCOUNTING_INHERITED(WorkerThreadPrimaryRunnable, Runnable)
1087
1088 private:
~WorkerThreadPrimaryRunnable()1089 ~WorkerThreadPrimaryRunnable() {}
1090
1091 NS_DECL_NSIRUNNABLE
1092 };
1093
PrefLanguagesChanged(const char *,void *)1094 void PrefLanguagesChanged(const char* /* aPrefName */, void* /* aClosure */) {
1095 AssertIsOnMainThread();
1096
1097 nsTArray<nsString> languages;
1098 Navigator::GetAcceptLanguages(languages);
1099
1100 RuntimeService* runtime = RuntimeService::GetService();
1101 if (runtime) {
1102 runtime->UpdateAllWorkerLanguages(languages);
1103 }
1104 }
1105
AppNameOverrideChanged(const char *,void *)1106 void AppNameOverrideChanged(const char* /* aPrefName */, void* /* aClosure */) {
1107 AssertIsOnMainThread();
1108
1109 nsAutoString override;
1110 Preferences::GetString("general.appname.override", override);
1111
1112 RuntimeService* runtime = RuntimeService::GetService();
1113 if (runtime) {
1114 runtime->UpdateAppNameOverridePreference(override);
1115 }
1116 }
1117
AppVersionOverrideChanged(const char *,void *)1118 void AppVersionOverrideChanged(const char* /* aPrefName */,
1119 void* /* aClosure */) {
1120 AssertIsOnMainThread();
1121
1122 nsAutoString override;
1123 Preferences::GetString("general.appversion.override", override);
1124
1125 RuntimeService* runtime = RuntimeService::GetService();
1126 if (runtime) {
1127 runtime->UpdateAppVersionOverridePreference(override);
1128 }
1129 }
1130
PlatformOverrideChanged(const char *,void *)1131 void PlatformOverrideChanged(const char* /* aPrefName */,
1132 void* /* aClosure */) {
1133 AssertIsOnMainThread();
1134
1135 nsAutoString override;
1136 Preferences::GetString("general.platform.override", override);
1137
1138 RuntimeService* runtime = RuntimeService::GetService();
1139 if (runtime) {
1140 runtime->UpdatePlatformOverridePreference(override);
1141 }
1142 }
1143
1144 } /* anonymous namespace */
1145
1146 struct RuntimeService::IdleThreadInfo {
1147 RefPtr<WorkerThread> mThread;
1148 mozilla::TimeStamp mExpirationTime;
1149 };
1150
1151 // This is only touched on the main thread. Initialized in Init() below.
1152 JSSettings RuntimeService::sDefaultJSSettings;
1153
RuntimeService()1154 RuntimeService::RuntimeService()
1155 : mMutex("RuntimeService::mMutex"),
1156 mObserved(false),
1157 mShuttingDown(false),
1158 mNavigatorPropertiesLoaded(false) {
1159 AssertIsOnMainThread();
1160 NS_ASSERTION(!gRuntimeService, "More than one service!");
1161 }
1162
~RuntimeService()1163 RuntimeService::~RuntimeService() {
1164 AssertIsOnMainThread();
1165
1166 // gRuntimeService can be null if Init() fails.
1167 NS_ASSERTION(!gRuntimeService || gRuntimeService == this,
1168 "More than one service!");
1169
1170 gRuntimeService = nullptr;
1171 }
1172
1173 // static
GetOrCreateService()1174 RuntimeService* RuntimeService::GetOrCreateService() {
1175 AssertIsOnMainThread();
1176
1177 if (!gRuntimeService) {
1178 // The observer service now owns us until shutdown.
1179 gRuntimeService = new RuntimeService();
1180 if (NS_FAILED(gRuntimeService->Init())) {
1181 NS_WARNING("Failed to initialize!");
1182 gRuntimeService->Cleanup();
1183 gRuntimeService = nullptr;
1184 return nullptr;
1185 }
1186 }
1187
1188 return gRuntimeService;
1189 }
1190
1191 // static
GetService()1192 RuntimeService* RuntimeService::GetService() { return gRuntimeService; }
1193
RegisterWorker(WorkerPrivate * aWorkerPrivate)1194 bool RuntimeService::RegisterWorker(WorkerPrivate* aWorkerPrivate) {
1195 aWorkerPrivate->AssertIsOnParentThread();
1196
1197 WorkerPrivate* parent = aWorkerPrivate->GetParent();
1198 if (!parent) {
1199 AssertIsOnMainThread();
1200
1201 if (mShuttingDown) {
1202 return false;
1203 }
1204 }
1205
1206 const bool isServiceWorker = aWorkerPrivate->IsServiceWorker();
1207 const bool isSharedWorker = aWorkerPrivate->IsSharedWorker();
1208 const bool isDedicatedWorker = aWorkerPrivate->IsDedicatedWorker();
1209 if (isServiceWorker) {
1210 AssertIsOnMainThread();
1211 Telemetry::Accumulate(Telemetry::SERVICE_WORKER_SPAWN_ATTEMPTS, 1);
1212 }
1213
1214 nsCString sharedWorkerScriptSpec;
1215 if (isSharedWorker) {
1216 AssertIsOnMainThread();
1217
1218 nsCOMPtr<nsIURI> scriptURI = aWorkerPrivate->GetResolvedScriptURI();
1219 NS_ASSERTION(scriptURI, "Null script URI!");
1220
1221 nsresult rv = scriptURI->GetSpec(sharedWorkerScriptSpec);
1222 if (NS_FAILED(rv)) {
1223 NS_WARNING("GetSpec failed?!");
1224 return false;
1225 }
1226
1227 NS_ASSERTION(!sharedWorkerScriptSpec.IsEmpty(), "Empty spec!");
1228 }
1229
1230 bool exemptFromPerDomainMax = false;
1231 if (isServiceWorker) {
1232 AssertIsOnMainThread();
1233 exemptFromPerDomainMax = Preferences::GetBool(
1234 "dom.serviceWorkers.exemptFromPerDomainMax", false);
1235 }
1236
1237 const nsCString& domain = aWorkerPrivate->Domain();
1238
1239 WorkerDomainInfo* domainInfo;
1240 bool queued = false;
1241 {
1242 MutexAutoLock lock(mMutex);
1243
1244 domainInfo = mDomainMap.LookupForAdd(domain).OrInsert([&domain, parent]() {
1245 NS_ASSERTION(!parent, "Shouldn't have a parent here!");
1246 Unused << parent; // silence clang -Wunused-lambda-capture in opt builds
1247 WorkerDomainInfo* wdi = new WorkerDomainInfo();
1248 wdi->mDomain = domain;
1249 return wdi;
1250 });
1251
1252 queued = gMaxWorkersPerDomain &&
1253 domainInfo->ActiveWorkerCount() >= gMaxWorkersPerDomain &&
1254 !domain.IsEmpty() && !exemptFromPerDomainMax;
1255
1256 if (queued) {
1257 domainInfo->mQueuedWorkers.AppendElement(aWorkerPrivate);
1258
1259 // Worker spawn gets queued due to hitting max workers per domain
1260 // limit so let's log a warning.
1261 WorkerPrivate::ReportErrorToConsole("HittingMaxWorkersPerDomain2");
1262
1263 if (isServiceWorker) {
1264 Telemetry::Accumulate(Telemetry::SERVICE_WORKER_SPAWN_GETS_QUEUED, 1);
1265 } else if (isSharedWorker) {
1266 Telemetry::Accumulate(Telemetry::SHARED_WORKER_SPAWN_GETS_QUEUED, 1);
1267 } else if (isDedicatedWorker) {
1268 Telemetry::Accumulate(Telemetry::DEDICATED_WORKER_SPAWN_GETS_QUEUED, 1);
1269 }
1270 } else if (parent) {
1271 domainInfo->mChildWorkerCount++;
1272 } else if (isServiceWorker) {
1273 domainInfo->mActiveServiceWorkers.AppendElement(aWorkerPrivate);
1274 } else {
1275 domainInfo->mActiveWorkers.AppendElement(aWorkerPrivate);
1276 }
1277
1278 if (isSharedWorker) {
1279 #ifdef DEBUG
1280 for (const UniquePtr<SharedWorkerInfo>& data :
1281 domainInfo->mSharedWorkerInfos) {
1282 if (data->mScriptSpec == sharedWorkerScriptSpec &&
1283 data->mName == aWorkerPrivate->WorkerName() &&
1284 // We want to be sure that the window's principal subsumes the
1285 // SharedWorker's principal and vice versa.
1286 data->mWorkerPrivate->GetPrincipal()->Subsumes(
1287 aWorkerPrivate->GetPrincipal()) &&
1288 aWorkerPrivate->GetPrincipal()->Subsumes(
1289 data->mWorkerPrivate->GetPrincipal())) {
1290 MOZ_CRASH("We should not instantiate a new SharedWorker!");
1291 }
1292 }
1293 #endif
1294
1295 UniquePtr<SharedWorkerInfo> sharedWorkerInfo(
1296 new SharedWorkerInfo(aWorkerPrivate, sharedWorkerScriptSpec,
1297 aWorkerPrivate->WorkerName()));
1298 domainInfo->mSharedWorkerInfos.AppendElement(Move(sharedWorkerInfo));
1299 }
1300 }
1301
1302 // From here on out we must call UnregisterWorker if something fails!
1303 if (parent) {
1304 if (!parent->AddChildWorker(aWorkerPrivate)) {
1305 UnregisterWorker(aWorkerPrivate);
1306 return false;
1307 }
1308 } else {
1309 if (!mNavigatorPropertiesLoaded) {
1310 Navigator::AppName(mNavigatorProperties.mAppName,
1311 false /* aUsePrefOverriddenValue */);
1312 if (NS_FAILED(
1313 Navigator::GetAppVersion(mNavigatorProperties.mAppVersion,
1314 false /* aUsePrefOverriddenValue */)) ||
1315 NS_FAILED(
1316 Navigator::GetPlatform(mNavigatorProperties.mPlatform,
1317 false /* aUsePrefOverriddenValue */))) {
1318 UnregisterWorker(aWorkerPrivate);
1319 return false;
1320 }
1321
1322 // The navigator overridden properties should have already been read.
1323
1324 Navigator::GetAcceptLanguages(mNavigatorProperties.mLanguages);
1325 mNavigatorPropertiesLoaded = true;
1326 }
1327
1328 nsPIDOMWindowInner* window = aWorkerPrivate->GetWindow();
1329
1330 if (!isServiceWorker) {
1331 // Service workers are excluded since their lifetime is separate from
1332 // that of dom windows.
1333 nsTArray<WorkerPrivate*>* windowArray =
1334 mWindowMap.LookupForAdd(window).OrInsert(
1335 []() { return new nsTArray<WorkerPrivate*>(1); });
1336 if (!windowArray->Contains(aWorkerPrivate)) {
1337 windowArray->AppendElement(aWorkerPrivate);
1338 } else {
1339 MOZ_ASSERT(aWorkerPrivate->IsSharedWorker());
1340 }
1341 }
1342 }
1343
1344 if (!queued && !ScheduleWorker(aWorkerPrivate)) {
1345 return false;
1346 }
1347
1348 if (isServiceWorker) {
1349 AssertIsOnMainThread();
1350 Telemetry::Accumulate(Telemetry::SERVICE_WORKER_WAS_SPAWNED, 1);
1351 }
1352 return true;
1353 }
1354
RemoveSharedWorker(WorkerDomainInfo * aDomainInfo,WorkerPrivate * aWorkerPrivate)1355 void RuntimeService::RemoveSharedWorker(WorkerDomainInfo* aDomainInfo,
1356 WorkerPrivate* aWorkerPrivate) {
1357 for (uint32_t i = 0; i < aDomainInfo->mSharedWorkerInfos.Length(); ++i) {
1358 const UniquePtr<SharedWorkerInfo>& data =
1359 aDomainInfo->mSharedWorkerInfos[i];
1360 if (data->mWorkerPrivate == aWorkerPrivate) {
1361 aDomainInfo->mSharedWorkerInfos.RemoveElementAt(i);
1362 break;
1363 }
1364 }
1365 }
1366
UnregisterWorker(WorkerPrivate * aWorkerPrivate)1367 void RuntimeService::UnregisterWorker(WorkerPrivate* aWorkerPrivate) {
1368 aWorkerPrivate->AssertIsOnParentThread();
1369
1370 WorkerPrivate* parent = aWorkerPrivate->GetParent();
1371 if (!parent) {
1372 AssertIsOnMainThread();
1373 }
1374
1375 const nsCString& domain = aWorkerPrivate->Domain();
1376
1377 WorkerPrivate* queuedWorker = nullptr;
1378 {
1379 MutexAutoLock lock(mMutex);
1380
1381 WorkerDomainInfo* domainInfo;
1382 if (!mDomainMap.Get(domain, &domainInfo)) {
1383 NS_ERROR("Don't have an entry for this domain!");
1384 }
1385
1386 // Remove old worker from everywhere.
1387 uint32_t index = domainInfo->mQueuedWorkers.IndexOf(aWorkerPrivate);
1388 if (index != kNoIndex) {
1389 // Was queued, remove from the list.
1390 domainInfo->mQueuedWorkers.RemoveElementAt(index);
1391 } else if (parent) {
1392 MOZ_ASSERT(domainInfo->mChildWorkerCount, "Must be non-zero!");
1393 domainInfo->mChildWorkerCount--;
1394 } else if (aWorkerPrivate->IsServiceWorker()) {
1395 MOZ_ASSERT(domainInfo->mActiveServiceWorkers.Contains(aWorkerPrivate),
1396 "Don't know about this worker!");
1397 domainInfo->mActiveServiceWorkers.RemoveElement(aWorkerPrivate);
1398 } else {
1399 MOZ_ASSERT(domainInfo->mActiveWorkers.Contains(aWorkerPrivate),
1400 "Don't know about this worker!");
1401 domainInfo->mActiveWorkers.RemoveElement(aWorkerPrivate);
1402 }
1403
1404 if (aWorkerPrivate->IsSharedWorker()) {
1405 RemoveSharedWorker(domainInfo, aWorkerPrivate);
1406 }
1407
1408 // See if there's a queued worker we can schedule.
1409 if (domainInfo->ActiveWorkerCount() < gMaxWorkersPerDomain &&
1410 !domainInfo->mQueuedWorkers.IsEmpty()) {
1411 queuedWorker = domainInfo->mQueuedWorkers[0];
1412 domainInfo->mQueuedWorkers.RemoveElementAt(0);
1413
1414 if (queuedWorker->GetParent()) {
1415 domainInfo->mChildWorkerCount++;
1416 } else if (queuedWorker->IsServiceWorker()) {
1417 domainInfo->mActiveServiceWorkers.AppendElement(queuedWorker);
1418 } else {
1419 domainInfo->mActiveWorkers.AppendElement(queuedWorker);
1420 }
1421 }
1422
1423 if (domainInfo->HasNoWorkers()) {
1424 MOZ_ASSERT(domainInfo->mQueuedWorkers.IsEmpty());
1425 mDomainMap.Remove(domain);
1426 }
1427 }
1428
1429 if (aWorkerPrivate->IsServiceWorker()) {
1430 AssertIsOnMainThread();
1431 Telemetry::AccumulateTimeDelta(Telemetry::SERVICE_WORKER_LIFE_TIME,
1432 aWorkerPrivate->CreationTimeStamp());
1433 }
1434
1435 if (aWorkerPrivate->IsSharedWorker() || aWorkerPrivate->IsServiceWorker()) {
1436 AssertIsOnMainThread();
1437 aWorkerPrivate->CloseAllSharedWorkers();
1438 }
1439
1440 if (parent) {
1441 parent->RemoveChildWorker(aWorkerPrivate);
1442 } else if (aWorkerPrivate->IsSharedWorker()) {
1443 AssertIsOnMainThread();
1444
1445 for (auto iter = mWindowMap.Iter(); !iter.Done(); iter.Next()) {
1446 nsAutoPtr<nsTArray<WorkerPrivate*>>& workers = iter.Data();
1447 MOZ_ASSERT(workers.get());
1448
1449 if (workers->RemoveElement(aWorkerPrivate)) {
1450 MOZ_ASSERT(!workers->Contains(aWorkerPrivate),
1451 "Added worker more than once!");
1452
1453 if (workers->IsEmpty()) {
1454 iter.Remove();
1455 }
1456 }
1457 }
1458 } else if (aWorkerPrivate->IsDedicatedWorker()) {
1459 // May be null.
1460 nsPIDOMWindowInner* window = aWorkerPrivate->GetWindow();
1461 if (auto entry = mWindowMap.Lookup(window)) {
1462 MOZ_ALWAYS_TRUE(entry.Data()->RemoveElement(aWorkerPrivate));
1463 if (entry.Data()->IsEmpty()) {
1464 entry.Remove();
1465 }
1466 } else {
1467 MOZ_ASSERT_UNREACHABLE("window is not in mWindowMap");
1468 }
1469 }
1470
1471 if (queuedWorker && !ScheduleWorker(queuedWorker)) {
1472 UnregisterWorker(queuedWorker);
1473 }
1474 }
1475
ScheduleWorker(WorkerPrivate * aWorkerPrivate)1476 bool RuntimeService::ScheduleWorker(WorkerPrivate* aWorkerPrivate) {
1477 if (!aWorkerPrivate->Start()) {
1478 // This is ok, means that we didn't need to make a thread for this worker.
1479 return true;
1480 }
1481
1482 RefPtr<WorkerThread> thread;
1483 {
1484 MutexAutoLock lock(mMutex);
1485 if (!mIdleThreadArray.IsEmpty()) {
1486 uint32_t index = mIdleThreadArray.Length() - 1;
1487 mIdleThreadArray[index].mThread.swap(thread);
1488 mIdleThreadArray.RemoveElementAt(index);
1489 }
1490 }
1491
1492 const WorkerThreadFriendKey friendKey;
1493
1494 if (!thread) {
1495 thread = WorkerThread::Create(friendKey);
1496 if (!thread) {
1497 UnregisterWorker(aWorkerPrivate);
1498 return false;
1499 }
1500 }
1501
1502 int32_t priority = aWorkerPrivate->IsChromeWorker()
1503 ? nsISupportsPriority::PRIORITY_NORMAL
1504 : nsISupportsPriority::PRIORITY_LOW;
1505
1506 if (NS_FAILED(thread->SetPriority(priority))) {
1507 NS_WARNING("Could not set the thread's priority!");
1508 }
1509
1510 JSContext* cx = CycleCollectedJSContext::Get()->Context();
1511 nsCOMPtr<nsIRunnable> runnable = new WorkerThreadPrimaryRunnable(
1512 aWorkerPrivate, thread, JS_GetParentRuntime(cx));
1513 if (NS_FAILED(
1514 thread->DispatchPrimaryRunnable(friendKey, runnable.forget()))) {
1515 UnregisterWorker(aWorkerPrivate);
1516 return false;
1517 }
1518
1519 return true;
1520 }
1521
1522 // static
ShutdownIdleThreads(nsITimer * aTimer,void *)1523 void RuntimeService::ShutdownIdleThreads(nsITimer* aTimer,
1524 void* /* aClosure */) {
1525 AssertIsOnMainThread();
1526
1527 RuntimeService* runtime = RuntimeService::GetService();
1528 NS_ASSERTION(runtime, "This should never be null!");
1529
1530 NS_ASSERTION(aTimer == runtime->mIdleThreadTimer, "Wrong timer!");
1531
1532 // Cheat a little and grab all threads that expire within one second of now.
1533 TimeStamp now = TimeStamp::NowLoRes() + TimeDuration::FromSeconds(1);
1534
1535 TimeStamp nextExpiration;
1536
1537 AutoTArray<RefPtr<WorkerThread>, 20> expiredThreads;
1538 {
1539 MutexAutoLock lock(runtime->mMutex);
1540
1541 for (uint32_t index = 0; index < runtime->mIdleThreadArray.Length();
1542 index++) {
1543 IdleThreadInfo& info = runtime->mIdleThreadArray[index];
1544 if (info.mExpirationTime > now) {
1545 nextExpiration = info.mExpirationTime;
1546 break;
1547 }
1548
1549 RefPtr<WorkerThread>* thread = expiredThreads.AppendElement();
1550 thread->swap(info.mThread);
1551 }
1552
1553 if (!expiredThreads.IsEmpty()) {
1554 runtime->mIdleThreadArray.RemoveElementsAt(0, expiredThreads.Length());
1555 }
1556 }
1557
1558 if (!nextExpiration.IsNull()) {
1559 TimeDuration delta = nextExpiration - TimeStamp::NowLoRes();
1560 uint32_t delay(delta > TimeDuration(0) ? delta.ToMilliseconds() : 0);
1561
1562 // Reschedule the timer.
1563 MOZ_ALWAYS_SUCCEEDS(aTimer->InitWithNamedFuncCallback(
1564 ShutdownIdleThreads, nullptr, delay, nsITimer::TYPE_ONE_SHOT,
1565 "RuntimeService::ShutdownIdleThreads"));
1566 }
1567
1568 for (uint32_t index = 0; index < expiredThreads.Length(); index++) {
1569 if (NS_FAILED(expiredThreads[index]->Shutdown())) {
1570 NS_WARNING("Failed to shutdown thread!");
1571 }
1572 }
1573 }
1574
Init()1575 nsresult RuntimeService::Init() {
1576 AssertIsOnMainThread();
1577
1578 nsLayoutStatics::AddRef();
1579
1580 // Initialize JSSettings.
1581 if (sDefaultJSSettings.gcSettings[0].key.isNothing()) {
1582 sDefaultJSSettings.contextOptions = JS::ContextOptions();
1583 sDefaultJSSettings.chrome.maxScriptRuntime = -1;
1584 sDefaultJSSettings.content.maxScriptRuntime = MAX_SCRIPT_RUN_TIME_SEC;
1585 #ifdef JS_GC_ZEAL
1586 sDefaultJSSettings.gcZealFrequency = JS_DEFAULT_ZEAL_FREQ;
1587 sDefaultJSSettings.gcZeal = 0;
1588 #endif
1589 SetDefaultJSGCSettings(JSGC_MAX_BYTES, WORKER_DEFAULT_RUNTIME_HEAPSIZE);
1590 SetDefaultJSGCSettings(JSGC_ALLOCATION_THRESHOLD,
1591 WORKER_DEFAULT_ALLOCATION_THRESHOLD);
1592 }
1593
1594 // nsIStreamTransportService is thread-safe but it must be initialized on the
1595 // main-thread. FileReader needs it, so, let's initialize it now.
1596 nsresult rv;
1597 nsCOMPtr<nsIStreamTransportService> sts =
1598 do_GetService(kStreamTransportServiceCID, &rv);
1599 NS_ENSURE_TRUE(sts, NS_ERROR_FAILURE);
1600
1601 mIdleThreadTimer = NS_NewTimer();
1602 NS_ENSURE_STATE(mIdleThreadTimer);
1603
1604 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
1605 NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE);
1606
1607 rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, false);
1608 NS_ENSURE_SUCCESS(rv, rv);
1609
1610 rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
1611 NS_ENSURE_SUCCESS(rv, rv);
1612
1613 mObserved = true;
1614
1615 if (NS_FAILED(obs->AddObserver(this, GC_REQUEST_OBSERVER_TOPIC, false))) {
1616 NS_WARNING("Failed to register for GC request notifications!");
1617 }
1618
1619 if (NS_FAILED(obs->AddObserver(this, CC_REQUEST_OBSERVER_TOPIC, false))) {
1620 NS_WARNING("Failed to register for CC request notifications!");
1621 }
1622
1623 if (NS_FAILED(
1624 obs->AddObserver(this, MEMORY_PRESSURE_OBSERVER_TOPIC, false))) {
1625 NS_WARNING("Failed to register for memory pressure notifications!");
1626 }
1627
1628 if (NS_FAILED(
1629 obs->AddObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC, false))) {
1630 NS_WARNING("Failed to register for offline notification event!");
1631 }
1632
1633 MOZ_ASSERT(!gRuntimeServiceDuringInit, "This should be false!");
1634 gRuntimeServiceDuringInit = true;
1635
1636 if (NS_FAILED(Preferences::RegisterPrefixCallback(
1637 LoadJSGCMemoryOptions,
1638 PREF_JS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX)) ||
1639 NS_FAILED(Preferences::RegisterPrefixCallbackAndCall(
1640 LoadJSGCMemoryOptions,
1641 PREF_WORKERS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX)) ||
1642 #ifdef JS_GC_ZEAL
1643 NS_FAILED(Preferences::RegisterCallback(
1644 LoadGCZealOptions, PREF_JS_OPTIONS_PREFIX PREF_GCZEAL)) ||
1645 #endif
1646
1647 #define WORKER_PREF(name, callback) \
1648 NS_FAILED(Preferences::RegisterCallbackAndCall(callback, name)) ||
1649 WORKER_PREF("intl.accept_languages", PrefLanguagesChanged) WORKER_PREF(
1650 "general.appname.override", AppNameOverrideChanged)
1651 WORKER_PREF("general.appversion.override", AppVersionOverrideChanged)
1652 WORKER_PREF("general.platform.override", PlatformOverrideChanged)
1653 #ifdef JS_GC_ZEAL
1654 WORKER_PREF("dom.workers.options.gcZeal", LoadGCZealOptions)
1655 #endif
1656 #undef WORKER_PREF
1657
1658 NS_FAILED(Preferences::RegisterPrefixCallbackAndCall(
1659 LoadContextOptions, PREF_WORKERS_OPTIONS_PREFIX)) ||
1660 NS_FAILED(Preferences::RegisterPrefixCallback(LoadContextOptions,
1661 PREF_JS_OPTIONS_PREFIX))) {
1662 NS_WARNING("Failed to register pref callbacks!");
1663 }
1664
1665 MOZ_ASSERT(gRuntimeServiceDuringInit, "Should be true!");
1666 gRuntimeServiceDuringInit = false;
1667
1668 // We assume atomic 32bit reads/writes. If this assumption doesn't hold on
1669 // some wacky platform then the worst that could happen is that the close
1670 // handler will run for a slightly different amount of time.
1671 if (NS_FAILED(Preferences::AddIntVarCache(
1672 &sDefaultJSSettings.content.maxScriptRuntime,
1673 PREF_MAX_SCRIPT_RUN_TIME_CONTENT, MAX_SCRIPT_RUN_TIME_SEC)) ||
1674 NS_FAILED(Preferences::AddIntVarCache(
1675 &sDefaultJSSettings.chrome.maxScriptRuntime,
1676 PREF_MAX_SCRIPT_RUN_TIME_CHROME, -1))) {
1677 NS_WARNING("Failed to register timeout cache!");
1678 }
1679
1680 int32_t maxPerDomain =
1681 Preferences::GetInt(PREF_WORKERS_MAX_PER_DOMAIN, MAX_WORKERS_PER_DOMAIN);
1682 gMaxWorkersPerDomain = std::max(0, maxPerDomain);
1683
1684 int32_t maxHardwareConcurrency = Preferences::GetInt(
1685 PREF_WORKERS_MAX_HARDWARE_CONCURRENCY, MAX_HARDWARE_CONCURRENCY);
1686 gMaxHardwareConcurrency = std::max(0, maxHardwareConcurrency);
1687
1688 RefPtr<OSFileConstantsService> osFileConstantsService =
1689 OSFileConstantsService::GetOrCreate();
1690 if (NS_WARN_IF(!osFileConstantsService)) {
1691 return NS_ERROR_FAILURE;
1692 }
1693
1694 if (NS_WARN_IF(!IndexedDatabaseManager::GetOrCreate())) {
1695 return NS_ERROR_UNEXPECTED;
1696 }
1697
1698 // PerformanceService must be initialized on the main-thread.
1699 PerformanceService::GetOrCreate();
1700
1701 return NS_OK;
1702 }
1703
Shutdown()1704 void RuntimeService::Shutdown() {
1705 AssertIsOnMainThread();
1706
1707 MOZ_ASSERT(!mShuttingDown);
1708 // That's it, no more workers.
1709 mShuttingDown = true;
1710
1711 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
1712 NS_WARNING_ASSERTION(obs, "Failed to get observer service?!");
1713
1714 // Tell anyone that cares that they're about to lose worker support.
1715 if (obs && NS_FAILED(obs->NotifyObservers(nullptr, WORKERS_SHUTDOWN_TOPIC,
1716 nullptr))) {
1717 NS_WARNING("NotifyObservers failed!");
1718 }
1719
1720 {
1721 MutexAutoLock lock(mMutex);
1722
1723 AutoTArray<WorkerPrivate*, 100> workers;
1724 AddAllTopLevelWorkersToArray(workers);
1725
1726 if (!workers.IsEmpty()) {
1727 // Cancel all top-level workers.
1728 {
1729 MutexAutoUnlock unlock(mMutex);
1730
1731 for (uint32_t index = 0; index < workers.Length(); index++) {
1732 if (!workers[index]->Kill()) {
1733 NS_WARNING("Failed to cancel worker!");
1734 }
1735 }
1736 }
1737 }
1738 }
1739 }
1740
1741 namespace {
1742
1743 class CrashIfHangingRunnable : public WorkerControlRunnable {
1744 public:
CrashIfHangingRunnable(WorkerPrivate * aWorkerPrivate)1745 explicit CrashIfHangingRunnable(WorkerPrivate* aWorkerPrivate)
1746 : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
1747 mMonitor("CrashIfHangingRunnable::mMonitor") {}
1748
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)1749 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
1750 aWorkerPrivate->DumpCrashInformation(mMsg);
1751
1752 MonitorAutoLock lock(mMonitor);
1753 lock.Notify();
1754 return true;
1755 }
1756
Cancel()1757 nsresult Cancel() override {
1758 mMsg.Assign("Canceled");
1759
1760 MonitorAutoLock lock(mMonitor);
1761 lock.Notify();
1762
1763 return NS_OK;
1764 }
1765
DispatchAndWait()1766 void DispatchAndWait() {
1767 MonitorAutoLock lock(mMonitor);
1768
1769 if (!Dispatch()) {
1770 mMsg.Assign("Dispatch Error");
1771 return;
1772 }
1773
1774 lock.Wait();
1775 }
1776
MsgData() const1777 const nsCString& MsgData() const { return mMsg; }
1778
1779 private:
PreDispatch(WorkerPrivate * aWorkerPrivate)1780 bool PreDispatch(WorkerPrivate* aWorkerPrivate) override { return true; }
1781
PostDispatch(WorkerPrivate * aWorkerPrivate,bool aDispatchResult)1782 void PostDispatch(WorkerPrivate* aWorkerPrivate,
1783 bool aDispatchResult) override {}
1784
1785 Monitor mMonitor;
1786 nsCString mMsg;
1787 };
1788
1789 } // namespace
1790
CrashIfHanging()1791 void RuntimeService::CrashIfHanging() {
1792 MutexAutoLock lock(mMutex);
1793
1794 if (mDomainMap.IsEmpty()) {
1795 return;
1796 }
1797
1798 uint32_t activeWorkers = 0;
1799 uint32_t activeServiceWorkers = 0;
1800 uint32_t inactiveWorkers = 0;
1801
1802 nsTArray<WorkerPrivate*> workers;
1803
1804 for (auto iter = mDomainMap.Iter(); !iter.Done(); iter.Next()) {
1805 WorkerDomainInfo* aData = iter.UserData();
1806
1807 activeWorkers += aData->mActiveWorkers.Length();
1808 activeServiceWorkers += aData->mActiveServiceWorkers.Length();
1809
1810 workers.AppendElements(aData->mActiveWorkers);
1811 workers.AppendElements(aData->mActiveServiceWorkers);
1812
1813 // These might not be top-level workers...
1814 for (uint32_t index = 0; index < aData->mQueuedWorkers.Length(); index++) {
1815 WorkerPrivate* worker = aData->mQueuedWorkers[index];
1816 if (!worker->GetParent()) {
1817 ++inactiveWorkers;
1818 }
1819 }
1820 }
1821
1822 // We must have something pending...
1823 MOZ_DIAGNOSTIC_ASSERT(activeWorkers + activeServiceWorkers + inactiveWorkers);
1824
1825 nsCString msg;
1826
1827 // A: active Workers | S: active ServiceWorkers | Q: queued Workers
1828 msg.AppendPrintf("Workers Hanging - %d|A:%d|S:%d|Q:%d", mShuttingDown ? 1 : 0,
1829 activeWorkers, activeServiceWorkers, inactiveWorkers);
1830
1831 // For each thread, let's print some data to know what is going wrong.
1832 for (uint32_t i = 0; i < workers.Length(); ++i) {
1833 WorkerPrivate* workerPrivate = workers[i];
1834
1835 // BC: Busy Count
1836 msg.AppendPrintf("-BC:%d", workerPrivate->BusyCount());
1837
1838 RefPtr<CrashIfHangingRunnable> runnable =
1839 new CrashIfHangingRunnable(workerPrivate);
1840 runnable->DispatchAndWait();
1841
1842 msg.Append(runnable->MsgData());
1843 }
1844
1845 // This string will be leaked.
1846 MOZ_CRASH_UNSAFE_OOL(strdup(msg.BeginReading()));
1847 }
1848
1849 // This spins the event loop until all workers are finished and their threads
1850 // have been joined.
Cleanup()1851 void RuntimeService::Cleanup() {
1852 AssertIsOnMainThread();
1853
1854 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
1855 NS_WARNING_ASSERTION(obs, "Failed to get observer service?!");
1856
1857 if (mIdleThreadTimer) {
1858 if (NS_FAILED(mIdleThreadTimer->Cancel())) {
1859 NS_WARNING("Failed to cancel idle timer!");
1860 }
1861 mIdleThreadTimer = nullptr;
1862 }
1863
1864 {
1865 MutexAutoLock lock(mMutex);
1866
1867 AutoTArray<WorkerPrivate*, 100> workers;
1868 AddAllTopLevelWorkersToArray(workers);
1869
1870 if (!workers.IsEmpty()) {
1871 nsIThread* currentThread = NS_GetCurrentThread();
1872 NS_ASSERTION(currentThread, "This should never be null!");
1873
1874 // Shut down any idle threads.
1875 if (!mIdleThreadArray.IsEmpty()) {
1876 AutoTArray<RefPtr<WorkerThread>, 20> idleThreads;
1877
1878 uint32_t idleThreadCount = mIdleThreadArray.Length();
1879 idleThreads.SetLength(idleThreadCount);
1880
1881 for (uint32_t index = 0; index < idleThreadCount; index++) {
1882 NS_ASSERTION(mIdleThreadArray[index].mThread, "Null thread!");
1883 idleThreads[index].swap(mIdleThreadArray[index].mThread);
1884 }
1885
1886 mIdleThreadArray.Clear();
1887
1888 MutexAutoUnlock unlock(mMutex);
1889
1890 for (uint32_t index = 0; index < idleThreadCount; index++) {
1891 if (NS_FAILED(idleThreads[index]->Shutdown())) {
1892 NS_WARNING("Failed to shutdown thread!");
1893 }
1894 }
1895 }
1896
1897 // And make sure all their final messages have run and all their threads
1898 // have joined.
1899 while (mDomainMap.Count()) {
1900 MutexAutoUnlock unlock(mMutex);
1901
1902 if (!NS_ProcessNextEvent(currentThread)) {
1903 NS_WARNING("Something bad happened!");
1904 break;
1905 }
1906 }
1907 }
1908 }
1909
1910 NS_ASSERTION(!mWindowMap.Count(), "All windows should have been released!");
1911
1912 if (mObserved) {
1913 if (NS_FAILED(Preferences::UnregisterPrefixCallback(
1914 LoadContextOptions, PREF_JS_OPTIONS_PREFIX)) ||
1915 NS_FAILED(Preferences::UnregisterPrefixCallback(
1916 LoadContextOptions, PREF_WORKERS_OPTIONS_PREFIX)) ||
1917 #define WORKER_PREF(name, callback) \
1918 NS_FAILED(Preferences::UnregisterCallback(callback, name)) ||
1919 WORKER_PREF("intl.accept_languages", PrefLanguagesChanged) WORKER_PREF(
1920 "general.appname.override",
1921 AppNameOverrideChanged) WORKER_PREF("general.appversion.override",
1922 AppVersionOverrideChanged)
1923 WORKER_PREF("general.platform.override", PlatformOverrideChanged)
1924 #ifdef JS_GC_ZEAL
1925 WORKER_PREF("dom.workers.options.gcZeal", LoadGCZealOptions)
1926 #endif
1927 #undef WORKER_PREF
1928
1929 #ifdef JS_GC_ZEAL
1930 NS_FAILED(Preferences::UnregisterCallback(
1931 LoadGCZealOptions,
1932 PREF_JS_OPTIONS_PREFIX PREF_GCZEAL)) ||
1933 #endif
1934 NS_FAILED(Preferences::UnregisterPrefixCallback(
1935 LoadJSGCMemoryOptions,
1936 PREF_JS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX)) ||
1937 NS_FAILED(Preferences::UnregisterPrefixCallback(
1938 LoadJSGCMemoryOptions,
1939 PREF_WORKERS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX))) {
1940 NS_WARNING("Failed to unregister pref callbacks!");
1941 }
1942
1943 if (obs) {
1944 if (NS_FAILED(obs->RemoveObserver(this, GC_REQUEST_OBSERVER_TOPIC))) {
1945 NS_WARNING("Failed to unregister for GC request notifications!");
1946 }
1947
1948 if (NS_FAILED(obs->RemoveObserver(this, CC_REQUEST_OBSERVER_TOPIC))) {
1949 NS_WARNING("Failed to unregister for CC request notifications!");
1950 }
1951
1952 if (NS_FAILED(
1953 obs->RemoveObserver(this, MEMORY_PRESSURE_OBSERVER_TOPIC))) {
1954 NS_WARNING("Failed to unregister for memory pressure notifications!");
1955 }
1956
1957 if (NS_FAILED(
1958 obs->RemoveObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC))) {
1959 NS_WARNING("Failed to unregister for offline notification event!");
1960 }
1961 obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID);
1962 obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
1963 mObserved = false;
1964 }
1965 }
1966
1967 nsLayoutStatics::Release();
1968 }
1969
AddAllTopLevelWorkersToArray(nsTArray<WorkerPrivate * > & aWorkers)1970 void RuntimeService::AddAllTopLevelWorkersToArray(
1971 nsTArray<WorkerPrivate*>& aWorkers) {
1972 for (auto iter = mDomainMap.Iter(); !iter.Done(); iter.Next()) {
1973 WorkerDomainInfo* aData = iter.UserData();
1974
1975 #ifdef DEBUG
1976 for (uint32_t index = 0; index < aData->mActiveWorkers.Length(); index++) {
1977 MOZ_ASSERT(!aData->mActiveWorkers[index]->GetParent(),
1978 "Shouldn't have a parent in this list!");
1979 }
1980 for (uint32_t index = 0; index < aData->mActiveServiceWorkers.Length();
1981 index++) {
1982 MOZ_ASSERT(!aData->mActiveServiceWorkers[index]->GetParent(),
1983 "Shouldn't have a parent in this list!");
1984 }
1985 #endif
1986
1987 aWorkers.AppendElements(aData->mActiveWorkers);
1988 aWorkers.AppendElements(aData->mActiveServiceWorkers);
1989
1990 // These might not be top-level workers...
1991 for (uint32_t index = 0; index < aData->mQueuedWorkers.Length(); index++) {
1992 WorkerPrivate* worker = aData->mQueuedWorkers[index];
1993 if (!worker->GetParent()) {
1994 aWorkers.AppendElement(worker);
1995 }
1996 }
1997 }
1998 }
1999
GetWorkersForWindow(nsPIDOMWindowInner * aWindow,nsTArray<WorkerPrivate * > & aWorkers)2000 void RuntimeService::GetWorkersForWindow(nsPIDOMWindowInner* aWindow,
2001 nsTArray<WorkerPrivate*>& aWorkers) {
2002 AssertIsOnMainThread();
2003
2004 nsTArray<WorkerPrivate*>* workers;
2005 if (mWindowMap.Get(aWindow, &workers)) {
2006 NS_ASSERTION(!workers->IsEmpty(), "Should have been removed!");
2007 aWorkers.AppendElements(*workers);
2008 } else {
2009 NS_ASSERTION(aWorkers.IsEmpty(), "Should be empty!");
2010 }
2011 }
2012
CancelWorkersForWindow(nsPIDOMWindowInner * aWindow)2013 void RuntimeService::CancelWorkersForWindow(nsPIDOMWindowInner* aWindow) {
2014 AssertIsOnMainThread();
2015
2016 nsTArray<WorkerPrivate*> workers;
2017 GetWorkersForWindow(aWindow, workers);
2018
2019 if (!workers.IsEmpty()) {
2020 for (uint32_t index = 0; index < workers.Length(); index++) {
2021 WorkerPrivate*& worker = workers[index];
2022
2023 if (worker->IsSharedWorker()) {
2024 worker->CloseSharedWorkersForWindow(aWindow);
2025 } else {
2026 worker->Cancel();
2027 }
2028 }
2029 }
2030 }
2031
FreezeWorkersForWindow(nsPIDOMWindowInner * aWindow)2032 void RuntimeService::FreezeWorkersForWindow(nsPIDOMWindowInner* aWindow) {
2033 AssertIsOnMainThread();
2034 MOZ_ASSERT(aWindow);
2035
2036 nsTArray<WorkerPrivate*> workers;
2037 GetWorkersForWindow(aWindow, workers);
2038
2039 for (uint32_t index = 0; index < workers.Length(); index++) {
2040 workers[index]->Freeze(aWindow);
2041 }
2042 }
2043
ThawWorkersForWindow(nsPIDOMWindowInner * aWindow)2044 void RuntimeService::ThawWorkersForWindow(nsPIDOMWindowInner* aWindow) {
2045 AssertIsOnMainThread();
2046 MOZ_ASSERT(aWindow);
2047
2048 nsTArray<WorkerPrivate*> workers;
2049 GetWorkersForWindow(aWindow, workers);
2050
2051 for (uint32_t index = 0; index < workers.Length(); index++) {
2052 workers[index]->Thaw(aWindow);
2053 }
2054 }
2055
SuspendWorkersForWindow(nsPIDOMWindowInner * aWindow)2056 void RuntimeService::SuspendWorkersForWindow(nsPIDOMWindowInner* aWindow) {
2057 AssertIsOnMainThread();
2058 MOZ_ASSERT(aWindow);
2059
2060 nsTArray<WorkerPrivate*> workers;
2061 GetWorkersForWindow(aWindow, workers);
2062
2063 for (uint32_t index = 0; index < workers.Length(); index++) {
2064 workers[index]->ParentWindowPaused();
2065 }
2066 }
2067
ResumeWorkersForWindow(nsPIDOMWindowInner * aWindow)2068 void RuntimeService::ResumeWorkersForWindow(nsPIDOMWindowInner* aWindow) {
2069 AssertIsOnMainThread();
2070 MOZ_ASSERT(aWindow);
2071
2072 nsTArray<WorkerPrivate*> workers;
2073 GetWorkersForWindow(aWindow, workers);
2074
2075 for (uint32_t index = 0; index < workers.Length(); index++) {
2076 workers[index]->ParentWindowResumed();
2077 }
2078 }
2079
CreateSharedWorker(const GlobalObject & aGlobal,const nsAString & aScriptURL,const nsAString & aName,SharedWorker ** aSharedWorker)2080 nsresult RuntimeService::CreateSharedWorker(const GlobalObject& aGlobal,
2081 const nsAString& aScriptURL,
2082 const nsAString& aName,
2083 SharedWorker** aSharedWorker) {
2084 AssertIsOnMainThread();
2085
2086 nsCOMPtr<nsPIDOMWindowInner> window =
2087 do_QueryInterface(aGlobal.GetAsSupports());
2088 MOZ_ASSERT(window);
2089
2090 // If the window is blocked from accessing storage, do not allow it
2091 // to connect to a SharedWorker. This would potentially allow it
2092 // to communicate with other windows that do have storage access.
2093 // Allow private browsing, however, as we handle that isolation
2094 // via the principal.
2095 auto storageAllowed = nsContentUtils::StorageAllowedForWindow(window);
2096 if (storageAllowed != nsContentUtils::StorageAccess::eAllow &&
2097 storageAllowed != nsContentUtils::StorageAccess::ePrivateBrowsing) {
2098 return NS_ERROR_DOM_SECURITY_ERR;
2099 }
2100
2101 // Assert that the principal private browsing state matches the
2102 // StorageAccess value.
2103 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
2104 if (storageAllowed == nsContentUtils::StorageAccess::ePrivateBrowsing) {
2105 nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
2106 nsCOMPtr<nsIPrincipal> principal = doc ? doc->NodePrincipal() : nullptr;
2107 uint32_t privateBrowsingId = 0;
2108 if (principal) {
2109 MOZ_ALWAYS_SUCCEEDS(principal->GetPrivateBrowsingId(&privateBrowsingId));
2110 }
2111 MOZ_DIAGNOSTIC_ASSERT(privateBrowsingId != 0);
2112 }
2113 #endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
2114
2115 JSContext* cx = aGlobal.Context();
2116
2117 WorkerLoadInfo loadInfo;
2118 nsresult rv = WorkerPrivate::GetLoadInfo(
2119 cx, window, nullptr, aScriptURL, false, WorkerPrivate::OverrideLoadGroup,
2120 WorkerTypeShared, &loadInfo);
2121 NS_ENSURE_SUCCESS(rv, rv);
2122
2123 return CreateSharedWorkerFromLoadInfo(cx, &loadInfo, aScriptURL, aName,
2124 aSharedWorker);
2125 }
2126
CreateSharedWorkerFromLoadInfo(JSContext * aCx,WorkerLoadInfo * aLoadInfo,const nsAString & aScriptURL,const nsAString & aName,SharedWorker ** aSharedWorker)2127 nsresult RuntimeService::CreateSharedWorkerFromLoadInfo(
2128 JSContext* aCx, WorkerLoadInfo* aLoadInfo, const nsAString& aScriptURL,
2129 const nsAString& aName, SharedWorker** aSharedWorker) {
2130 AssertIsOnMainThread();
2131 MOZ_ASSERT(aLoadInfo);
2132 MOZ_ASSERT(aLoadInfo->mResolvedScriptURI);
2133
2134 RefPtr<WorkerPrivate> workerPrivate;
2135 {
2136 MutexAutoLock lock(mMutex);
2137
2138 nsCString scriptSpec;
2139 nsresult rv = aLoadInfo->mResolvedScriptURI->GetSpec(scriptSpec);
2140 NS_ENSURE_SUCCESS(rv, rv);
2141
2142 MOZ_DIAGNOSTIC_ASSERT(aLoadInfo->mPrincipal &&
2143 aLoadInfo->mLoadingPrincipal);
2144
2145 WorkerDomainInfo* domainInfo;
2146 if (mDomainMap.Get(aLoadInfo->mDomain, &domainInfo)) {
2147 for (const UniquePtr<SharedWorkerInfo>& data :
2148 domainInfo->mSharedWorkerInfos) {
2149 if (data->mScriptSpec == scriptSpec && data->mName == aName &&
2150 // We want to be sure that the window's principal subsumes the
2151 // SharedWorker's loading principal and vice versa.
2152 aLoadInfo->mLoadingPrincipal->Subsumes(
2153 data->mWorkerPrivate->GetLoadingPrincipal()) &&
2154 data->mWorkerPrivate->GetLoadingPrincipal()->Subsumes(
2155 aLoadInfo->mLoadingPrincipal)) {
2156 workerPrivate = data->mWorkerPrivate;
2157 break;
2158 }
2159 }
2160 }
2161 }
2162
2163 // Keep a reference to the window before spawning the worker. If the worker is
2164 // a Shared/Service worker and the worker script loads and executes before
2165 // the SharedWorker object itself is created before then WorkerScriptLoaded()
2166 // will reset the loadInfo's window.
2167 nsCOMPtr<nsPIDOMWindowInner> window = aLoadInfo->mWindow;
2168
2169 // shouldAttachToWorkerPrivate tracks whether our SharedWorker should actually
2170 // get attached to the WorkerPrivate we're using. It will become false if the
2171 // WorkerPrivate already exists and its secure context state doesn't match
2172 // what we want for the new SharedWorker.
2173 bool shouldAttachToWorkerPrivate = true;
2174 bool created = false;
2175 ErrorResult rv;
2176 if (!workerPrivate) {
2177 workerPrivate =
2178 WorkerPrivate::Constructor(aCx, aScriptURL, false, WorkerTypeShared,
2179 aName, VoidCString(), aLoadInfo, rv);
2180 NS_ENSURE_TRUE(workerPrivate, rv.StealNSResult());
2181
2182 created = true;
2183 } else {
2184 // Check whether the secure context state matches. The current compartment
2185 // of aCx is the compartment of the SharedWorker constructor that was
2186 // invoked, which is the compartment of the document that will be hooked up
2187 // to the worker, so that's what we want to check.
2188 shouldAttachToWorkerPrivate =
2189 workerPrivate->IsSecureContext() ==
2190 JS_GetIsSecureContext(js::GetContextCompartment(aCx));
2191
2192 // If we're attaching to an existing SharedWorker private, then we
2193 // must update the overriden load group to account for our document's
2194 // load group.
2195 if (shouldAttachToWorkerPrivate) {
2196 workerPrivate->UpdateOverridenLoadGroup(aLoadInfo->mLoadGroup);
2197 }
2198 }
2199
2200 // We don't actually care about this MessageChannel, but we use it to 'steal'
2201 // its 2 connected ports.
2202 RefPtr<MessageChannel> channel =
2203 MessageChannel::Constructor(window->AsGlobal(), rv);
2204 if (NS_WARN_IF(rv.Failed())) {
2205 return rv.StealNSResult();
2206 }
2207
2208 RefPtr<SharedWorker> sharedWorker =
2209 new SharedWorker(window, workerPrivate, channel->Port1());
2210
2211 if (!shouldAttachToWorkerPrivate) {
2212 // We're done here. Just queue up our error event and return our
2213 // dead-on-arrival SharedWorker.
2214 RefPtr<AsyncEventDispatcher> errorEvent = new AsyncEventDispatcher(
2215 sharedWorker, NS_LITERAL_STRING("error"), false);
2216 errorEvent->PostDOMEvent();
2217 sharedWorker.forget(aSharedWorker);
2218 return NS_OK;
2219 }
2220
2221 if (!workerPrivate->RegisterSharedWorker(sharedWorker, channel->Port2())) {
2222 NS_WARNING("Worker is unreachable, this shouldn't happen!");
2223 sharedWorker->Close();
2224 return NS_ERROR_FAILURE;
2225 }
2226
2227 // This is normally handled in RegisterWorker, but that wasn't called if the
2228 // worker already existed.
2229 if (!created) {
2230 nsTArray<WorkerPrivate*>* windowArray;
2231 if (!mWindowMap.Get(window, &windowArray)) {
2232 windowArray = new nsTArray<WorkerPrivate*>(1);
2233 mWindowMap.Put(window, windowArray);
2234 }
2235
2236 if (!windowArray->Contains(workerPrivate)) {
2237 windowArray->AppendElement(workerPrivate);
2238 }
2239 }
2240
2241 sharedWorker.forget(aSharedWorker);
2242 return NS_OK;
2243 }
2244
ForgetSharedWorker(WorkerPrivate * aWorkerPrivate)2245 void RuntimeService::ForgetSharedWorker(WorkerPrivate* aWorkerPrivate) {
2246 AssertIsOnMainThread();
2247 MOZ_ASSERT(aWorkerPrivate);
2248 MOZ_ASSERT(aWorkerPrivate->IsSharedWorker());
2249
2250 MutexAutoLock lock(mMutex);
2251
2252 WorkerDomainInfo* domainInfo;
2253 if (mDomainMap.Get(aWorkerPrivate->Domain(), &domainInfo)) {
2254 RemoveSharedWorker(domainInfo, aWorkerPrivate);
2255 }
2256 }
2257
NoteIdleThread(WorkerThread * aThread)2258 void RuntimeService::NoteIdleThread(WorkerThread* aThread) {
2259 AssertIsOnMainThread();
2260 MOZ_ASSERT(aThread);
2261
2262 bool shutdownThread = mShuttingDown;
2263 bool scheduleTimer = false;
2264
2265 if (!shutdownThread) {
2266 static TimeDuration timeout =
2267 TimeDuration::FromSeconds(IDLE_THREAD_TIMEOUT_SEC);
2268
2269 TimeStamp expirationTime = TimeStamp::NowLoRes() + timeout;
2270
2271 MutexAutoLock lock(mMutex);
2272
2273 uint32_t previousIdleCount = mIdleThreadArray.Length();
2274
2275 if (previousIdleCount < MAX_IDLE_THREADS) {
2276 IdleThreadInfo* info = mIdleThreadArray.AppendElement();
2277 info->mThread = aThread;
2278 info->mExpirationTime = expirationTime;
2279
2280 scheduleTimer = previousIdleCount == 0;
2281 } else {
2282 shutdownThread = true;
2283 }
2284 }
2285
2286 MOZ_ASSERT_IF(shutdownThread, !scheduleTimer);
2287 MOZ_ASSERT_IF(scheduleTimer, !shutdownThread);
2288
2289 // Too many idle threads, just shut this one down.
2290 if (shutdownThread) {
2291 MOZ_ALWAYS_SUCCEEDS(aThread->Shutdown());
2292 } else if (scheduleTimer) {
2293 MOZ_ALWAYS_SUCCEEDS(mIdleThreadTimer->InitWithNamedFuncCallback(
2294 ShutdownIdleThreads, nullptr, IDLE_THREAD_TIMEOUT_SEC * 1000,
2295 nsITimer::TYPE_ONE_SHOT, "RuntimeService::ShutdownIdleThreads"));
2296 }
2297 }
2298
UpdateAllWorkerContextOptions()2299 void RuntimeService::UpdateAllWorkerContextOptions() {
2300 BROADCAST_ALL_WORKERS(UpdateContextOptions,
2301 sDefaultJSSettings.contextOptions);
2302 }
2303
UpdateAppNameOverridePreference(const nsAString & aValue)2304 void RuntimeService::UpdateAppNameOverridePreference(const nsAString& aValue) {
2305 AssertIsOnMainThread();
2306 mNavigatorProperties.mAppNameOverridden = aValue;
2307 }
2308
UpdateAppVersionOverridePreference(const nsAString & aValue)2309 void RuntimeService::UpdateAppVersionOverridePreference(
2310 const nsAString& aValue) {
2311 AssertIsOnMainThread();
2312 mNavigatorProperties.mAppVersionOverridden = aValue;
2313 }
2314
UpdatePlatformOverridePreference(const nsAString & aValue)2315 void RuntimeService::UpdatePlatformOverridePreference(const nsAString& aValue) {
2316 AssertIsOnMainThread();
2317 mNavigatorProperties.mPlatformOverridden = aValue;
2318 }
2319
UpdateAllWorkerLanguages(const nsTArray<nsString> & aLanguages)2320 void RuntimeService::UpdateAllWorkerLanguages(
2321 const nsTArray<nsString>& aLanguages) {
2322 MOZ_ASSERT(NS_IsMainThread());
2323
2324 mNavigatorProperties.mLanguages = aLanguages;
2325 BROADCAST_ALL_WORKERS(UpdateLanguages, aLanguages);
2326 }
2327
UpdateAllWorkerMemoryParameter(JSGCParamKey aKey,uint32_t aValue)2328 void RuntimeService::UpdateAllWorkerMemoryParameter(JSGCParamKey aKey,
2329 uint32_t aValue) {
2330 BROADCAST_ALL_WORKERS(UpdateJSWorkerMemoryParameter, aKey, aValue);
2331 }
2332
2333 #ifdef JS_GC_ZEAL
UpdateAllWorkerGCZeal()2334 void RuntimeService::UpdateAllWorkerGCZeal() {
2335 BROADCAST_ALL_WORKERS(UpdateGCZeal, sDefaultJSSettings.gcZeal,
2336 sDefaultJSSettings.gcZealFrequency);
2337 }
2338 #endif
2339
GarbageCollectAllWorkers(bool aShrinking)2340 void RuntimeService::GarbageCollectAllWorkers(bool aShrinking) {
2341 BROADCAST_ALL_WORKERS(GarbageCollect, aShrinking);
2342 }
2343
CycleCollectAllWorkers()2344 void RuntimeService::CycleCollectAllWorkers() {
2345 BROADCAST_ALL_WORKERS(CycleCollect, /* dummy = */ false);
2346 }
2347
SendOfflineStatusChangeEventToAllWorkers(bool aIsOffline)2348 void RuntimeService::SendOfflineStatusChangeEventToAllWorkers(bool aIsOffline) {
2349 BROADCAST_ALL_WORKERS(OfflineStatusChangeEvent, aIsOffline);
2350 }
2351
MemoryPressureAllWorkers()2352 void RuntimeService::MemoryPressureAllWorkers() {
2353 BROADCAST_ALL_WORKERS(MemoryPressure, /* dummy = */ false);
2354 }
2355
ClampedHardwareConcurrency() const2356 uint32_t RuntimeService::ClampedHardwareConcurrency() const {
2357 // The Firefox Hardware Report says 70% of Firefox users have exactly 2 cores.
2358 // When the resistFingerprinting pref is set, we want to blend into the crowd
2359 // so spoof navigator.hardwareConcurrency = 2 to reduce user uniqueness.
2360 if (MOZ_UNLIKELY(nsContentUtils::ShouldResistFingerprinting())) {
2361 return 2;
2362 }
2363
2364 // This needs to be atomic, because multiple workers, and even mainthread,
2365 // could race to initialize it at once.
2366 static Atomic<uint32_t> clampedHardwareConcurrency;
2367
2368 // No need to loop here: if compareExchange fails, that just means that some
2369 // other worker has initialized numberOfProcessors, so we're good to go.
2370 if (!clampedHardwareConcurrency) {
2371 int32_t numberOfProcessors = PR_GetNumberOfProcessors();
2372 if (numberOfProcessors <= 0) {
2373 numberOfProcessors = 1; // Must be one there somewhere
2374 }
2375 uint32_t clampedValue =
2376 std::min(uint32_t(numberOfProcessors), gMaxHardwareConcurrency);
2377 clampedHardwareConcurrency.compareExchange(0, clampedValue);
2378 }
2379
2380 return clampedHardwareConcurrency;
2381 }
2382
2383 // nsISupports
NS_IMPL_ISUPPORTS(RuntimeService,nsIObserver)2384 NS_IMPL_ISUPPORTS(RuntimeService, nsIObserver)
2385
2386 // nsIObserver
2387 NS_IMETHODIMP
2388 RuntimeService::Observe(nsISupports* aSubject, const char* aTopic,
2389 const char16_t* aData) {
2390 AssertIsOnMainThread();
2391
2392 if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
2393 Shutdown();
2394 return NS_OK;
2395 }
2396 if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID)) {
2397 Cleanup();
2398 return NS_OK;
2399 }
2400 if (!strcmp(aTopic, GC_REQUEST_OBSERVER_TOPIC)) {
2401 GarbageCollectAllWorkers(/* shrinking = */ false);
2402 return NS_OK;
2403 }
2404 if (!strcmp(aTopic, CC_REQUEST_OBSERVER_TOPIC)) {
2405 CycleCollectAllWorkers();
2406 return NS_OK;
2407 }
2408 if (!strcmp(aTopic, MEMORY_PRESSURE_OBSERVER_TOPIC)) {
2409 GarbageCollectAllWorkers(/* shrinking = */ true);
2410 CycleCollectAllWorkers();
2411 MemoryPressureAllWorkers();
2412 return NS_OK;
2413 }
2414 if (!strcmp(aTopic, NS_IOSERVICE_OFFLINE_STATUS_TOPIC)) {
2415 SendOfflineStatusChangeEventToAllWorkers(NS_IsOffline());
2416 return NS_OK;
2417 }
2418
2419 NS_NOTREACHED("Unknown observer topic!");
2420 return NS_OK;
2421 }
2422
MainThreadRun()2423 bool LogViolationDetailsRunnable::MainThreadRun() {
2424 AssertIsOnMainThread();
2425
2426 nsIContentSecurityPolicy* csp = mWorkerPrivate->GetCSP();
2427 if (csp) {
2428 NS_NAMED_LITERAL_STRING(
2429 scriptSample, "Call to eval() or related function blocked by CSP.");
2430 if (mWorkerPrivate->GetReportCSPViolations()) {
2431 csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL,
2432 mFileName, scriptSample, mLineNum, EmptyString(),
2433 EmptyString());
2434 }
2435 }
2436
2437 return true;
2438 }
2439
2440 NS_IMETHODIMP
Run()2441 WorkerThreadPrimaryRunnable::Run() {
2442 AUTO_PROFILER_LABEL_DYNAMIC_LOSSY_NSSTRING(
2443 "WorkerThreadPrimaryRunnable::Run", OTHER, mWorkerPrivate->ScriptURL());
2444
2445 using mozilla::ipc::BackgroundChild;
2446
2447 // Note: GetOrCreateForCurrentThread() must be called prior to
2448 // mWorkerPrivate->SetThread() in order to avoid accidentally consuming
2449 // worker messages here.
2450 if (NS_WARN_IF(!BackgroundChild::GetOrCreateForCurrentThread())) {
2451 // XXX need to fire an error at parent.
2452 // Failed in creating BackgroundChild: probably in shutdown. Continue to run
2453 // without BackgroundChild created.
2454 }
2455
2456 class MOZ_STACK_CLASS SetThreadHelper final {
2457 // Raw pointer: this class is on the stack.
2458 WorkerPrivate* mWorkerPrivate;
2459 RefPtr<AbstractThread> mAbstractThread;
2460
2461 public:
2462 SetThreadHelper(WorkerPrivate* aWorkerPrivate, WorkerThread* aThread)
2463 : mWorkerPrivate(aWorkerPrivate),
2464 mAbstractThread(AbstractThread::CreateXPCOMThreadWrapper(
2465 NS_GetCurrentThread(), false)) {
2466 MOZ_ASSERT(aWorkerPrivate);
2467 MOZ_ASSERT(aThread);
2468
2469 mWorkerPrivate->SetThread(aThread);
2470 }
2471
2472 ~SetThreadHelper() {
2473 if (mWorkerPrivate) {
2474 mWorkerPrivate->SetThread(nullptr);
2475 }
2476 }
2477
2478 void Nullify() {
2479 MOZ_ASSERT(mWorkerPrivate);
2480 mWorkerPrivate->SetThread(nullptr);
2481 mWorkerPrivate = nullptr;
2482 }
2483 };
2484
2485 SetThreadHelper threadHelper(mWorkerPrivate, mThread);
2486
2487 mWorkerPrivate->AssertIsOnWorkerThread();
2488
2489 {
2490 nsCycleCollector_startup();
2491
2492 WorkerJSContext context(mWorkerPrivate);
2493 nsresult rv = context.Initialize(mParentRuntime);
2494 if (NS_WARN_IF(NS_FAILED(rv))) {
2495 return rv;
2496 }
2497
2498 JSContext* cx = context.Context();
2499
2500 if (!InitJSContextForWorker(mWorkerPrivate, cx)) {
2501 // XXX need to fire an error at parent.
2502 NS_ERROR("Failed to create context!");
2503 return NS_ERROR_FAILURE;
2504 }
2505
2506 {
2507 PROFILER_SET_JS_CONTEXT(cx);
2508
2509 {
2510 JSAutoRequest ar(cx);
2511
2512 mWorkerPrivate->DoRunLoop(cx);
2513 // The AutoJSAPI in DoRunLoop should have reported any exceptions left
2514 // on cx. Note that we still need the JSAutoRequest above because
2515 // AutoJSAPI on workers does NOT enter a request!
2516 MOZ_ASSERT(!JS_IsExceptionPending(cx));
2517 }
2518
2519 BackgroundChild::CloseForCurrentThread();
2520
2521 PROFILER_CLEAR_JS_CONTEXT();
2522 }
2523
2524 // There may still be runnables on the debugger event queue that hold a
2525 // strong reference to the debugger global scope. These runnables are not
2526 // visible to the cycle collector, so we need to make sure to clear the
2527 // debugger event queue before we try to destroy the context. If we don't,
2528 // the garbage collector will crash.
2529 mWorkerPrivate->ClearDebuggerEventQueue();
2530
2531 // Perform a full GC. This will collect the main worker global and CC,
2532 // which should break all cycles that touch JS.
2533 JS_GC(cx);
2534
2535 // Before shutting down the cycle collector we need to do one more pass
2536 // through the event loop to clean up any C++ objects that need deferred
2537 // cleanup.
2538 mWorkerPrivate->ClearMainEventQueue(WorkerPrivate::WorkerRan);
2539
2540 // Now WorkerJSContext goes out of scope and its destructor will shut
2541 // down the cycle collector. This breaks any remaining cycles and collects
2542 // any remaining C++ objects.
2543 }
2544
2545 threadHelper.Nullify();
2546
2547 mWorkerPrivate->ScheduleDeletion(WorkerPrivate::WorkerRan);
2548
2549 // It is no longer safe to touch mWorkerPrivate.
2550 mWorkerPrivate = nullptr;
2551
2552 // Now recycle this thread.
2553 nsCOMPtr<nsIEventTarget> mainTarget = GetMainThreadEventTarget();
2554 MOZ_ASSERT(mainTarget);
2555
2556 RefPtr<FinishedRunnable> finishedRunnable =
2557 new FinishedRunnable(mThread.forget());
2558 MOZ_ALWAYS_SUCCEEDS(
2559 mainTarget->Dispatch(finishedRunnable, NS_DISPATCH_NORMAL));
2560
2561 return NS_OK;
2562 }
2563
2564 NS_IMETHODIMP
Run()2565 WorkerThreadPrimaryRunnable::FinishedRunnable::Run() {
2566 AssertIsOnMainThread();
2567
2568 RefPtr<WorkerThread> thread;
2569 mThread.swap(thread);
2570
2571 RuntimeService* rts = RuntimeService::GetService();
2572 if (rts) {
2573 rts->NoteIdleThread(thread);
2574 } else if (thread->ShutdownRequired()) {
2575 MOZ_ALWAYS_SUCCEEDS(thread->Shutdown());
2576 }
2577
2578 return NS_OK;
2579 }
2580
2581 } // workerinternals namespace
2582
CancelWorkersForWindow(nsPIDOMWindowInner * aWindow)2583 void CancelWorkersForWindow(nsPIDOMWindowInner* aWindow) {
2584 AssertIsOnMainThread();
2585 RuntimeService* runtime = RuntimeService::GetService();
2586 if (runtime) {
2587 runtime->CancelWorkersForWindow(aWindow);
2588 }
2589 }
2590
FreezeWorkersForWindow(nsPIDOMWindowInner * aWindow)2591 void FreezeWorkersForWindow(nsPIDOMWindowInner* aWindow) {
2592 AssertIsOnMainThread();
2593 RuntimeService* runtime = RuntimeService::GetService();
2594 if (runtime) {
2595 runtime->FreezeWorkersForWindow(aWindow);
2596 }
2597 }
2598
ThawWorkersForWindow(nsPIDOMWindowInner * aWindow)2599 void ThawWorkersForWindow(nsPIDOMWindowInner* aWindow) {
2600 AssertIsOnMainThread();
2601 RuntimeService* runtime = RuntimeService::GetService();
2602 if (runtime) {
2603 runtime->ThawWorkersForWindow(aWindow);
2604 }
2605 }
2606
SuspendWorkersForWindow(nsPIDOMWindowInner * aWindow)2607 void SuspendWorkersForWindow(nsPIDOMWindowInner* aWindow) {
2608 AssertIsOnMainThread();
2609 RuntimeService* runtime = RuntimeService::GetService();
2610 if (runtime) {
2611 runtime->SuspendWorkersForWindow(aWindow);
2612 }
2613 }
2614
ResumeWorkersForWindow(nsPIDOMWindowInner * aWindow)2615 void ResumeWorkersForWindow(nsPIDOMWindowInner* aWindow) {
2616 AssertIsOnMainThread();
2617 RuntimeService* runtime = RuntimeService::GetService();
2618 if (runtime) {
2619 runtime->ResumeWorkersForWindow(aWindow);
2620 }
2621 }
2622
GetWorkerPrivateFromContext(JSContext * aCx)2623 WorkerPrivate* GetWorkerPrivateFromContext(JSContext* aCx) {
2624 MOZ_ASSERT(!NS_IsMainThread());
2625 MOZ_ASSERT(aCx);
2626
2627 void* cxPrivate = JS_GetContextPrivate(aCx);
2628 if (!cxPrivate) {
2629 return nullptr;
2630 }
2631
2632 return static_cast<WorkerThreadContextPrivate*>(cxPrivate)
2633 ->GetWorkerPrivate();
2634 }
2635
GetCurrentThreadWorkerPrivate()2636 WorkerPrivate* GetCurrentThreadWorkerPrivate() {
2637 MOZ_ASSERT(!NS_IsMainThread());
2638
2639 CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get();
2640 if (!ccjscx) {
2641 return nullptr;
2642 }
2643
2644 JSContext* cx = ccjscx->Context();
2645 MOZ_ASSERT(cx);
2646
2647 // Note that we can return nullptr if the nsCycleCollector_shutdown() in
2648 // ~WorkerJSContext() triggers any calls to GetCurrentThreadWorkerPrivate().
2649 // At this stage CycleCollectedJSContext::Get() will still return a context,
2650 // but the context private has already been cleared.
2651 return GetWorkerPrivateFromContext(cx);
2652 }
2653
IsCurrentThreadRunningChromeWorker()2654 bool IsCurrentThreadRunningChromeWorker() {
2655 return GetCurrentThreadWorkerPrivate()->UsesSystemPrincipal();
2656 }
2657
GetCurrentWorkerThreadJSContext()2658 JSContext* GetCurrentWorkerThreadJSContext() {
2659 WorkerPrivate* wp = GetCurrentThreadWorkerPrivate();
2660 if (!wp) {
2661 return nullptr;
2662 }
2663 return wp->GetJSContext();
2664 }
2665
GetCurrentThreadWorkerGlobal()2666 JSObject* GetCurrentThreadWorkerGlobal() {
2667 WorkerPrivate* wp = GetCurrentThreadWorkerPrivate();
2668 if (!wp) {
2669 return nullptr;
2670 }
2671 WorkerGlobalScope* scope = wp->GlobalScope();
2672 if (!scope) {
2673 return nullptr;
2674 }
2675 return scope->GetGlobalJSObject();
2676 }
2677
2678 } // namespace dom
2679 } // namespace mozilla
2680