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 "WorkerPrivate.h"
8
9 #include <utility>
10
11 #include "js/CompilationAndEvaluation.h"
12 #include "js/ContextOptions.h"
13 #include "js/Exception.h"
14 #include "js/friend/ErrorMessages.h" // JSMSG_OUT_OF_MEMORY
15 #include "js/LocaleSensitive.h"
16 #include "js/MemoryMetrics.h"
17 #include "js/SourceText.h"
18 #include "MessageEventRunnable.h"
19 #include "mozilla/BasePrincipal.h"
20 #include "mozilla/CycleCollectedJSContext.h"
21 #include "mozilla/ExtensionPolicyService.h"
22 #include "mozilla/ProfilerLabels.h"
23 #include "mozilla/Result.h"
24 #include "mozilla/ScopeExit.h"
25 #include "mozilla/StaticPrefs_browser.h"
26 #include "mozilla/StaticPrefs_dom.h"
27 #include "mozilla/dom/BrowsingContextGroup.h"
28 #include "mozilla/dom/CallbackDebuggerNotification.h"
29 #include "mozilla/dom/ClientManager.h"
30 #include "mozilla/dom/ClientState.h"
31 #include "mozilla/dom/Console.h"
32 #include "mozilla/dom/DocGroup.h"
33 #include "mozilla/dom/Document.h"
34 #include "mozilla/dom/DOMTypes.h"
35 #include "mozilla/dom/Event.h"
36 #include "mozilla/dom/Exceptions.h"
37 #include "mozilla/dom/FunctionBinding.h"
38 #include "mozilla/dom/IndexedDatabaseManager.h"
39 #include "mozilla/dom/MessageEvent.h"
40 #include "mozilla/dom/MessageEventBinding.h"
41 #include "mozilla/dom/MessagePort.h"
42 #include "mozilla/dom/MessagePortBinding.h"
43 #include "mozilla/dom/nsCSPContext.h"
44 #include "mozilla/dom/nsCSPUtils.h"
45 #include "mozilla/dom/Performance.h"
46 #include "mozilla/dom/PerformanceStorageWorker.h"
47 #include "mozilla/dom/PromiseDebugging.h"
48 #include "mozilla/dom/RemoteWorkerChild.h"
49 #include "mozilla/dom/RemoteWorkerService.h"
50 #include "mozilla/dom/TimeoutHandler.h"
51 #include "mozilla/dom/WorkerBinding.h"
52 #include "mozilla/dom/JSExecutionManager.h"
53 #include "mozilla/dom/WindowContext.h"
54 #include "mozilla/extensions/WebExtensionPolicy.h"
55 #include "mozilla/StorageAccess.h"
56 #include "mozilla/StoragePrincipalHelper.h"
57 #include "mozilla/Telemetry.h"
58 #include "mozilla/ThreadEventQueue.h"
59 #include "mozilla/ThrottledEventQueue.h"
60 #include "mozilla/TimelineConsumers.h"
61 #include "mozilla/WorkerTimelineMarker.h"
62 #include "nsCycleCollector.h"
63 #include "nsGlobalWindowInner.h"
64 #include "nsNetUtil.h"
65 #include "nsIFile.h"
66 #include "nsIMemoryReporter.h"
67 #include "nsIPermissionManager.h"
68 #include "nsIProtocolHandler.h"
69 #include "nsIScriptError.h"
70 #include "nsIURI.h"
71 #include "nsIURL.h"
72 #include "nsIUUIDGenerator.h"
73 #include "nsPrintfCString.h"
74 #include "nsProxyRelease.h"
75 #include "nsQueryObject.h"
76 #include "nsRFPService.h"
77 #include "nsSandboxFlags.h"
78 #include "nsUTF8Utils.h"
79
80 #include "RuntimeService.h"
81 #include "ScriptLoader.h"
82 #include "mozilla/dom/ServiceWorkerEvents.h"
83 #include "mozilla/dom/ServiceWorkerManager.h"
84 #include "mozilla/net/CookieJarSettings.h"
85 #include "WorkerCSPEventListener.h"
86 #include "WorkerDebugger.h"
87 #include "WorkerDebuggerManager.h"
88 #include "WorkerError.h"
89 #include "WorkerEventTarget.h"
90 #include "WorkerNavigator.h"
91 #include "WorkerRef.h"
92 #include "WorkerRunnable.h"
93 #include "WorkerThread.h"
94
95 #include "nsThreadManager.h"
96
97 #ifdef XP_WIN
98 # undef PostMessage
99 #endif
100
101 // JS_MaybeGC will run once every second during normal execution.
102 #define PERIODIC_GC_TIMER_DELAY_SEC 1
103
104 // A shrinking GC will run five seconds after the last event is processed.
105 #define IDLE_GC_TIMER_DELAY_SEC 5
106
107 static mozilla::LazyLogModule sWorkerPrivateLog("WorkerPrivate");
108 static mozilla::LazyLogModule sWorkerTimeoutsLog("WorkerTimeouts");
109
WorkerLog()110 mozilla::LogModule* WorkerLog() { return sWorkerPrivateLog; }
111
TimeoutsLog()112 mozilla::LogModule* TimeoutsLog() { return sWorkerTimeoutsLog; }
113
114 #ifdef LOG
115 # undef LOG
116 #endif
117 #define LOG(log, _args) MOZ_LOG(log, LogLevel::Debug, _args);
118
119 namespace mozilla {
120
121 using namespace ipc;
122
123 namespace dom {
124
125 using namespace workerinternals;
126
127 MOZ_DEFINE_MALLOC_SIZE_OF(JsWorkerMallocSizeOf)
128
129 namespace {
130
131 #ifdef DEBUG
132
133 const nsIID kDEBUGWorkerEventTargetIID = {
134 0xccaba3fa,
135 0x5be2,
136 0x4de2,
137 {0xba, 0x87, 0x3b, 0x3b, 0x5b, 0x1d, 0x5, 0xfb}};
138
139 #endif
140
141 // The number of nested timeouts before we start clamping. HTML says 5.
142 const uint32_t kClampTimeoutNestingLevel = 5u;
143
144 template <class T>
145 class UniquePtrComparator {
146 typedef UniquePtr<T> A;
147 typedef T* B;
148
149 public:
Equals(const A & a,const A & b) const150 bool Equals(const A& a, const A& b) const {
151 return a && b ? *a == *b : !a && !b ? true : false;
152 }
LessThan(const A & a,const A & b) const153 bool LessThan(const A& a, const A& b) const {
154 return a && b ? *a < *b : b ? true : false;
155 }
156 };
157
158 template <class T>
GetUniquePtrComparator(const nsTArray<UniquePtr<T>> &)159 inline UniquePtrComparator<T> GetUniquePtrComparator(
160 const nsTArray<UniquePtr<T>>&) {
161 return UniquePtrComparator<T>();
162 }
163
164 // This class is used to wrap any runnables that the worker receives via the
165 // nsIEventTarget::Dispatch() method (either from NS_DispatchToCurrentThread or
166 // from the worker's EventTarget).
167 class ExternalRunnableWrapper final : public WorkerRunnable {
168 nsCOMPtr<nsIRunnable> mWrappedRunnable;
169
170 public:
ExternalRunnableWrapper(WorkerPrivate * aWorkerPrivate,nsIRunnable * aWrappedRunnable)171 ExternalRunnableWrapper(WorkerPrivate* aWorkerPrivate,
172 nsIRunnable* aWrappedRunnable)
173 : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
174 mWrappedRunnable(aWrappedRunnable) {
175 MOZ_ASSERT(aWorkerPrivate);
176 MOZ_ASSERT(aWrappedRunnable);
177 }
178
179 NS_INLINE_DECL_REFCOUNTING_INHERITED(ExternalRunnableWrapper, WorkerRunnable)
180
181 private:
182 ~ExternalRunnableWrapper() = default;
183
PreDispatch(WorkerPrivate * aWorkerPrivate)184 virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
185 // Silence bad assertions.
186 return true;
187 }
188
PostDispatch(WorkerPrivate * aWorkerPrivate,bool aDispatchResult)189 virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
190 bool aDispatchResult) override {
191 // Silence bad assertions.
192 }
193
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)194 virtual bool WorkerRun(JSContext* aCx,
195 WorkerPrivate* aWorkerPrivate) override {
196 nsresult rv = mWrappedRunnable->Run();
197 if (NS_FAILED(rv)) {
198 if (!JS_IsExceptionPending(aCx)) {
199 Throw(aCx, rv);
200 }
201 return false;
202 }
203 return true;
204 }
205
Cancel()206 nsresult Cancel() override {
207 nsCOMPtr<nsIDiscardableRunnable> doomed =
208 do_QueryInterface(mWrappedRunnable);
209 MOZ_ASSERT(doomed); // We checked this earlier!
210 doomed->OnDiscard();
211 return WorkerRunnable::Cancel();
212 }
213 };
214
215 struct WindowAction {
216 nsPIDOMWindowInner* mWindow;
217 bool mDefaultAction;
218
WindowActionmozilla::dom::__anon9fd1c7250111::WindowAction219 MOZ_IMPLICIT WindowAction(nsPIDOMWindowInner* aWindow)
220 : mWindow(aWindow), mDefaultAction(true) {}
221
operator ==mozilla::dom::__anon9fd1c7250111::WindowAction222 bool operator==(const WindowAction& aOther) const {
223 return mWindow == aOther.mWindow;
224 }
225 };
226
227 class WorkerFinishedRunnable final : public WorkerControlRunnable {
228 WorkerPrivate* mFinishedWorker;
229
230 public:
WorkerFinishedRunnable(WorkerPrivate * aWorkerPrivate,WorkerPrivate * aFinishedWorker)231 WorkerFinishedRunnable(WorkerPrivate* aWorkerPrivate,
232 WorkerPrivate* aFinishedWorker)
233 : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
234 mFinishedWorker(aFinishedWorker) {}
235
236 private:
PreDispatch(WorkerPrivate * aWorkerPrivate)237 virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
238 // Silence bad assertions.
239 return true;
240 }
241
PostDispatch(WorkerPrivate * aWorkerPrivate,bool aDispatchResult)242 virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
243 bool aDispatchResult) override {
244 // Silence bad assertions.
245 }
246
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)247 virtual bool WorkerRun(JSContext* aCx,
248 WorkerPrivate* aWorkerPrivate) override {
249 // This may block on the main thread.
250 AutoYieldJSThreadExecution yield;
251
252 if (!mFinishedWorker->ProxyReleaseMainThreadObjects()) {
253 NS_WARNING("Failed to dispatch, going to leak!");
254 }
255
256 RuntimeService* runtime = RuntimeService::GetService();
257 NS_ASSERTION(runtime, "This should never be null!");
258
259 mFinishedWorker->DisableDebugger();
260
261 runtime->UnregisterWorker(*mFinishedWorker);
262
263 mFinishedWorker->ClearSelfAndParentEventTargetRef();
264 return true;
265 }
266 };
267
268 class TopLevelWorkerFinishedRunnable final : public Runnable {
269 WorkerPrivate* mFinishedWorker;
270
271 public:
TopLevelWorkerFinishedRunnable(WorkerPrivate * aFinishedWorker)272 explicit TopLevelWorkerFinishedRunnable(WorkerPrivate* aFinishedWorker)
273 : mozilla::Runnable("TopLevelWorkerFinishedRunnable"),
274 mFinishedWorker(aFinishedWorker) {
275 aFinishedWorker->AssertIsOnWorkerThread();
276 }
277
278 NS_INLINE_DECL_REFCOUNTING_INHERITED(TopLevelWorkerFinishedRunnable, Runnable)
279
280 private:
281 ~TopLevelWorkerFinishedRunnable() = default;
282
283 NS_IMETHOD
Run()284 Run() override {
285 AssertIsOnMainThread();
286
287 RuntimeService* runtime = RuntimeService::GetService();
288 MOZ_ASSERT(runtime);
289
290 mFinishedWorker->DisableDebugger();
291
292 runtime->UnregisterWorker(*mFinishedWorker);
293
294 if (!mFinishedWorker->ProxyReleaseMainThreadObjects()) {
295 NS_WARNING("Failed to dispatch, going to leak!");
296 }
297
298 mFinishedWorker->ClearSelfAndParentEventTargetRef();
299 return NS_OK;
300 }
301 };
302
303 class ModifyBusyCountRunnable final : public WorkerControlRunnable {
304 bool mIncrease;
305
306 public:
ModifyBusyCountRunnable(WorkerPrivate * aWorkerPrivate,bool aIncrease)307 ModifyBusyCountRunnable(WorkerPrivate* aWorkerPrivate, bool aIncrease)
308 : WorkerControlRunnable(aWorkerPrivate, ParentThreadUnchangedBusyCount),
309 mIncrease(aIncrease) {}
310
311 private:
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)312 virtual bool WorkerRun(JSContext* aCx,
313 WorkerPrivate* aWorkerPrivate) override {
314 return aWorkerPrivate->ModifyBusyCount(mIncrease);
315 }
316
PostRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate,bool aRunResult)317 virtual void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
318 bool aRunResult) override {
319 if (mIncrease) {
320 WorkerControlRunnable::PostRun(aCx, aWorkerPrivate, aRunResult);
321 return;
322 }
323 // Don't do anything here as it's possible that aWorkerPrivate has been
324 // deleted.
325 }
326 };
327
328 class CompileScriptRunnable final : public WorkerDebuggeeRunnable {
329 nsString mScriptURL;
330 UniquePtr<SerializedStackHolder> mOriginStack;
331
332 public:
CompileScriptRunnable(WorkerPrivate * aWorkerPrivate,UniquePtr<SerializedStackHolder> aOriginStack,const nsAString & aScriptURL)333 explicit CompileScriptRunnable(WorkerPrivate* aWorkerPrivate,
334 UniquePtr<SerializedStackHolder> aOriginStack,
335 const nsAString& aScriptURL)
336 : WorkerDebuggeeRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount),
337 mScriptURL(aScriptURL),
338 mOriginStack(aOriginStack.release()) {}
339
340 private:
341 // We can't implement PreRun effectively, because at the point when that would
342 // run we have not yet done our load so don't know things like our final
343 // principal and whatnot.
344
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)345 virtual bool WorkerRun(JSContext* aCx,
346 WorkerPrivate* aWorkerPrivate) override {
347 aWorkerPrivate->AssertIsOnWorkerThread();
348
349 WorkerGlobalScope* globalScope =
350 aWorkerPrivate->GetOrCreateGlobalScope(aCx);
351 if (NS_WARN_IF(!globalScope)) {
352 return false;
353 }
354
355 if (NS_WARN_IF(!aWorkerPrivate->EnsureCSPEventListener())) {
356 return false;
357 }
358
359 ErrorResult rv;
360 workerinternals::LoadMainScript(aWorkerPrivate, std::move(mOriginStack),
361 mScriptURL, WorkerScript, rv);
362 rv.WouldReportJSException();
363 // Explicitly ignore NS_BINDING_ABORTED on rv. Or more precisely, still
364 // return false and don't SetWorkerScriptExecutedSuccessfully() in that
365 // case, but don't throw anything on aCx. The idea is to not dispatch error
366 // events if our load is canceled with that error code.
367 if (rv.ErrorCodeIs(NS_BINDING_ABORTED)) {
368 rv.SuppressException();
369 return false;
370 }
371
372 // Make sure to propagate exceptions from rv onto aCx, so that they will get
373 // reported after we return. We want to propagate just JS exceptions,
374 // because all the other errors are handled when the script is loaded.
375 // See: https://dom.spec.whatwg.org/#concept-event-fire
376 if (rv.Failed() && !rv.IsJSException()) {
377 WorkerErrorReport::CreateAndDispatchGenericErrorRunnableToParent(
378 aWorkerPrivate);
379 rv.SuppressException();
380 return false;
381 }
382
383 // This is a little dumb, but aCx is in the null realm here because we
384 // set it up that way in our Run(), since we had not created the global at
385 // that point yet. So we need to enter the realm of our global,
386 // because setting a pending exception on aCx involves wrapping into its
387 // current compartment. Luckily we have a global now.
388 JSAutoRealm ar(aCx, globalScope->GetGlobalJSObject());
389 if (rv.MaybeSetPendingException(aCx)) {
390 // In the event of an uncaught exception, the worker should still keep
391 // running (return true) but should not be marked as having executed
392 // successfully (which will cause ServiceWorker installation to fail).
393 // In previous error handling cases in this method, we return false (to
394 // trigger CloseInternal) because the global is not in an operable
395 // state at all.
396 //
397 // For ServiceWorkers, this would correspond to the "Run Service Worker"
398 // algorithm returning an "abrupt completion" and _not_ failure.
399 //
400 // For DedicatedWorkers and SharedWorkers, this would correspond to the
401 // "run a worker" algorithm disregarding the return value of "run the
402 // classic script"/"run the module script" in step 24:
403 //
404 // "If script is a classic script, then run the classic script script.
405 // Otherwise, it is a module script; run the module script script."
406 return true;
407 }
408
409 aWorkerPrivate->SetWorkerScriptExecutedSuccessfully();
410 return true;
411 }
412
PostRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate,bool aRunResult)413 void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
414 bool aRunResult) override {
415 if (!aRunResult) {
416 aWorkerPrivate->CloseInternal();
417 }
418 WorkerRunnable::PostRun(aCx, aWorkerPrivate, aRunResult);
419 }
420 };
421
422 class NotifyRunnable final : public WorkerControlRunnable {
423 WorkerStatus mStatus;
424
425 public:
NotifyRunnable(WorkerPrivate * aWorkerPrivate,WorkerStatus aStatus)426 NotifyRunnable(WorkerPrivate* aWorkerPrivate, WorkerStatus aStatus)
427 : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
428 mStatus(aStatus) {
429 MOZ_ASSERT(aStatus == Closing || aStatus == Canceling ||
430 aStatus == Killing);
431 }
432
433 private:
PreDispatch(WorkerPrivate * aWorkerPrivate)434 virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
435 aWorkerPrivate->AssertIsOnParentThread();
436 return aWorkerPrivate->ModifyBusyCount(true);
437 }
438
PostDispatch(WorkerPrivate * aWorkerPrivate,bool aDispatchResult)439 virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
440 bool aDispatchResult) override {
441 aWorkerPrivate->AssertIsOnParentThread();
442 if (!aDispatchResult) {
443 // We couldn't dispatch to the worker, which means it's already dead.
444 // Undo the busy count modification.
445 aWorkerPrivate->ModifyBusyCount(false);
446 }
447 }
448
PostRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate,bool aRunResult)449 virtual void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
450 bool aRunResult) override {
451 aWorkerPrivate->ModifyBusyCountFromWorker(false);
452 }
453
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)454 virtual bool WorkerRun(JSContext* aCx,
455 WorkerPrivate* aWorkerPrivate) override {
456 return aWorkerPrivate->NotifyInternal(mStatus);
457 }
458 };
459
460 class FreezeRunnable final : public WorkerControlRunnable {
461 public:
FreezeRunnable(WorkerPrivate * aWorkerPrivate)462 explicit FreezeRunnable(WorkerPrivate* aWorkerPrivate)
463 : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) {}
464
465 private:
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)466 virtual bool WorkerRun(JSContext* aCx,
467 WorkerPrivate* aWorkerPrivate) override {
468 return aWorkerPrivate->FreezeInternal();
469 }
470 };
471
472 class ThawRunnable final : public WorkerControlRunnable {
473 public:
ThawRunnable(WorkerPrivate * aWorkerPrivate)474 explicit ThawRunnable(WorkerPrivate* aWorkerPrivate)
475 : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) {}
476
477 private:
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)478 virtual bool WorkerRun(JSContext* aCx,
479 WorkerPrivate* aWorkerPrivate) override {
480 return aWorkerPrivate->ThawInternal();
481 }
482 };
483
484 class PropagateStorageAccessPermissionGrantedRunnable final
485 : public WorkerControlRunnable {
486 public:
PropagateStorageAccessPermissionGrantedRunnable(WorkerPrivate * aWorkerPrivate)487 explicit PropagateStorageAccessPermissionGrantedRunnable(
488 WorkerPrivate* aWorkerPrivate)
489 : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) {}
490
491 private:
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)492 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
493 aWorkerPrivate->PropagateStorageAccessPermissionGrantedInternal();
494 return true;
495 }
496 };
497
498 class ReportErrorToConsoleRunnable final : public WorkerRunnable {
499 const char* mMessage;
500 const nsTArray<nsString> mParams;
501
502 public:
503 // aWorkerPrivate is the worker thread we're on (or the main thread, if null)
Report(WorkerPrivate * aWorkerPrivate,const char * aMessage,const nsTArray<nsString> & aParams)504 static void Report(WorkerPrivate* aWorkerPrivate, const char* aMessage,
505 const nsTArray<nsString>& aParams) {
506 if (aWorkerPrivate) {
507 aWorkerPrivate->AssertIsOnWorkerThread();
508 } else {
509 AssertIsOnMainThread();
510 }
511
512 // Now fire a runnable to do the same on the parent's thread if we can.
513 if (aWorkerPrivate) {
514 RefPtr<ReportErrorToConsoleRunnable> runnable =
515 new ReportErrorToConsoleRunnable(aWorkerPrivate, aMessage, aParams);
516 runnable->Dispatch();
517 return;
518 }
519
520 // Log a warning to the console.
521 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
522 nullptr, nsContentUtils::eDOM_PROPERTIES,
523 aMessage, aParams);
524 }
525
526 private:
ReportErrorToConsoleRunnable(WorkerPrivate * aWorkerPrivate,const char * aMessage,const nsTArray<nsString> & aParams)527 ReportErrorToConsoleRunnable(WorkerPrivate* aWorkerPrivate,
528 const char* aMessage,
529 const nsTArray<nsString>& aParams)
530 : WorkerRunnable(aWorkerPrivate, ParentThreadUnchangedBusyCount),
531 mMessage(aMessage),
532 mParams(aParams.Clone()) {}
533
PostDispatch(WorkerPrivate * aWorkerPrivate,bool aDispatchResult)534 virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
535 bool aDispatchResult) override {
536 aWorkerPrivate->AssertIsOnWorkerThread();
537
538 // Dispatch may fail if the worker was canceled, no need to report that as
539 // an error, so don't call base class PostDispatch.
540 }
541
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)542 virtual bool WorkerRun(JSContext* aCx,
543 WorkerPrivate* aWorkerPrivate) override {
544 WorkerPrivate* parent = aWorkerPrivate->GetParent();
545 MOZ_ASSERT_IF(!parent, NS_IsMainThread());
546 Report(parent, mMessage, mParams);
547 return true;
548 }
549 };
550
551 class TimerRunnable final : public WorkerRunnable,
552 public nsITimerCallback,
553 public nsINamed {
554 public:
555 NS_DECL_ISUPPORTS_INHERITED
556
TimerRunnable(WorkerPrivate * aWorkerPrivate)557 explicit TimerRunnable(WorkerPrivate* aWorkerPrivate)
558 : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) {}
559
560 private:
561 ~TimerRunnable() = default;
562
PreDispatch(WorkerPrivate * aWorkerPrivate)563 virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
564 // Silence bad assertions.
565 return true;
566 }
567
PostDispatch(WorkerPrivate * aWorkerPrivate,bool aDispatchResult)568 virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
569 bool aDispatchResult) override {
570 // Silence bad assertions.
571 }
572
573 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until worker runnables are generally
574 // MOZ_CAN_RUN_SCRIPT.
575 MOZ_CAN_RUN_SCRIPT_BOUNDARY
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)576 virtual bool WorkerRun(JSContext* aCx,
577 WorkerPrivate* aWorkerPrivate) override {
578 return aWorkerPrivate->RunExpiredTimeouts(aCx);
579 }
580
581 NS_IMETHOD
Notify(nsITimer * aTimer)582 Notify(nsITimer* aTimer) override { return Run(); }
583
584 NS_IMETHOD
GetName(nsACString & aName)585 GetName(nsACString& aName) override {
586 aName.AssignLiteral("TimerRunnable");
587 return NS_OK;
588 }
589 };
590
591 NS_IMPL_ISUPPORTS_INHERITED(TimerRunnable, WorkerRunnable, nsITimerCallback,
592 nsINamed)
593
594 class DebuggerImmediateRunnable : public WorkerRunnable {
595 RefPtr<dom::Function> mHandler;
596
597 public:
DebuggerImmediateRunnable(WorkerPrivate * aWorkerPrivate,dom::Function & aHandler)598 explicit DebuggerImmediateRunnable(WorkerPrivate* aWorkerPrivate,
599 dom::Function& aHandler)
600 : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
601 mHandler(&aHandler) {}
602
603 private:
IsDebuggerRunnable() const604 virtual bool IsDebuggerRunnable() const override { return true; }
605
PreDispatch(WorkerPrivate * aWorkerPrivate)606 virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
607 // Silence bad assertions.
608 return true;
609 }
610
PostDispatch(WorkerPrivate * aWorkerPrivate,bool aDispatchResult)611 virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
612 bool aDispatchResult) override {
613 // Silence bad assertions.
614 }
615
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)616 virtual bool WorkerRun(JSContext* aCx,
617 WorkerPrivate* aWorkerPrivate) override {
618 JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
619 JS::Rooted<JS::Value> callable(
620 aCx, JS::ObjectOrNullValue(mHandler->CallableOrNull()));
621 JS::HandleValueArray args = JS::HandleValueArray::empty();
622 JS::Rooted<JS::Value> rval(aCx);
623 if (!JS_CallFunctionValue(aCx, global, callable, args, &rval)) {
624 // Just return false; WorkerRunnable::Run will report the exception.
625 return false;
626 }
627
628 return true;
629 }
630 };
631
PeriodicGCTimerCallback(nsITimer * aTimer,void * aClosure)632 void PeriodicGCTimerCallback(nsITimer* aTimer, void* aClosure) {
633 auto workerPrivate = static_cast<WorkerPrivate*>(aClosure);
634 MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
635 workerPrivate->AssertIsOnWorkerThread();
636 workerPrivate->GarbageCollectInternal(workerPrivate->GetJSContext(),
637 false /* shrinking */,
638 false /* collect children */);
639 }
640
IdleGCTimerCallback(nsITimer * aTimer,void * aClosure)641 void IdleGCTimerCallback(nsITimer* aTimer, void* aClosure) {
642 auto workerPrivate = static_cast<WorkerPrivate*>(aClosure);
643 MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
644 workerPrivate->AssertIsOnWorkerThread();
645 workerPrivate->GarbageCollectInternal(workerPrivate->GetJSContext(),
646 true /* shrinking */,
647 false /* collect children */);
648 }
649
650 class UpdateContextOptionsRunnable final : public WorkerControlRunnable {
651 JS::ContextOptions mContextOptions;
652
653 public:
UpdateContextOptionsRunnable(WorkerPrivate * aWorkerPrivate,const JS::ContextOptions & aContextOptions)654 UpdateContextOptionsRunnable(WorkerPrivate* aWorkerPrivate,
655 const JS::ContextOptions& aContextOptions)
656 : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
657 mContextOptions(aContextOptions) {}
658
659 private:
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)660 virtual bool WorkerRun(JSContext* aCx,
661 WorkerPrivate* aWorkerPrivate) override {
662 aWorkerPrivate->UpdateContextOptionsInternal(aCx, mContextOptions);
663 return true;
664 }
665 };
666
667 class UpdateLanguagesRunnable final : public WorkerRunnable {
668 nsTArray<nsString> mLanguages;
669
670 public:
UpdateLanguagesRunnable(WorkerPrivate * aWorkerPrivate,const nsTArray<nsString> & aLanguages)671 UpdateLanguagesRunnable(WorkerPrivate* aWorkerPrivate,
672 const nsTArray<nsString>& aLanguages)
673 : WorkerRunnable(aWorkerPrivate), mLanguages(aLanguages.Clone()) {}
674
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)675 virtual bool WorkerRun(JSContext* aCx,
676 WorkerPrivate* aWorkerPrivate) override {
677 aWorkerPrivate->UpdateLanguagesInternal(mLanguages);
678 return true;
679 }
680 };
681
682 class UpdateJSWorkerMemoryParameterRunnable final
683 : public WorkerControlRunnable {
684 Maybe<uint32_t> mValue;
685 JSGCParamKey mKey;
686
687 public:
UpdateJSWorkerMemoryParameterRunnable(WorkerPrivate * aWorkerPrivate,JSGCParamKey aKey,Maybe<uint32_t> aValue)688 UpdateJSWorkerMemoryParameterRunnable(WorkerPrivate* aWorkerPrivate,
689 JSGCParamKey aKey,
690 Maybe<uint32_t> aValue)
691 : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
692 mValue(aValue),
693 mKey(aKey) {}
694
695 private:
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)696 virtual bool WorkerRun(JSContext* aCx,
697 WorkerPrivate* aWorkerPrivate) override {
698 aWorkerPrivate->UpdateJSWorkerMemoryParameterInternal(aCx, mKey, mValue);
699 return true;
700 }
701 };
702
703 #ifdef JS_GC_ZEAL
704 class UpdateGCZealRunnable final : public WorkerControlRunnable {
705 uint8_t mGCZeal;
706 uint32_t mFrequency;
707
708 public:
UpdateGCZealRunnable(WorkerPrivate * aWorkerPrivate,uint8_t aGCZeal,uint32_t aFrequency)709 UpdateGCZealRunnable(WorkerPrivate* aWorkerPrivate, uint8_t aGCZeal,
710 uint32_t aFrequency)
711 : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
712 mGCZeal(aGCZeal),
713 mFrequency(aFrequency) {}
714
715 private:
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)716 virtual bool WorkerRun(JSContext* aCx,
717 WorkerPrivate* aWorkerPrivate) override {
718 aWorkerPrivate->UpdateGCZealInternal(aCx, mGCZeal, mFrequency);
719 return true;
720 }
721 };
722 #endif
723
724 class SetLowMemoryStateRunnable final : public WorkerControlRunnable {
725 bool mState;
726
727 public:
SetLowMemoryStateRunnable(WorkerPrivate * aWorkerPrivate,bool aState)728 SetLowMemoryStateRunnable(WorkerPrivate* aWorkerPrivate, bool aState)
729 : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
730 mState(aState) {}
731
732 private:
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)733 virtual bool WorkerRun(JSContext* aCx,
734 WorkerPrivate* aWorkerPrivate) override {
735 aWorkerPrivate->SetLowMemoryStateInternal(aCx, mState);
736 return true;
737 }
738 };
739
740 class GarbageCollectRunnable final : public WorkerControlRunnable {
741 bool mShrinking;
742 bool mCollectChildren;
743
744 public:
GarbageCollectRunnable(WorkerPrivate * aWorkerPrivate,bool aShrinking,bool aCollectChildren)745 GarbageCollectRunnable(WorkerPrivate* aWorkerPrivate, bool aShrinking,
746 bool aCollectChildren)
747 : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
748 mShrinking(aShrinking),
749 mCollectChildren(aCollectChildren) {}
750
751 private:
PreDispatch(WorkerPrivate * aWorkerPrivate)752 virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
753 // Silence bad assertions, this can be dispatched from either the main
754 // thread or the timer thread..
755 return true;
756 }
757
PostDispatch(WorkerPrivate * aWorkerPrivate,bool aDispatchResult)758 virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
759 bool aDispatchResult) override {
760 // Silence bad assertions, this can be dispatched from either the main
761 // thread or the timer thread..
762 }
763
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)764 virtual bool WorkerRun(JSContext* aCx,
765 WorkerPrivate* aWorkerPrivate) override {
766 aWorkerPrivate->GarbageCollectInternal(aCx, mShrinking, mCollectChildren);
767 return true;
768 }
769 };
770
771 class CycleCollectRunnable : public WorkerControlRunnable {
772 bool mCollectChildren;
773
774 public:
CycleCollectRunnable(WorkerPrivate * aWorkerPrivate,bool aCollectChildren)775 CycleCollectRunnable(WorkerPrivate* aWorkerPrivate, bool aCollectChildren)
776 : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
777 mCollectChildren(aCollectChildren) {}
778
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)779 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
780 aWorkerPrivate->CycleCollectInternal(mCollectChildren);
781 return true;
782 }
783 };
784
785 class OfflineStatusChangeRunnable : public WorkerRunnable {
786 public:
OfflineStatusChangeRunnable(WorkerPrivate * aWorkerPrivate,bool aIsOffline)787 OfflineStatusChangeRunnable(WorkerPrivate* aWorkerPrivate, bool aIsOffline)
788 : WorkerRunnable(aWorkerPrivate), mIsOffline(aIsOffline) {}
789
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)790 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
791 aWorkerPrivate->OfflineStatusChangeEventInternal(mIsOffline);
792 return true;
793 }
794
795 private:
796 bool mIsOffline;
797 };
798
799 class MemoryPressureRunnable : public WorkerControlRunnable {
800 public:
MemoryPressureRunnable(WorkerPrivate * aWorkerPrivate)801 explicit MemoryPressureRunnable(WorkerPrivate* aWorkerPrivate)
802 : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) {}
803
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)804 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
805 aWorkerPrivate->MemoryPressureInternal();
806 return true;
807 }
808 };
809
810 #ifdef DEBUG
StartsWithExplicit(nsACString & s)811 static bool StartsWithExplicit(nsACString& s) {
812 return StringBeginsWith(s, "explicit/"_ns);
813 }
814 #endif
815
PRThreadFromThread(nsIThread * aThread)816 PRThread* PRThreadFromThread(nsIThread* aThread) {
817 MOZ_ASSERT(aThread);
818
819 PRThread* result;
820 MOZ_ALWAYS_SUCCEEDS(aThread->GetPRThread(&result));
821 MOZ_ASSERT(result);
822
823 return result;
824 }
825
826 // A runnable to cancel the worker from the parent thread when self.close() is
827 // called. This runnable is executed on the parent process in order to cancel
828 // the current runnable. It uses a normal WorkerDebuggeeRunnable in order to be
829 // sure that all the pending WorkerDebuggeeRunnables are executed before this.
830 class CancelingOnParentRunnable final : public WorkerDebuggeeRunnable {
831 public:
CancelingOnParentRunnable(WorkerPrivate * aWorkerPrivate)832 explicit CancelingOnParentRunnable(WorkerPrivate* aWorkerPrivate)
833 : WorkerDebuggeeRunnable(aWorkerPrivate, ParentThreadUnchangedBusyCount) {
834 }
835
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)836 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
837 aWorkerPrivate->Cancel();
838 return true;
839 }
840 };
841
842 // A runnable to cancel the worker from the parent process.
843 class CancelingWithTimeoutOnParentRunnable final
844 : public WorkerControlRunnable {
845 public:
CancelingWithTimeoutOnParentRunnable(WorkerPrivate * aWorkerPrivate)846 explicit CancelingWithTimeoutOnParentRunnable(WorkerPrivate* aWorkerPrivate)
847 : WorkerControlRunnable(aWorkerPrivate, ParentThreadUnchangedBusyCount) {}
848
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)849 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
850 aWorkerPrivate->AssertIsOnParentThread();
851 aWorkerPrivate->StartCancelingTimer();
852 return true;
853 }
854 };
855
856 class CancelingTimerCallback final : public nsITimerCallback {
857 public:
858 NS_DECL_ISUPPORTS
859
CancelingTimerCallback(WorkerPrivate * aWorkerPrivate)860 explicit CancelingTimerCallback(WorkerPrivate* aWorkerPrivate)
861 : mWorkerPrivate(aWorkerPrivate) {}
862
863 NS_IMETHOD
Notify(nsITimer * aTimer)864 Notify(nsITimer* aTimer) override {
865 mWorkerPrivate->AssertIsOnParentThread();
866 mWorkerPrivate->Cancel();
867 return NS_OK;
868 }
869
870 private:
871 ~CancelingTimerCallback() = default;
872
873 // Raw pointer here is OK because the timer is canceled during the shutdown
874 // steps.
875 WorkerPrivate* mWorkerPrivate;
876 };
877
878 NS_IMPL_ISUPPORTS(CancelingTimerCallback, nsITimerCallback)
879
880 // This runnable starts the canceling of a worker after a self.close().
881 class CancelingRunnable final : public Runnable {
882 public:
CancelingRunnable()883 CancelingRunnable() : Runnable("CancelingRunnable") {}
884
885 NS_IMETHOD
Run()886 Run() override {
887 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
888 MOZ_ASSERT(workerPrivate);
889 workerPrivate->AssertIsOnWorkerThread();
890
891 // Now we can cancel the this worker from the parent process.
892 RefPtr<CancelingOnParentRunnable> r =
893 new CancelingOnParentRunnable(workerPrivate);
894 r->Dispatch();
895
896 return NS_OK;
897 }
898 };
899
900 } /* anonymous namespace */
901
ComputeWorkerPrivateId()902 nsString ComputeWorkerPrivateId() {
903 nsresult rv;
904 nsCOMPtr<nsIUUIDGenerator> uuidGenerator =
905 do_GetService("@mozilla.org/uuid-generator;1", &rv);
906 MOZ_ASSERT(NS_SUCCEEDED(rv));
907
908 nsID uuid;
909 rv = uuidGenerator->GenerateUUIDInPlace(&uuid);
910 MOZ_ASSERT(NS_SUCCEEDED(rv));
911 char buffer[NSID_LENGTH];
912 uuid.ToProvidedString(buffer);
913
914 nsString id;
915 // Remove {} and the null terminator
916 id.AssignASCII(&buffer[1], NSID_LENGTH - 3);
917
918 return id;
919 }
920
921 class WorkerPrivate::EventTarget final : public nsISerialEventTarget {
922 // This mutex protects mWorkerPrivate and must be acquired *before* the
923 // WorkerPrivate's mutex whenever they must both be held.
924 mozilla::Mutex mMutex;
925 WorkerPrivate* mWorkerPrivate;
926 nsIEventTarget* mWeakNestedEventTarget;
927 nsCOMPtr<nsIEventTarget> mNestedEventTarget;
928
929 public:
EventTarget(WorkerPrivate * aWorkerPrivate)930 explicit EventTarget(WorkerPrivate* aWorkerPrivate)
931 : mMutex("WorkerPrivate::EventTarget::mMutex"),
932 mWorkerPrivate(aWorkerPrivate),
933 mWeakNestedEventTarget(nullptr) {
934 MOZ_ASSERT(aWorkerPrivate);
935 }
936
EventTarget(WorkerPrivate * aWorkerPrivate,nsIEventTarget * aNestedEventTarget)937 EventTarget(WorkerPrivate* aWorkerPrivate, nsIEventTarget* aNestedEventTarget)
938 : mMutex("WorkerPrivate::EventTarget::mMutex"),
939 mWorkerPrivate(aWorkerPrivate),
940 mWeakNestedEventTarget(aNestedEventTarget),
941 mNestedEventTarget(aNestedEventTarget) {
942 MOZ_ASSERT(aWorkerPrivate);
943 MOZ_ASSERT(aNestedEventTarget);
944 }
945
Disable()946 void Disable() {
947 nsCOMPtr<nsIEventTarget> nestedEventTarget;
948 {
949 MutexAutoLock lock(mMutex);
950
951 // Note, Disable() can be called more than once safely.
952 mWorkerPrivate = nullptr;
953 mNestedEventTarget.swap(nestedEventTarget);
954 }
955 }
956
GetWeakNestedEventTarget() const957 nsIEventTarget* GetWeakNestedEventTarget() const {
958 MOZ_ASSERT(mWeakNestedEventTarget);
959 return mWeakNestedEventTarget;
960 }
961
962 NS_DECL_THREADSAFE_ISUPPORTS
963 NS_DECL_NSIEVENTTARGET_FULL
964
965 private:
966 ~EventTarget() = default;
967 };
968
969 struct WorkerPrivate::TimeoutInfo {
TimeoutInfomozilla::dom::WorkerPrivate::TimeoutInfo970 TimeoutInfo()
971 : mId(0),
972 mNestingLevel(0),
973 mIsInterval(false),
974 mCanceled(false),
975 mOnChromeWorker(false) {
976 MOZ_COUNT_CTOR(mozilla::dom::WorkerPrivate::TimeoutInfo);
977 }
978
~TimeoutInfomozilla::dom::WorkerPrivate::TimeoutInfo979 ~TimeoutInfo() { MOZ_COUNT_DTOR(mozilla::dom::WorkerPrivate::TimeoutInfo); }
980
operator ==mozilla::dom::WorkerPrivate::TimeoutInfo981 bool operator==(const TimeoutInfo& aOther) {
982 return mTargetTime == aOther.mTargetTime;
983 }
984
operator <mozilla::dom::WorkerPrivate::TimeoutInfo985 bool operator<(const TimeoutInfo& aOther) {
986 return mTargetTime < aOther.mTargetTime;
987 }
988
AccumulateNestingLevelmozilla::dom::WorkerPrivate::TimeoutInfo989 void AccumulateNestingLevel(const uint32_t& aBaseLevel) {
990 if (aBaseLevel < kClampTimeoutNestingLevel) {
991 mNestingLevel = aBaseLevel + 1;
992 return;
993 }
994 mNestingLevel = kClampTimeoutNestingLevel;
995 }
996
CalculateTargetTimemozilla::dom::WorkerPrivate::TimeoutInfo997 void CalculateTargetTime() {
998 auto target = mInterval;
999 // Don't clamp timeout for chrome workers
1000 if (mNestingLevel >= kClampTimeoutNestingLevel && !mOnChromeWorker) {
1001 target = TimeDuration::Max(
1002 mInterval,
1003 TimeDuration::FromMilliseconds(StaticPrefs::dom_min_timeout_value()));
1004 }
1005 mTargetTime = TimeStamp::Now() + target;
1006 }
1007
1008 RefPtr<TimeoutHandler> mHandler;
1009 mozilla::TimeStamp mTargetTime;
1010 mozilla::TimeDuration mInterval;
1011 int32_t mId;
1012 uint32_t mNestingLevel;
1013 bool mIsInterval;
1014 bool mCanceled;
1015 bool mOnChromeWorker;
1016 };
1017
1018 class WorkerJSContextStats final : public JS::RuntimeStats {
1019 const nsCString mRtPath;
1020
1021 public:
WorkerJSContextStats(const nsACString & aRtPath)1022 explicit WorkerJSContextStats(const nsACString& aRtPath)
1023 : JS::RuntimeStats(JsWorkerMallocSizeOf), mRtPath(aRtPath) {}
1024
~WorkerJSContextStats()1025 ~WorkerJSContextStats() {
1026 for (size_t i = 0; i != zoneStatsVector.length(); i++) {
1027 delete static_cast<xpc::ZoneStatsExtras*>(zoneStatsVector[i].extra);
1028 }
1029
1030 for (size_t i = 0; i != realmStatsVector.length(); i++) {
1031 delete static_cast<xpc::RealmStatsExtras*>(realmStatsVector[i].extra);
1032 }
1033 }
1034
Path() const1035 const nsCString& Path() const { return mRtPath; }
1036
initExtraZoneStats(JS::Zone * aZone,JS::ZoneStats * aZoneStats,const JS::AutoRequireNoGC & nogc)1037 virtual void initExtraZoneStats(JS::Zone* aZone, JS::ZoneStats* aZoneStats,
1038 const JS::AutoRequireNoGC& nogc) override {
1039 MOZ_ASSERT(!aZoneStats->extra);
1040
1041 // ReportJSRuntimeExplicitTreeStats expects that
1042 // aZoneStats->extra is a xpc::ZoneStatsExtras pointer.
1043 xpc::ZoneStatsExtras* extras = new xpc::ZoneStatsExtras;
1044 extras->pathPrefix = mRtPath;
1045 extras->pathPrefix += nsPrintfCString("zone(0x%p)/", (void*)aZone);
1046
1047 MOZ_ASSERT(StartsWithExplicit(extras->pathPrefix));
1048
1049 aZoneStats->extra = extras;
1050 }
1051
initExtraRealmStats(JS::Realm * aRealm,JS::RealmStats * aRealmStats,const JS::AutoRequireNoGC & nogc)1052 virtual void initExtraRealmStats(JS::Realm* aRealm,
1053 JS::RealmStats* aRealmStats,
1054 const JS::AutoRequireNoGC& nogc) override {
1055 MOZ_ASSERT(!aRealmStats->extra);
1056
1057 // ReportJSRuntimeExplicitTreeStats expects that
1058 // aRealmStats->extra is a xpc::RealmStatsExtras pointer.
1059 xpc::RealmStatsExtras* extras = new xpc::RealmStatsExtras;
1060
1061 // This is the |jsPathPrefix|. Each worker has exactly one realm.
1062 extras->jsPathPrefix.Assign(mRtPath);
1063 extras->jsPathPrefix +=
1064 nsPrintfCString("zone(0x%p)/", (void*)js::GetRealmZone(aRealm));
1065 extras->jsPathPrefix += "realm(web-worker)/"_ns;
1066
1067 // This should never be used when reporting with workers (hence the "?!").
1068 extras->domPathPrefix.AssignLiteral("explicit/workers/?!/");
1069
1070 MOZ_ASSERT(StartsWithExplicit(extras->jsPathPrefix));
1071 MOZ_ASSERT(StartsWithExplicit(extras->domPathPrefix));
1072
1073 extras->location = nullptr;
1074
1075 aRealmStats->extra = extras;
1076 }
1077 };
1078
1079 class WorkerPrivate::MemoryReporter final : public nsIMemoryReporter {
1080 NS_DECL_THREADSAFE_ISUPPORTS
1081
1082 friend class WorkerPrivate;
1083
1084 SharedMutex mMutex;
1085 WorkerPrivate* mWorkerPrivate;
1086
1087 public:
MemoryReporter(WorkerPrivate * aWorkerPrivate)1088 explicit MemoryReporter(WorkerPrivate* aWorkerPrivate)
1089 : mMutex(aWorkerPrivate->mMutex), mWorkerPrivate(aWorkerPrivate) {
1090 aWorkerPrivate->AssertIsOnWorkerThread();
1091 }
1092
1093 NS_IMETHOD
1094 CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData,
1095 bool aAnonymize) override;
1096
1097 private:
1098 class FinishCollectRunnable;
1099
1100 class CollectReportsRunnable final : public MainThreadWorkerControlRunnable {
1101 RefPtr<FinishCollectRunnable> mFinishCollectRunnable;
1102 const bool mAnonymize;
1103
1104 public:
1105 CollectReportsRunnable(WorkerPrivate* aWorkerPrivate,
1106 nsIHandleReportCallback* aHandleReport,
1107 nsISupports* aHandlerData, bool aAnonymize,
1108 const nsACString& aPath);
1109
1110 private:
1111 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override;
1112
~CollectReportsRunnable()1113 ~CollectReportsRunnable() {
1114 if (NS_IsMainThread()) {
1115 mFinishCollectRunnable->Run();
1116 return;
1117 }
1118
1119 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
1120 MOZ_ASSERT(workerPrivate);
1121 MOZ_ALWAYS_SUCCEEDS(workerPrivate->DispatchToMainThreadForMessaging(
1122 mFinishCollectRunnable.forget()));
1123 }
1124 };
1125
1126 class FinishCollectRunnable final : public Runnable {
1127 nsCOMPtr<nsIHandleReportCallback> mHandleReport;
1128 nsCOMPtr<nsISupports> mHandlerData;
1129 size_t mPerformanceUserEntries;
1130 size_t mPerformanceResourceEntries;
1131 const bool mAnonymize;
1132 bool mSuccess;
1133
1134 public:
1135 WorkerJSContextStats mCxStats;
1136
1137 explicit FinishCollectRunnable(nsIHandleReportCallback* aHandleReport,
1138 nsISupports* aHandlerData, bool aAnonymize,
1139 const nsACString& aPath);
1140
1141 NS_IMETHOD Run() override;
1142
SetPerformanceSizes(size_t userEntries,size_t resourceEntries)1143 void SetPerformanceSizes(size_t userEntries, size_t resourceEntries) {
1144 mPerformanceUserEntries = userEntries;
1145 mPerformanceResourceEntries = resourceEntries;
1146 }
1147
SetSuccess(bool success)1148 void SetSuccess(bool success) { mSuccess = success; }
1149
1150 private:
~FinishCollectRunnable()1151 ~FinishCollectRunnable() {
1152 // mHandleReport and mHandlerData are released on the main thread.
1153 AssertIsOnMainThread();
1154 }
1155
1156 FinishCollectRunnable(const FinishCollectRunnable&) = delete;
1157 FinishCollectRunnable& operator=(const FinishCollectRunnable&) = delete;
1158 FinishCollectRunnable& operator=(const FinishCollectRunnable&&) = delete;
1159 };
1160
1161 ~MemoryReporter() = default;
1162
Disable()1163 void Disable() {
1164 // Called from WorkerPrivate::DisableMemoryReporter.
1165 mMutex.AssertCurrentThreadOwns();
1166
1167 NS_ASSERTION(mWorkerPrivate, "Disabled more than once!");
1168 mWorkerPrivate = nullptr;
1169 }
1170 };
1171
NS_IMPL_ISUPPORTS(WorkerPrivate::MemoryReporter,nsIMemoryReporter)1172 NS_IMPL_ISUPPORTS(WorkerPrivate::MemoryReporter, nsIMemoryReporter)
1173
1174 NS_IMETHODIMP
1175 WorkerPrivate::MemoryReporter::CollectReports(
1176 nsIHandleReportCallback* aHandleReport, nsISupports* aData,
1177 bool aAnonymize) {
1178 AssertIsOnMainThread();
1179
1180 RefPtr<CollectReportsRunnable> runnable;
1181
1182 {
1183 MutexAutoLock lock(mMutex);
1184
1185 if (!mWorkerPrivate) {
1186 // This will effectively report 0 memory.
1187 nsCOMPtr<nsIMemoryReporterManager> manager =
1188 do_GetService("@mozilla.org/memory-reporter-manager;1");
1189 if (manager) {
1190 manager->EndReport();
1191 }
1192 return NS_OK;
1193 }
1194
1195 nsAutoCString path;
1196 path.AppendLiteral("explicit/workers/workers(");
1197 if (aAnonymize && !mWorkerPrivate->Domain().IsEmpty()) {
1198 path.AppendLiteral("<anonymized-domain>)/worker(<anonymized-url>");
1199 } else {
1200 nsAutoCString escapedDomain(mWorkerPrivate->Domain());
1201 if (escapedDomain.IsEmpty()) {
1202 escapedDomain += "chrome";
1203 } else {
1204 escapedDomain.ReplaceChar('/', '\\');
1205 }
1206 path.Append(escapedDomain);
1207 path.AppendLiteral(")/worker(");
1208 NS_ConvertUTF16toUTF8 escapedURL(mWorkerPrivate->ScriptURL());
1209 escapedURL.ReplaceChar('/', '\\');
1210 path.Append(escapedURL);
1211 }
1212 path.AppendPrintf(", 0x%p)/", static_cast<void*>(mWorkerPrivate));
1213
1214 runnable = new CollectReportsRunnable(mWorkerPrivate, aHandleReport, aData,
1215 aAnonymize, path);
1216 }
1217
1218 if (!runnable->Dispatch()) {
1219 return NS_ERROR_UNEXPECTED;
1220 }
1221
1222 return NS_OK;
1223 }
1224
CollectReportsRunnable(WorkerPrivate * aWorkerPrivate,nsIHandleReportCallback * aHandleReport,nsISupports * aHandlerData,bool aAnonymize,const nsACString & aPath)1225 WorkerPrivate::MemoryReporter::CollectReportsRunnable::CollectReportsRunnable(
1226 WorkerPrivate* aWorkerPrivate, nsIHandleReportCallback* aHandleReport,
1227 nsISupports* aHandlerData, bool aAnonymize, const nsACString& aPath)
1228 : MainThreadWorkerControlRunnable(aWorkerPrivate),
1229 mFinishCollectRunnable(new FinishCollectRunnable(
1230 aHandleReport, aHandlerData, aAnonymize, aPath)),
1231 mAnonymize(aAnonymize) {}
1232
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)1233 bool WorkerPrivate::MemoryReporter::CollectReportsRunnable::WorkerRun(
1234 JSContext* aCx, WorkerPrivate* aWorkerPrivate) {
1235 aWorkerPrivate->AssertIsOnWorkerThread();
1236
1237 RefPtr<WorkerGlobalScope> scope = aWorkerPrivate->GlobalScope();
1238 RefPtr<Performance> performance =
1239 scope ? scope->GetPerformanceIfExists() : nullptr;
1240 if (performance) {
1241 size_t userEntries = performance->SizeOfUserEntries(JsWorkerMallocSizeOf);
1242 size_t resourceEntries =
1243 performance->SizeOfResourceEntries(JsWorkerMallocSizeOf);
1244 mFinishCollectRunnable->SetPerformanceSizes(userEntries, resourceEntries);
1245 }
1246
1247 mFinishCollectRunnable->SetSuccess(aWorkerPrivate->CollectRuntimeStats(
1248 &mFinishCollectRunnable->mCxStats, mAnonymize));
1249
1250 return true;
1251 }
1252
FinishCollectRunnable(nsIHandleReportCallback * aHandleReport,nsISupports * aHandlerData,bool aAnonymize,const nsACString & aPath)1253 WorkerPrivate::MemoryReporter::FinishCollectRunnable::FinishCollectRunnable(
1254 nsIHandleReportCallback* aHandleReport, nsISupports* aHandlerData,
1255 bool aAnonymize, const nsACString& aPath)
1256 : mozilla::Runnable(
1257 "dom::WorkerPrivate::MemoryReporter::FinishCollectRunnable"),
1258 mHandleReport(aHandleReport),
1259 mHandlerData(aHandlerData),
1260 mPerformanceUserEntries(0),
1261 mPerformanceResourceEntries(0),
1262 mAnonymize(aAnonymize),
1263 mSuccess(false),
1264 mCxStats(aPath) {}
1265
1266 NS_IMETHODIMP
Run()1267 WorkerPrivate::MemoryReporter::FinishCollectRunnable::Run() {
1268 AssertIsOnMainThread();
1269
1270 nsCOMPtr<nsIMemoryReporterManager> manager =
1271 do_GetService("@mozilla.org/memory-reporter-manager;1");
1272
1273 if (!manager) return NS_OK;
1274
1275 if (mSuccess) {
1276 xpc::ReportJSRuntimeExplicitTreeStats(
1277 mCxStats, mCxStats.Path(), mHandleReport, mHandlerData, mAnonymize);
1278
1279 if (mPerformanceUserEntries) {
1280 nsCString path = mCxStats.Path();
1281 path.AppendLiteral("dom/performance/user-entries");
1282 mHandleReport->Callback(
1283 ""_ns, path, nsIMemoryReporter::KIND_HEAP,
1284 nsIMemoryReporter::UNITS_BYTES, mPerformanceUserEntries,
1285 "Memory used for performance user entries."_ns, mHandlerData);
1286 }
1287
1288 if (mPerformanceResourceEntries) {
1289 nsCString path = mCxStats.Path();
1290 path.AppendLiteral("dom/performance/resource-entries");
1291 mHandleReport->Callback(
1292 ""_ns, path, nsIMemoryReporter::KIND_HEAP,
1293 nsIMemoryReporter::UNITS_BYTES, mPerformanceResourceEntries,
1294 "Memory used for performance resource entries."_ns, mHandlerData);
1295 }
1296 }
1297
1298 manager->EndReport();
1299
1300 return NS_OK;
1301 }
1302
SyncLoopInfo(EventTarget * aEventTarget)1303 WorkerPrivate::SyncLoopInfo::SyncLoopInfo(EventTarget* aEventTarget)
1304 : mEventTarget(aEventTarget),
1305 mCompleted(false),
1306 mResult(false)
1307 #ifdef DEBUG
1308 ,
1309 mHasRun(false)
1310 #endif
1311 {
1312 }
1313
GetDocument() const1314 Document* WorkerPrivate::GetDocument() const {
1315 AssertIsOnMainThread();
1316 if (mLoadInfo.mWindow) {
1317 return mLoadInfo.mWindow->GetExtantDoc();
1318 }
1319 // if we don't have a document, we should query the document
1320 // from the parent in case of a nested worker
1321 WorkerPrivate* parent = mParent;
1322 while (parent) {
1323 if (parent->mLoadInfo.mWindow) {
1324 return parent->mLoadInfo.mWindow->GetExtantDoc();
1325 }
1326 parent = parent->GetParent();
1327 }
1328 // couldn't query a document, give up and return nullptr
1329 return nullptr;
1330 }
1331
SetCSP(nsIContentSecurityPolicy * aCSP)1332 void WorkerPrivate::SetCSP(nsIContentSecurityPolicy* aCSP) {
1333 AssertIsOnMainThread();
1334 if (!aCSP) {
1335 return;
1336 }
1337 aCSP->EnsureEventTarget(mMainThreadEventTarget);
1338
1339 mLoadInfo.mCSP = aCSP;
1340 mLoadInfo.mCSPInfo = MakeUnique<CSPInfo>();
1341 nsresult rv = CSPToCSPInfo(mLoadInfo.mCSP, mLoadInfo.mCSPInfo.get());
1342 if (NS_WARN_IF(NS_FAILED(rv))) {
1343 return;
1344 }
1345 }
1346
SetCSPFromHeaderValues(const nsACString & aCSPHeaderValue,const nsACString & aCSPReportOnlyHeaderValue)1347 nsresult WorkerPrivate::SetCSPFromHeaderValues(
1348 const nsACString& aCSPHeaderValue,
1349 const nsACString& aCSPReportOnlyHeaderValue) {
1350 AssertIsOnMainThread();
1351 MOZ_DIAGNOSTIC_ASSERT(!mLoadInfo.mCSP);
1352
1353 NS_ConvertASCIItoUTF16 cspHeaderValue(aCSPHeaderValue);
1354 NS_ConvertASCIItoUTF16 cspROHeaderValue(aCSPReportOnlyHeaderValue);
1355
1356 nsresult rv;
1357 nsCOMPtr<nsIContentSecurityPolicy> csp = new nsCSPContext();
1358
1359 // First, we try to query the URI from the Principal, but
1360 // in case selfURI remains empty (e.g in case the Principal
1361 // is a SystemPrincipal) then we fall back and use the
1362 // base URI as selfURI for CSP.
1363 nsCOMPtr<nsIURI> selfURI;
1364 // Its not recommended to use the BasePrincipal to get the URI
1365 // but in this case we need to make an exception
1366 auto* basePrin = BasePrincipal::Cast(mLoadInfo.mPrincipal);
1367 if (basePrin) {
1368 basePrin->GetURI(getter_AddRefs(selfURI));
1369 }
1370 if (!selfURI) {
1371 selfURI = mLoadInfo.mBaseURI;
1372 }
1373 MOZ_ASSERT(selfURI, "need a self URI for CSP");
1374
1375 rv = csp->SetRequestContextWithPrincipal(mLoadInfo.mPrincipal, selfURI,
1376 u""_ns, 0);
1377 NS_ENSURE_SUCCESS(rv, rv);
1378
1379 csp->EnsureEventTarget(mMainThreadEventTarget);
1380
1381 // If there's a CSP header, apply it.
1382 if (!cspHeaderValue.IsEmpty()) {
1383 rv = CSP_AppendCSPFromHeader(csp, cspHeaderValue, false);
1384 NS_ENSURE_SUCCESS(rv, rv);
1385 }
1386 // If there's a report-only CSP header, apply it.
1387 if (!cspROHeaderValue.IsEmpty()) {
1388 rv = CSP_AppendCSPFromHeader(csp, cspROHeaderValue, true);
1389 NS_ENSURE_SUCCESS(rv, rv);
1390 }
1391
1392 // Set evalAllowed, default value is set in GetAllowsEval
1393 bool evalAllowed = false;
1394 bool reportEvalViolations = false;
1395 rv = csp->GetAllowsEval(&reportEvalViolations, &evalAllowed);
1396 NS_ENSURE_SUCCESS(rv, rv);
1397
1398 mLoadInfo.mCSP = csp;
1399 mLoadInfo.mEvalAllowed = evalAllowed;
1400 mLoadInfo.mReportCSPViolations = reportEvalViolations;
1401
1402 mLoadInfo.mCSPInfo = MakeUnique<CSPInfo>();
1403 rv = CSPToCSPInfo(csp, mLoadInfo.mCSPInfo.get());
1404 if (NS_WARN_IF(NS_FAILED(rv))) {
1405 return rv;
1406 }
1407 return NS_OK;
1408 }
1409
StoreCSPOnClient()1410 void WorkerPrivate::StoreCSPOnClient() {
1411 auto data = mWorkerThreadAccessible.Access();
1412 MOZ_ASSERT(data->mScope);
1413 if (mLoadInfo.mCSPInfo) {
1414 data->mScope->MutableClientSourceRef().SetCspInfo(*mLoadInfo.mCSPInfo);
1415 }
1416 }
1417
UpdateReferrerInfoFromHeader(const nsACString & aReferrerPolicyHeaderValue)1418 void WorkerPrivate::UpdateReferrerInfoFromHeader(
1419 const nsACString& aReferrerPolicyHeaderValue) {
1420 NS_ConvertUTF8toUTF16 headerValue(aReferrerPolicyHeaderValue);
1421
1422 if (headerValue.IsEmpty()) {
1423 return;
1424 }
1425
1426 ReferrerPolicy policy =
1427 ReferrerInfo::ReferrerPolicyFromHeaderString(headerValue);
1428 if (policy == ReferrerPolicy::_empty) {
1429 return;
1430 }
1431
1432 nsCOMPtr<nsIReferrerInfo> referrerInfo =
1433 static_cast<ReferrerInfo*>(GetReferrerInfo())->CloneWithNewPolicy(policy);
1434 SetReferrerInfo(referrerInfo);
1435 }
1436
Traverse(nsCycleCollectionTraversalCallback & aCb)1437 void WorkerPrivate::Traverse(nsCycleCollectionTraversalCallback& aCb) {
1438 AssertIsOnParentThread();
1439
1440 // The WorkerPrivate::mParentEventTargetRef has a reference to the exposed
1441 // Worker object, which is really held by the worker thread. We traverse this
1442 // reference if and only if our busy count is zero and we have not released
1443 // the main thread reference. We do not unlink it. This allows the CC to
1444 // break cycles involving the Worker and begin shutting it down (which does
1445 // happen in unlink) but ensures that the WorkerPrivate won't be deleted
1446 // before we're done shutting down the thread.
1447 if (!mBusyCount && !mMainThreadObjectsForgotten) {
1448 nsCycleCollectionTraversalCallback& cb = aCb;
1449 WorkerPrivate* tmp = this;
1450 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParentEventTargetRef);
1451 }
1452 }
1453
Dispatch(already_AddRefed<WorkerRunnable> aRunnable,nsIEventTarget * aSyncLoopTarget)1454 nsresult WorkerPrivate::Dispatch(already_AddRefed<WorkerRunnable> aRunnable,
1455 nsIEventTarget* aSyncLoopTarget) {
1456 // May be called on any thread!
1457 MutexAutoLock lock(mMutex);
1458 return DispatchLockHeld(std::move(aRunnable), aSyncLoopTarget, lock);
1459 }
1460
DispatchLockHeld(already_AddRefed<WorkerRunnable> aRunnable,nsIEventTarget * aSyncLoopTarget,const MutexAutoLock & aProofOfLock)1461 nsresult WorkerPrivate::DispatchLockHeld(
1462 already_AddRefed<WorkerRunnable> aRunnable, nsIEventTarget* aSyncLoopTarget,
1463 const MutexAutoLock& aProofOfLock) {
1464 // May be called on any thread!
1465 RefPtr<WorkerRunnable> runnable(aRunnable);
1466
1467 MOZ_ASSERT_IF(aSyncLoopTarget, mThread);
1468
1469 if (mStatus == Dead || (!aSyncLoopTarget && ParentStatus() > Running)) {
1470 NS_WARNING(
1471 "A runnable was posted to a worker that is already shutting "
1472 "down!");
1473 return NS_ERROR_UNEXPECTED;
1474 }
1475
1476 if (runnable->IsDebuggeeRunnable() && !mDebuggerReady) {
1477 MOZ_RELEASE_ASSERT(!aSyncLoopTarget);
1478 mDelayedDebuggeeRunnables.AppendElement(runnable);
1479 return NS_OK;
1480 }
1481
1482 if (!mThread) {
1483 if (ParentStatus() == Pending || mStatus == Pending) {
1484 mPreStartRunnables.AppendElement(runnable);
1485 return NS_OK;
1486 }
1487
1488 NS_WARNING(
1489 "Using a worker event target after the thread has already"
1490 "been released!");
1491 return NS_ERROR_UNEXPECTED;
1492 }
1493
1494 nsresult rv;
1495 if (aSyncLoopTarget) {
1496 rv = aSyncLoopTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
1497 } else {
1498 // WorkerDebuggeeRunnables don't need any special treatment here. True,
1499 // they should not be delivered to a frozen worker. But frozen workers
1500 // aren't drawing from the thread's main event queue anyway, only from
1501 // mControlQueue.
1502 rv = mThread->DispatchAnyThread(WorkerThreadFriendKey(), runnable.forget());
1503 }
1504
1505 if (NS_WARN_IF(NS_FAILED(rv))) {
1506 return rv;
1507 }
1508
1509 mCondVar.Notify();
1510 return NS_OK;
1511 }
1512
EnableDebugger()1513 void WorkerPrivate::EnableDebugger() {
1514 AssertIsOnParentThread();
1515
1516 if (NS_FAILED(RegisterWorkerDebugger(this))) {
1517 NS_WARNING("Failed to register worker debugger!");
1518 return;
1519 }
1520 }
1521
DisableDebugger()1522 void WorkerPrivate::DisableDebugger() {
1523 AssertIsOnParentThread();
1524
1525 // RegisterDebuggerMainThreadRunnable might be dispatched but not executed.
1526 // Wait for its execution before unregistraion.
1527 if (!NS_IsMainThread()) {
1528 WaitForIsDebuggerRegistered(true);
1529 }
1530
1531 if (NS_FAILED(UnregisterWorkerDebugger(this))) {
1532 NS_WARNING("Failed to unregister worker debugger!");
1533 }
1534 }
1535
DispatchControlRunnable(already_AddRefed<WorkerControlRunnable> aWorkerControlRunnable)1536 nsresult WorkerPrivate::DispatchControlRunnable(
1537 already_AddRefed<WorkerControlRunnable> aWorkerControlRunnable) {
1538 // May be called on any thread!
1539 RefPtr<WorkerControlRunnable> runnable(aWorkerControlRunnable);
1540 MOZ_ASSERT(runnable);
1541
1542 {
1543 MutexAutoLock lock(mMutex);
1544
1545 if (mStatus == Dead) {
1546 return NS_ERROR_UNEXPECTED;
1547 }
1548
1549 // Transfer ownership to the control queue.
1550 mControlQueue.Push(runnable.forget().take());
1551
1552 if (JSContext* cx = mJSContext) {
1553 MOZ_ASSERT(mThread);
1554 JS_RequestInterruptCallback(cx);
1555 }
1556
1557 mCondVar.Notify();
1558 }
1559
1560 return NS_OK;
1561 }
1562
DispatchDebuggerRunnable(already_AddRefed<WorkerRunnable> aDebuggerRunnable)1563 nsresult WorkerPrivate::DispatchDebuggerRunnable(
1564 already_AddRefed<WorkerRunnable> aDebuggerRunnable) {
1565 // May be called on any thread!
1566
1567 RefPtr<WorkerRunnable> runnable(aDebuggerRunnable);
1568
1569 MOZ_ASSERT(runnable);
1570
1571 {
1572 MutexAutoLock lock(mMutex);
1573
1574 if (mStatus == Dead) {
1575 NS_WARNING(
1576 "A debugger runnable was posted to a worker that is already "
1577 "shutting down!");
1578 return NS_ERROR_UNEXPECTED;
1579 }
1580
1581 // Transfer ownership to the debugger queue.
1582 mDebuggerQueue.Push(runnable.forget().take());
1583
1584 mCondVar.Notify();
1585 }
1586
1587 return NS_OK;
1588 }
1589
MaybeWrapAsWorkerRunnable(already_AddRefed<nsIRunnable> aRunnable)1590 already_AddRefed<WorkerRunnable> WorkerPrivate::MaybeWrapAsWorkerRunnable(
1591 already_AddRefed<nsIRunnable> aRunnable) {
1592 // May be called on any thread!
1593
1594 nsCOMPtr<nsIRunnable> runnable(aRunnable);
1595 MOZ_ASSERT(runnable);
1596
1597 RefPtr<WorkerRunnable> workerRunnable =
1598 WorkerRunnable::FromRunnable(runnable);
1599 if (workerRunnable) {
1600 return workerRunnable.forget();
1601 }
1602
1603 nsCOMPtr<nsIDiscardableRunnable> maybe = do_QueryInterface(runnable);
1604 if (!maybe) {
1605 MOZ_CRASH(
1606 "All runnables destined for a worker thread must be "
1607 "nsIDiscardableRunnable!");
1608 }
1609
1610 workerRunnable = new ExternalRunnableWrapper(this, runnable);
1611 return workerRunnable.forget();
1612 }
1613
Start()1614 bool WorkerPrivate::Start() {
1615 // May be called on any thread!
1616 {
1617 MutexAutoLock lock(mMutex);
1618 NS_ASSERTION(mParentStatus != Running, "How can this be?!");
1619
1620 if (mParentStatus == Pending) {
1621 mParentStatus = Running;
1622 return true;
1623 }
1624 }
1625
1626 return false;
1627 }
1628
1629 // aCx is null when called from the finalizer
Notify(WorkerStatus aStatus)1630 bool WorkerPrivate::Notify(WorkerStatus aStatus) {
1631 AssertIsOnParentThread();
1632
1633 bool pending;
1634 {
1635 MutexAutoLock lock(mMutex);
1636
1637 if (mParentStatus >= aStatus) {
1638 return true;
1639 }
1640
1641 pending = mParentStatus == Pending;
1642 mParentStatus = aStatus;
1643 }
1644
1645 if (pending) {
1646 #ifdef DEBUG
1647 {
1648 // Fake a thread here just so that our assertions don't go off for no
1649 // reason.
1650 nsIThread* currentThread = NS_GetCurrentThread();
1651 MOZ_ASSERT(currentThread);
1652
1653 MOZ_ASSERT(!mPRThread);
1654 mPRThread = PRThreadFromThread(currentThread);
1655 MOZ_ASSERT(mPRThread);
1656 }
1657 #endif
1658
1659 // Worker never got a chance to run, go ahead and delete it.
1660 ScheduleDeletion(WorkerPrivate::WorkerNeverRan);
1661 return true;
1662 }
1663
1664 // No Canceling timeout is needed.
1665 if (mCancelingTimer) {
1666 mCancelingTimer->Cancel();
1667 mCancelingTimer = nullptr;
1668 }
1669
1670 RefPtr<NotifyRunnable> runnable = new NotifyRunnable(this, aStatus);
1671 return runnable->Dispatch();
1672 }
1673
Freeze(const nsPIDOMWindowInner * aWindow)1674 bool WorkerPrivate::Freeze(const nsPIDOMWindowInner* aWindow) {
1675 AssertIsOnParentThread();
1676
1677 mParentFrozen = true;
1678
1679 // WorkerDebuggeeRunnables sent from a worker to content must not be delivered
1680 // while the worker is frozen.
1681 //
1682 // Since a top-level worker and all its children share the same
1683 // mMainThreadDebuggeeEventTarget, it's sufficient to do this only in the
1684 // top-level worker.
1685 if (aWindow) {
1686 // This is called from WorkerPrivate construction, and We may not have
1687 // allocated mMainThreadDebuggeeEventTarget yet.
1688 if (mMainThreadDebuggeeEventTarget) {
1689 // Pausing a ThrottledEventQueue is infallible.
1690 MOZ_ALWAYS_SUCCEEDS(mMainThreadDebuggeeEventTarget->SetIsPaused(true));
1691 }
1692 }
1693
1694 {
1695 MutexAutoLock lock(mMutex);
1696
1697 if (mParentStatus >= Canceling) {
1698 return true;
1699 }
1700 }
1701
1702 DisableDebugger();
1703
1704 RefPtr<FreezeRunnable> runnable = new FreezeRunnable(this);
1705 if (!runnable->Dispatch()) {
1706 return false;
1707 }
1708
1709 return true;
1710 }
1711
Thaw(const nsPIDOMWindowInner * aWindow)1712 bool WorkerPrivate::Thaw(const nsPIDOMWindowInner* aWindow) {
1713 AssertIsOnParentThread();
1714 MOZ_ASSERT(mParentFrozen);
1715
1716 mParentFrozen = false;
1717
1718 // Delivery of WorkerDebuggeeRunnables to the window may resume.
1719 //
1720 // Since a top-level worker and all its children share the same
1721 // mMainThreadDebuggeeEventTarget, it's sufficient to do this only in the
1722 // top-level worker.
1723 if (aWindow) {
1724 // Since the worker is no longer frozen, only a paused parent window should
1725 // require the queue to remain paused.
1726 //
1727 // This can only fail if the ThrottledEventQueue cannot dispatch its
1728 // executor to the main thread, in which case the main thread was never
1729 // going to draw runnables from it anyway, so the failure doesn't matter.
1730 Unused << mMainThreadDebuggeeEventTarget->SetIsPaused(
1731 IsParentWindowPaused());
1732 }
1733
1734 {
1735 MutexAutoLock lock(mMutex);
1736
1737 if (mParentStatus >= Canceling) {
1738 return true;
1739 }
1740 }
1741
1742 EnableDebugger();
1743
1744 RefPtr<ThawRunnable> runnable = new ThawRunnable(this);
1745 if (!runnable->Dispatch()) {
1746 return false;
1747 }
1748
1749 return true;
1750 }
1751
ParentWindowPaused()1752 void WorkerPrivate::ParentWindowPaused() {
1753 AssertIsOnMainThread();
1754 MOZ_ASSERT(!mParentWindowPaused);
1755 mParentWindowPaused = true;
1756
1757 // This is called from WorkerPrivate construction, and we may not have
1758 // allocated mMainThreadDebuggeeEventTarget yet.
1759 if (mMainThreadDebuggeeEventTarget) {
1760 // Pausing a ThrottledEventQueue is infallible.
1761 MOZ_ALWAYS_SUCCEEDS(mMainThreadDebuggeeEventTarget->SetIsPaused(true));
1762 }
1763 }
1764
ParentWindowResumed()1765 void WorkerPrivate::ParentWindowResumed() {
1766 AssertIsOnMainThread();
1767
1768 MOZ_ASSERT(mParentWindowPaused);
1769 mParentWindowPaused = false;
1770
1771 {
1772 MutexAutoLock lock(mMutex);
1773
1774 if (mParentStatus >= Canceling) {
1775 return;
1776 }
1777 }
1778
1779 // Since the window is no longer paused, the queue should only remain paused
1780 // if the worker is frozen.
1781 //
1782 // This can only fail if the ThrottledEventQueue cannot dispatch its executor
1783 // to the main thread, in which case the main thread was never going to draw
1784 // runnables from it anyway, so the failure doesn't matter.
1785 Unused << mMainThreadDebuggeeEventTarget->SetIsPaused(IsFrozen());
1786 }
1787
PropagateStorageAccessPermissionGranted()1788 void WorkerPrivate::PropagateStorageAccessPermissionGranted() {
1789 AssertIsOnParentThread();
1790
1791 {
1792 MutexAutoLock lock(mMutex);
1793
1794 if (mParentStatus >= Canceling) {
1795 return;
1796 }
1797 }
1798
1799 RefPtr<PropagateStorageAccessPermissionGrantedRunnable> runnable =
1800 new PropagateStorageAccessPermissionGrantedRunnable(this);
1801 Unused << NS_WARN_IF(!runnable->Dispatch());
1802 }
1803
Close()1804 bool WorkerPrivate::Close() {
1805 mMutex.AssertCurrentThreadOwns();
1806 if (mParentStatus < Closing) {
1807 mParentStatus = Closing;
1808 }
1809
1810 return true;
1811 }
1812
ModifyBusyCount(bool aIncrease)1813 bool WorkerPrivate::ModifyBusyCount(bool aIncrease) {
1814 AssertIsOnParentThread();
1815
1816 MOZ_ASSERT(aIncrease || mBusyCount, "Mismatched busy count mods!");
1817
1818 if (aIncrease) {
1819 mBusyCount++;
1820 return true;
1821 }
1822
1823 if (--mBusyCount == 0) {
1824 bool shouldCancel;
1825 {
1826 MutexAutoLock lock(mMutex);
1827 shouldCancel = mParentStatus == Canceling;
1828 }
1829
1830 if (shouldCancel && !Cancel()) {
1831 return false;
1832 }
1833 }
1834
1835 return true;
1836 }
1837
ProxyReleaseMainThreadObjects()1838 bool WorkerPrivate::ProxyReleaseMainThreadObjects() {
1839 AssertIsOnParentThread();
1840 MOZ_ASSERT(!mMainThreadObjectsForgotten);
1841
1842 nsCOMPtr<nsILoadGroup> loadGroupToCancel;
1843 // If we're not overriden, then do nothing here. Let the load group get
1844 // handled in ForgetMainThreadObjects().
1845 if (mLoadInfo.mInterfaceRequestor) {
1846 mLoadInfo.mLoadGroup.swap(loadGroupToCancel);
1847 }
1848
1849 bool result = mLoadInfo.ProxyReleaseMainThreadObjects(
1850 this, std::move(loadGroupToCancel));
1851
1852 mMainThreadObjectsForgotten = true;
1853
1854 return result;
1855 }
1856
UpdateContextOptions(const JS::ContextOptions & aContextOptions)1857 void WorkerPrivate::UpdateContextOptions(
1858 const JS::ContextOptions& aContextOptions) {
1859 AssertIsOnParentThread();
1860
1861 {
1862 MutexAutoLock lock(mMutex);
1863 mJSSettings.contextOptions = aContextOptions;
1864 }
1865
1866 RefPtr<UpdateContextOptionsRunnable> runnable =
1867 new UpdateContextOptionsRunnable(this, aContextOptions);
1868 if (!runnable->Dispatch()) {
1869 NS_WARNING("Failed to update worker context options!");
1870 }
1871 }
1872
UpdateLanguages(const nsTArray<nsString> & aLanguages)1873 void WorkerPrivate::UpdateLanguages(const nsTArray<nsString>& aLanguages) {
1874 AssertIsOnParentThread();
1875
1876 RefPtr<UpdateLanguagesRunnable> runnable =
1877 new UpdateLanguagesRunnable(this, aLanguages);
1878 if (!runnable->Dispatch()) {
1879 NS_WARNING("Failed to update worker languages!");
1880 }
1881 }
1882
UpdateJSWorkerMemoryParameter(JSGCParamKey aKey,Maybe<uint32_t> aValue)1883 void WorkerPrivate::UpdateJSWorkerMemoryParameter(JSGCParamKey aKey,
1884 Maybe<uint32_t> aValue) {
1885 AssertIsOnParentThread();
1886
1887 bool changed = false;
1888
1889 {
1890 MutexAutoLock lock(mMutex);
1891 changed = mJSSettings.ApplyGCSetting(aKey, aValue);
1892 }
1893
1894 if (changed) {
1895 RefPtr<UpdateJSWorkerMemoryParameterRunnable> runnable =
1896 new UpdateJSWorkerMemoryParameterRunnable(this, aKey, aValue);
1897 if (!runnable->Dispatch()) {
1898 NS_WARNING("Failed to update memory parameter!");
1899 }
1900 }
1901 }
1902
1903 #ifdef JS_GC_ZEAL
UpdateGCZeal(uint8_t aGCZeal,uint32_t aFrequency)1904 void WorkerPrivate::UpdateGCZeal(uint8_t aGCZeal, uint32_t aFrequency) {
1905 AssertIsOnParentThread();
1906
1907 {
1908 MutexAutoLock lock(mMutex);
1909 mJSSettings.gcZeal = aGCZeal;
1910 mJSSettings.gcZealFrequency = aFrequency;
1911 }
1912
1913 RefPtr<UpdateGCZealRunnable> runnable =
1914 new UpdateGCZealRunnable(this, aGCZeal, aFrequency);
1915 if (!runnable->Dispatch()) {
1916 NS_WARNING("Failed to update worker gczeal!");
1917 }
1918 }
1919 #endif
1920
SetLowMemoryState(bool aState)1921 void WorkerPrivate::SetLowMemoryState(bool aState) {
1922 AssertIsOnParentThread();
1923
1924 RefPtr<SetLowMemoryStateRunnable> runnable =
1925 new SetLowMemoryStateRunnable(this, aState);
1926 if (!runnable->Dispatch()) {
1927 NS_WARNING("Failed to set low memory state!");
1928 }
1929 }
1930
GarbageCollect(bool aShrinking)1931 void WorkerPrivate::GarbageCollect(bool aShrinking) {
1932 AssertIsOnParentThread();
1933
1934 RefPtr<GarbageCollectRunnable> runnable = new GarbageCollectRunnable(
1935 this, aShrinking, /* collectChildren = */ true);
1936 if (!runnable->Dispatch()) {
1937 NS_WARNING("Failed to GC worker!");
1938 }
1939 }
1940
CycleCollect()1941 void WorkerPrivate::CycleCollect() {
1942 AssertIsOnParentThread();
1943
1944 RefPtr<CycleCollectRunnable> runnable =
1945 new CycleCollectRunnable(this, /* collectChildren = */ true);
1946 if (!runnable->Dispatch()) {
1947 NS_WARNING("Failed to CC worker!");
1948 }
1949 }
1950
OfflineStatusChangeEvent(bool aIsOffline)1951 void WorkerPrivate::OfflineStatusChangeEvent(bool aIsOffline) {
1952 AssertIsOnParentThread();
1953
1954 RefPtr<OfflineStatusChangeRunnable> runnable =
1955 new OfflineStatusChangeRunnable(this, aIsOffline);
1956 if (!runnable->Dispatch()) {
1957 NS_WARNING("Failed to dispatch offline status change event!");
1958 }
1959 }
1960
OfflineStatusChangeEventInternal(bool aIsOffline)1961 void WorkerPrivate::OfflineStatusChangeEventInternal(bool aIsOffline) {
1962 auto data = mWorkerThreadAccessible.Access();
1963
1964 // The worker is already in this state. No need to dispatch an event.
1965 if (data->mOnLine == !aIsOffline) {
1966 return;
1967 }
1968
1969 for (uint32_t index = 0; index < data->mChildWorkers.Length(); ++index) {
1970 data->mChildWorkers[index]->OfflineStatusChangeEvent(aIsOffline);
1971 }
1972
1973 data->mOnLine = !aIsOffline;
1974 WorkerGlobalScope* globalScope = GlobalScope();
1975 RefPtr<WorkerNavigator> nav = globalScope->GetExistingNavigator();
1976 if (nav) {
1977 nav->SetOnLine(data->mOnLine);
1978 }
1979
1980 nsString eventType;
1981 if (aIsOffline) {
1982 eventType.AssignLiteral("offline");
1983 } else {
1984 eventType.AssignLiteral("online");
1985 }
1986
1987 RefPtr<Event> event = NS_NewDOMEvent(globalScope, nullptr, nullptr);
1988
1989 event->InitEvent(eventType, false, false);
1990 event->SetTrusted(true);
1991
1992 globalScope->DispatchEvent(*event);
1993 }
1994
MemoryPressure()1995 void WorkerPrivate::MemoryPressure() {
1996 AssertIsOnParentThread();
1997
1998 RefPtr<MemoryPressureRunnable> runnable = new MemoryPressureRunnable(this);
1999 Unused << NS_WARN_IF(!runnable->Dispatch());
2000 }
2001
WorkerScriptLoaded()2002 void WorkerPrivate::WorkerScriptLoaded() {
2003 AssertIsOnMainThread();
2004
2005 if (IsSharedWorker() || IsServiceWorker()) {
2006 // No longer need to hold references to the window or document we came from.
2007 mLoadInfo.mWindow = nullptr;
2008 mLoadInfo.mScriptContext = nullptr;
2009 }
2010 }
2011
SetBaseURI(nsIURI * aBaseURI)2012 void WorkerPrivate::SetBaseURI(nsIURI* aBaseURI) {
2013 AssertIsOnMainThread();
2014
2015 if (!mLoadInfo.mBaseURI) {
2016 NS_ASSERTION(GetParent(), "Shouldn't happen without a parent!");
2017 mLoadInfo.mResolvedScriptURI = aBaseURI;
2018 }
2019
2020 mLoadInfo.mBaseURI = aBaseURI;
2021
2022 if (NS_FAILED(aBaseURI->GetSpec(mLocationInfo.mHref))) {
2023 mLocationInfo.mHref.Truncate();
2024 }
2025
2026 mLocationInfo.mHostname.Truncate();
2027 nsContentUtils::GetHostOrIPv6WithBrackets(aBaseURI, mLocationInfo.mHostname);
2028
2029 nsCOMPtr<nsIURL> url(do_QueryInterface(aBaseURI));
2030 if (!url || NS_FAILED(url->GetFilePath(mLocationInfo.mPathname))) {
2031 mLocationInfo.mPathname.Truncate();
2032 }
2033
2034 nsCString temp;
2035
2036 if (url && NS_SUCCEEDED(url->GetQuery(temp)) && !temp.IsEmpty()) {
2037 mLocationInfo.mSearch.Assign('?');
2038 mLocationInfo.mSearch.Append(temp);
2039 }
2040
2041 if (NS_SUCCEEDED(aBaseURI->GetRef(temp)) && !temp.IsEmpty()) {
2042 if (mLocationInfo.mHash.IsEmpty()) {
2043 mLocationInfo.mHash.Assign('#');
2044 mLocationInfo.mHash.Append(temp);
2045 }
2046 }
2047
2048 if (NS_SUCCEEDED(aBaseURI->GetScheme(mLocationInfo.mProtocol))) {
2049 mLocationInfo.mProtocol.Append(':');
2050 } else {
2051 mLocationInfo.mProtocol.Truncate();
2052 }
2053
2054 int32_t port;
2055 if (NS_SUCCEEDED(aBaseURI->GetPort(&port)) && port != -1) {
2056 mLocationInfo.mPort.AppendInt(port);
2057
2058 nsAutoCString host(mLocationInfo.mHostname);
2059 host.Append(':');
2060 host.Append(mLocationInfo.mPort);
2061
2062 mLocationInfo.mHost.Assign(host);
2063 } else {
2064 mLocationInfo.mHost.Assign(mLocationInfo.mHostname);
2065 }
2066
2067 nsContentUtils::GetUTFOrigin(aBaseURI, mLocationInfo.mOrigin);
2068 }
2069
SetPrincipalsAndCSPOnMainThread(nsIPrincipal * aPrincipal,nsIPrincipal * aPartitionedPrincipal,nsILoadGroup * aLoadGroup,nsIContentSecurityPolicy * aCsp)2070 nsresult WorkerPrivate::SetPrincipalsAndCSPOnMainThread(
2071 nsIPrincipal* aPrincipal, nsIPrincipal* aPartitionedPrincipal,
2072 nsILoadGroup* aLoadGroup, nsIContentSecurityPolicy* aCsp) {
2073 return mLoadInfo.SetPrincipalsAndCSPOnMainThread(
2074 aPrincipal, aPartitionedPrincipal, aLoadGroup, aCsp);
2075 }
2076
SetPrincipalsAndCSPFromChannel(nsIChannel * aChannel)2077 nsresult WorkerPrivate::SetPrincipalsAndCSPFromChannel(nsIChannel* aChannel) {
2078 return mLoadInfo.SetPrincipalsAndCSPFromChannel(aChannel);
2079 }
2080
FinalChannelPrincipalIsValid(nsIChannel * aChannel)2081 bool WorkerPrivate::FinalChannelPrincipalIsValid(nsIChannel* aChannel) {
2082 return mLoadInfo.FinalChannelPrincipalIsValid(aChannel);
2083 }
2084
2085 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
PrincipalURIMatchesScriptURL()2086 bool WorkerPrivate::PrincipalURIMatchesScriptURL() {
2087 return mLoadInfo.PrincipalURIMatchesScriptURL();
2088 }
2089 #endif
2090
UpdateOverridenLoadGroup(nsILoadGroup * aBaseLoadGroup)2091 void WorkerPrivate::UpdateOverridenLoadGroup(nsILoadGroup* aBaseLoadGroup) {
2092 AssertIsOnMainThread();
2093
2094 // The load group should have been overriden at init time.
2095 mLoadInfo.mInterfaceRequestor->MaybeAddBrowserChild(aBaseLoadGroup);
2096 }
2097
2098 #ifdef DEBUG
2099
AssertIsOnParentThread() const2100 void WorkerPrivate::AssertIsOnParentThread() const {
2101 if (GetParent()) {
2102 GetParent()->AssertIsOnWorkerThread();
2103 } else {
2104 AssertIsOnMainThread();
2105 }
2106 }
2107
AssertInnerWindowIsCorrect() const2108 void WorkerPrivate::AssertInnerWindowIsCorrect() const {
2109 AssertIsOnParentThread();
2110
2111 // Only care about top level workers from windows.
2112 if (mParent || !mLoadInfo.mWindow) {
2113 return;
2114 }
2115
2116 AssertIsOnMainThread();
2117
2118 nsPIDOMWindowOuter* outer = mLoadInfo.mWindow->GetOuterWindow();
2119 NS_ASSERTION(outer && outer->GetCurrentInnerWindow() == mLoadInfo.mWindow,
2120 "Inner window no longer correct!");
2121 }
2122
2123 #endif
2124
2125 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
PrincipalIsValid() const2126 bool WorkerPrivate::PrincipalIsValid() const {
2127 return mLoadInfo.PrincipalIsValid();
2128 }
2129 #endif
2130
WorkerThreadAccessible(WorkerPrivate * const aParent)2131 WorkerPrivate::WorkerThreadAccessible::WorkerThreadAccessible(
2132 WorkerPrivate* const aParent)
2133 : mNumWorkerRefsPreventingShutdownStart(0),
2134 mDebuggerEventLoopLevel(0),
2135 mErrorHandlerRecursionCount(0),
2136 mNextTimeoutId(1),
2137 mCurrentTimerNestingLevel(0),
2138 mFrozen(false),
2139 mTimerRunning(false),
2140 mRunningExpiredTimeouts(false),
2141 mPeriodicGCTimerRunning(false),
2142 mIdleGCTimerRunning(false),
2143 mOnLine(aParent ? aParent->OnLine() : !NS_IsOffline()),
2144 mJSThreadExecutionGranted(false),
2145 mCCCollectedAnything(false) {}
2146
2147 namespace {
2148
IsNewWorkerSecureContext(const WorkerPrivate * const aParent,const WorkerKind aWorkerKind,const WorkerLoadInfo & aLoadInfo)2149 bool IsNewWorkerSecureContext(const WorkerPrivate* const aParent,
2150 const WorkerKind aWorkerKind,
2151 const WorkerLoadInfo& aLoadInfo) {
2152 if (aParent) {
2153 return aParent->IsSecureContext();
2154 }
2155
2156 // Our secure context state depends on the kind of worker we have.
2157
2158 if (aLoadInfo.mPrincipalIsSystem) {
2159 return true;
2160 }
2161
2162 if (aWorkerKind == WorkerKindService) {
2163 return true;
2164 }
2165
2166 if (aLoadInfo.mSecureContext != WorkerLoadInfo::eNotSet) {
2167 return aLoadInfo.mSecureContext == WorkerLoadInfo::eSecureContext;
2168 }
2169
2170 MOZ_ASSERT_UNREACHABLE(
2171 "non-chrome worker that is not a service worker "
2172 "that has no parent and no associated window");
2173
2174 return false;
2175 }
2176
2177 } // namespace
2178
WorkerPrivate(WorkerPrivate * aParent,const nsAString & aScriptURL,bool aIsChromeWorker,WorkerKind aWorkerKind,const nsAString & aWorkerName,const nsACString & aServiceWorkerScope,WorkerLoadInfo & aLoadInfo,nsString && aId,const nsID & aAgentClusterId,const nsILoadInfo::CrossOriginOpenerPolicy aAgentClusterOpenerPolicy)2179 WorkerPrivate::WorkerPrivate(
2180 WorkerPrivate* aParent, const nsAString& aScriptURL, bool aIsChromeWorker,
2181 WorkerKind aWorkerKind, const nsAString& aWorkerName,
2182 const nsACString& aServiceWorkerScope, WorkerLoadInfo& aLoadInfo,
2183 nsString&& aId, const nsID& aAgentClusterId,
2184 const nsILoadInfo::CrossOriginOpenerPolicy aAgentClusterOpenerPolicy)
2185 : mMutex("WorkerPrivate Mutex"),
2186 mCondVar(mMutex, "WorkerPrivate CondVar"),
2187 mParent(aParent),
2188 mScriptURL(aScriptURL),
2189 mWorkerName(aWorkerName),
2190 mWorkerKind(aWorkerKind),
2191 mLoadInfo(std::move(aLoadInfo)),
2192 mDebugger(nullptr),
2193 mJSContext(nullptr),
2194 mPRThread(nullptr),
2195 mWorkerControlEventTarget(new WorkerEventTarget(
2196 this, WorkerEventTarget::Behavior::ControlOnly)),
2197 mWorkerHybridEventTarget(
2198 new WorkerEventTarget(this, WorkerEventTarget::Behavior::Hybrid)),
2199 mParentStatus(Pending),
2200 mStatus(Pending),
2201 mBusyCount(0),
2202 mLoadingWorkerScript(false),
2203 mCreationTimeStamp(TimeStamp::Now()),
2204 mCreationTimeHighRes((double)PR_Now() / PR_USEC_PER_MSEC),
2205 mReportedUseCounters(false),
2206 mAgentClusterId(aAgentClusterId),
2207 mWorkerThreadAccessible(aParent),
2208 mPostSyncLoopOperations(0),
2209 mParentWindowPaused(false),
2210 mCancelAllPendingRunnables(false),
2211 mWorkerScriptExecutedSuccessfully(false),
2212 mFetchHandlerWasAdded(false),
2213 mMainThreadObjectsForgotten(false),
2214 mIsChromeWorker(aIsChromeWorker),
2215 mParentFrozen(false),
2216 mIsSecureContext(
2217 IsNewWorkerSecureContext(mParent, mWorkerKind, mLoadInfo)),
2218 mDebuggerRegistered(false),
2219 mDebuggerReady(true),
2220 mExtensionAPIAllowed(false),
2221 mIsInAutomation(false),
2222 mId(std::move(aId)),
2223 mAgentClusterOpenerPolicy(aAgentClusterOpenerPolicy),
2224 mIsPrivilegedAddonGlobal(false) {
2225 MOZ_ASSERT_IF(!IsDedicatedWorker(), NS_IsMainThread());
2226
2227 if (aParent) {
2228 aParent->AssertIsOnWorkerThread();
2229
2230 // Note that this copies our parent's secure context state into mJSSettings.
2231 aParent->CopyJSSettings(mJSSettings);
2232
2233 MOZ_ASSERT_IF(mIsChromeWorker, mIsSecureContext);
2234
2235 mIsInAutomation = aParent->IsInAutomation();
2236
2237 MOZ_ASSERT(IsDedicatedWorker());
2238
2239 if (aParent->mParentFrozen) {
2240 Freeze(nullptr);
2241 }
2242
2243 mIsPrivilegedAddonGlobal = aParent->mIsPrivilegedAddonGlobal;
2244 } else {
2245 AssertIsOnMainThread();
2246
2247 RuntimeService::GetDefaultJSSettings(mJSSettings);
2248
2249 {
2250 JS::RealmOptions& chromeRealmOptions = mJSSettings.chromeRealmOptions;
2251 JS::RealmOptions& contentRealmOptions = mJSSettings.contentRealmOptions;
2252
2253 JS::RealmBehaviors& chromeRealmBehaviors = chromeRealmOptions.behaviors();
2254 JS::RealmBehaviors& contentRealmBehaviors =
2255 contentRealmOptions.behaviors();
2256
2257 bool usesSystemPrincipal = UsesSystemPrincipal();
2258
2259 // Make timing imprecise in unprivileged code to blunt Spectre timing
2260 // attacks.
2261 bool clampAndJitterTime = !usesSystemPrincipal;
2262 chromeRealmBehaviors.setClampAndJitterTime(clampAndJitterTime);
2263 contentRealmBehaviors.setClampAndJitterTime(clampAndJitterTime);
2264
2265 JS::RealmCreationOptions& chromeCreationOptions =
2266 chromeRealmOptions.creationOptions();
2267 JS::RealmCreationOptions& contentCreationOptions =
2268 contentRealmOptions.creationOptions();
2269
2270 // Expose uneval and toSource functions only if this is privileged code.
2271 bool toSourceEnabled = usesSystemPrincipal;
2272 chromeCreationOptions.setToSourceEnabled(toSourceEnabled);
2273 contentCreationOptions.setToSourceEnabled(toSourceEnabled);
2274
2275 if (mIsSecureContext) {
2276 chromeCreationOptions.setSecureContext(true);
2277 contentCreationOptions.setSecureContext(true);
2278 }
2279
2280 // Check if it's a privileged addon executing in order to allow access
2281 // to SharedArrayBuffer
2282 if (mLoadInfo.mPrincipal) {
2283 if (auto* policy =
2284 BasePrincipal::Cast(mLoadInfo.mPrincipal)->AddonPolicy()) {
2285 if (policy->IsPrivileged() &&
2286 ExtensionPolicyService::GetSingleton().IsExtensionProcess()) {
2287 // Privileged extensions are allowed to use SharedArrayBuffer in
2288 // their extension process, but never in content scripts in
2289 // content processes.
2290 mIsPrivilegedAddonGlobal = true;
2291 }
2292
2293 if (StaticPrefs::
2294 extensions_backgroundServiceWorker_enabled_AtStartup() &&
2295 mWorkerKind == WorkerKindService &&
2296 policy->IsManifestBackgroundWorker(mScriptURL)) {
2297 // Only allows ExtensionAPI for extension service workers
2298 // that are declared in the extension manifest json as
2299 // the background service worker.
2300 mExtensionAPIAllowed = true;
2301 }
2302 }
2303 }
2304
2305 // The SharedArrayBuffer global constructor property should not be present
2306 // in a fresh global object when shared memory objects aren't allowed
2307 // (because COOP/COEP support isn't enabled, or because COOP/COEP don't
2308 // act to isolate this worker to a separate process).
2309 const bool defineSharedArrayBufferConstructor = IsSharedMemoryAllowed();
2310 chromeCreationOptions.setDefineSharedArrayBufferConstructor(
2311 defineSharedArrayBufferConstructor);
2312 contentCreationOptions.setDefineSharedArrayBufferConstructor(
2313 defineSharedArrayBufferConstructor);
2314 }
2315
2316 mIsInAutomation = xpc::IsInAutomation();
2317
2318 // Our parent can get suspended after it initiates the async creation
2319 // of a new worker thread. In this case suspend the new worker as well.
2320 if (mLoadInfo.mWindow && mLoadInfo.mWindow->IsSuspended()) {
2321 ParentWindowPaused();
2322 }
2323
2324 if (mLoadInfo.mWindow && mLoadInfo.mWindow->IsFrozen()) {
2325 Freeze(mLoadInfo.mWindow);
2326 }
2327 }
2328
2329 nsCOMPtr<nsISerialEventTarget> target;
2330
2331 // A child worker just inherits the parent workers ThrottledEventQueue
2332 // and main thread target for now. This is mainly due to the restriction
2333 // that ThrottledEventQueue can only be created on the main thread at the
2334 // moment.
2335 if (aParent) {
2336 mMainThreadEventTargetForMessaging =
2337 aParent->mMainThreadEventTargetForMessaging;
2338 mMainThreadEventTarget = aParent->mMainThreadEventTarget;
2339 mMainThreadDebuggeeEventTarget = aParent->mMainThreadDebuggeeEventTarget;
2340 return;
2341 }
2342
2343 MOZ_ASSERT(NS_IsMainThread());
2344 target = GetWindow()
2345 ? GetWindow()->GetBrowsingContextGroup()->GetWorkerEventQueue()
2346 : nullptr;
2347
2348 if (!target) {
2349 target = GetMainThreadSerialEventTarget();
2350 MOZ_DIAGNOSTIC_ASSERT(target);
2351 }
2352
2353 // Throttle events to the main thread using a ThrottledEventQueue specific to
2354 // this tree of worker threads.
2355 mMainThreadEventTargetForMessaging =
2356 ThrottledEventQueue::Create(target, "Worker queue for messaging");
2357 if (StaticPrefs::dom_worker_use_medium_high_event_queue()) {
2358 mMainThreadEventTarget = ThrottledEventQueue::Create(
2359 GetMainThreadSerialEventTarget(), "Worker queue",
2360 nsIRunnablePriority::PRIORITY_MEDIUMHIGH);
2361 } else {
2362 mMainThreadEventTarget = mMainThreadEventTargetForMessaging;
2363 }
2364 mMainThreadDebuggeeEventTarget =
2365 ThrottledEventQueue::Create(target, "Worker debuggee queue");
2366 if (IsParentWindowPaused() || IsFrozen()) {
2367 MOZ_ALWAYS_SUCCEEDS(mMainThreadDebuggeeEventTarget->SetIsPaused(true));
2368 }
2369 }
2370
~WorkerPrivate()2371 WorkerPrivate::~WorkerPrivate() {
2372 mWorkerControlEventTarget->ForgetWorkerPrivate(this);
2373
2374 // We force the hybrid event target to forget the thread when we
2375 // enter the Killing state, but we do it again here to be safe.
2376 // Its possible that we may be created and destroyed without progressing
2377 // to Killing via some obscure code path.
2378 mWorkerHybridEventTarget->ForgetWorkerPrivate(this);
2379 }
2380
2381 // static
Constructor(JSContext * aCx,const nsAString & aScriptURL,bool aIsChromeWorker,WorkerKind aWorkerKind,const nsAString & aWorkerName,const nsACString & aServiceWorkerScope,WorkerLoadInfo * aLoadInfo,ErrorResult & aRv,nsString aId)2382 already_AddRefed<WorkerPrivate> WorkerPrivate::Constructor(
2383 JSContext* aCx, const nsAString& aScriptURL, bool aIsChromeWorker,
2384 WorkerKind aWorkerKind, const nsAString& aWorkerName,
2385 const nsACString& aServiceWorkerScope, WorkerLoadInfo* aLoadInfo,
2386 ErrorResult& aRv, nsString aId) {
2387 WorkerPrivate* parent =
2388 NS_IsMainThread() ? nullptr : GetCurrentThreadWorkerPrivate();
2389
2390 // If this is a sub-worker, we need to keep the parent worker alive until this
2391 // one is registered.
2392 RefPtr<StrongWorkerRef> workerRef;
2393 if (parent) {
2394 parent->AssertIsOnWorkerThread();
2395
2396 workerRef = StrongWorkerRef::Create(parent, "WorkerPrivate::Constructor");
2397 if (NS_WARN_IF(!workerRef)) {
2398 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
2399 return nullptr;
2400 }
2401 } else {
2402 AssertIsOnMainThread();
2403 }
2404
2405 Maybe<WorkerLoadInfo> stackLoadInfo;
2406 if (!aLoadInfo) {
2407 stackLoadInfo.emplace();
2408
2409 nsresult rv =
2410 GetLoadInfo(aCx, nullptr, parent, aScriptURL, aIsChromeWorker,
2411 InheritLoadGroup, aWorkerKind, stackLoadInfo.ptr());
2412 aRv.MightThrowJSException();
2413 if (NS_FAILED(rv)) {
2414 workerinternals::ReportLoadError(aRv, rv, aScriptURL);
2415 return nullptr;
2416 }
2417
2418 aLoadInfo = stackLoadInfo.ptr();
2419 }
2420
2421 // NB: This has to be done before creating the WorkerPrivate, because it will
2422 // attempt to use static variables that are initialized in the RuntimeService
2423 // constructor.
2424 RuntimeService* runtimeService;
2425
2426 if (!parent) {
2427 runtimeService = RuntimeService::GetOrCreateService();
2428 if (!runtimeService) {
2429 aRv.Throw(NS_ERROR_FAILURE);
2430 return nullptr;
2431 }
2432 } else {
2433 runtimeService = RuntimeService::GetService();
2434 }
2435
2436 MOZ_ASSERT(runtimeService);
2437
2438 // Don't create a worker with the shutting down RuntimeService.
2439 if (runtimeService->IsShuttingDown()) {
2440 aRv.Throw(NS_ERROR_UNEXPECTED);
2441 return nullptr;
2442 }
2443
2444 nsILoadInfo::CrossOriginOpenerPolicy agentClusterCoop =
2445 nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
2446 nsID agentClusterId;
2447 if (parent) {
2448 MOZ_ASSERT(aWorkerKind == WorkerKind::WorkerKindDedicated);
2449
2450 agentClusterId = parent->AgentClusterId();
2451 agentClusterCoop = parent->mAgentClusterOpenerPolicy;
2452 } else {
2453 AssertIsOnMainThread();
2454
2455 if (aWorkerKind == WorkerKind::WorkerKindService ||
2456 aWorkerKind == WorkerKind::WorkerKindShared) {
2457 agentClusterId = aLoadInfo->mAgentClusterId;
2458 } else if (aLoadInfo->mWindow) {
2459 Document* doc = aLoadInfo->mWindow->GetExtantDoc();
2460 MOZ_DIAGNOSTIC_ASSERT(doc);
2461 RefPtr<DocGroup> docGroup = doc->GetDocGroup();
2462
2463 agentClusterId = docGroup ? docGroup->AgentClusterId()
2464 : nsContentUtils::GenerateUUID();
2465
2466 BrowsingContext* bc = aLoadInfo->mWindow->GetBrowsingContext();
2467 MOZ_DIAGNOSTIC_ASSERT(bc);
2468 agentClusterCoop = bc->Top()->GetOpenerPolicy();
2469 } else {
2470 // If the window object was failed to be set into the WorkerLoadInfo, we
2471 // make the worker into another agent cluster group instead of failures.
2472 agentClusterId = nsContentUtils::GenerateUUID();
2473 }
2474 }
2475
2476 RefPtr<WorkerPrivate> worker =
2477 new WorkerPrivate(parent, aScriptURL, aIsChromeWorker, aWorkerKind,
2478 aWorkerName, aServiceWorkerScope, *aLoadInfo,
2479 std::move(aId), agentClusterId, agentClusterCoop);
2480
2481 // Gecko contexts always have an explicitly-set default locale (set by
2482 // XPJSRuntime::Initialize for the main thread, set by
2483 // WorkerThreadPrimaryRunnable::Run for workers just before running worker
2484 // code), so this is never SpiderMonkey's builtin default locale.
2485 JS::UniqueChars defaultLocale = JS_GetDefaultLocale(aCx);
2486 if (NS_WARN_IF(!defaultLocale)) {
2487 aRv.Throw(NS_ERROR_UNEXPECTED);
2488 return nullptr;
2489 }
2490
2491 worker->mDefaultLocale = std::move(defaultLocale);
2492
2493 if (!runtimeService->RegisterWorker(*worker)) {
2494 aRv.Throw(NS_ERROR_UNEXPECTED);
2495 return nullptr;
2496 }
2497
2498 // From this point on (worker thread has been started) we
2499 // must keep ourself alive. We can now only be cleared by
2500 // ClearSelfAndParentEventTargetRef().
2501 worker->mSelfRef = worker;
2502
2503 worker->EnableDebugger();
2504
2505 MOZ_DIAGNOSTIC_ASSERT(worker->PrincipalIsValid());
2506
2507 UniquePtr<SerializedStackHolder> stack;
2508 if (worker->IsWatchedByDevTools()) {
2509 stack = GetCurrentStackForNetMonitor(aCx);
2510 }
2511
2512 RefPtr<CompileScriptRunnable> compiler =
2513 new CompileScriptRunnable(worker, std::move(stack), aScriptURL);
2514 if (!compiler->Dispatch()) {
2515 aRv.Throw(NS_ERROR_UNEXPECTED);
2516 return nullptr;
2517 }
2518
2519 return worker.forget();
2520 }
2521
SetIsDebuggerReady(bool aReady)2522 nsresult WorkerPrivate::SetIsDebuggerReady(bool aReady) {
2523 AssertIsOnMainThread();
2524 MutexAutoLock lock(mMutex);
2525
2526 if (mDebuggerReady == aReady) {
2527 return NS_OK;
2528 }
2529
2530 if (!aReady && mDebuggerRegistered) {
2531 // The debugger can only be marked as not ready during registration.
2532 return NS_ERROR_FAILURE;
2533 }
2534
2535 mDebuggerReady = aReady;
2536
2537 if (aReady && mDebuggerRegistered) {
2538 // Dispatch all the delayed runnables without releasing the lock, to ensure
2539 // that the order in which debuggee runnables execute is the same as the
2540 // order in which they were originally dispatched.
2541 auto pending = std::move(mDelayedDebuggeeRunnables);
2542 for (uint32_t i = 0; i < pending.Length(); i++) {
2543 RefPtr<WorkerRunnable> runnable = std::move(pending[i]);
2544 nsresult rv = DispatchLockHeld(runnable.forget(), nullptr, lock);
2545 NS_ENSURE_SUCCESS(rv, rv);
2546 }
2547 MOZ_RELEASE_ASSERT(mDelayedDebuggeeRunnables.IsEmpty());
2548 }
2549
2550 return NS_OK;
2551 }
2552
2553 // static
GetLoadInfo(JSContext * aCx,nsPIDOMWindowInner * aWindow,WorkerPrivate * aParent,const nsAString & aScriptURL,bool aIsChromeWorker,LoadGroupBehavior aLoadGroupBehavior,WorkerKind aWorkerKind,WorkerLoadInfo * aLoadInfo)2554 nsresult WorkerPrivate::GetLoadInfo(JSContext* aCx, nsPIDOMWindowInner* aWindow,
2555 WorkerPrivate* aParent,
2556 const nsAString& aScriptURL,
2557 bool aIsChromeWorker,
2558 LoadGroupBehavior aLoadGroupBehavior,
2559 WorkerKind aWorkerKind,
2560 WorkerLoadInfo* aLoadInfo) {
2561 using namespace mozilla::dom::workerinternals;
2562
2563 MOZ_ASSERT(aCx);
2564 MOZ_ASSERT_IF(NS_IsMainThread(),
2565 aCx == nsContentUtils::GetCurrentJSContext());
2566
2567 if (aWindow) {
2568 AssertIsOnMainThread();
2569 }
2570
2571 WorkerLoadInfo loadInfo;
2572 nsresult rv;
2573
2574 if (aParent) {
2575 aParent->AssertIsOnWorkerThread();
2576
2577 // If the parent is going away give up now.
2578 WorkerStatus parentStatus;
2579 {
2580 MutexAutoLock lock(aParent->mMutex);
2581 parentStatus = aParent->mStatus;
2582 }
2583
2584 if (parentStatus > Running) {
2585 return NS_ERROR_FAILURE;
2586 }
2587
2588 // Passing a pointer to our stack loadInfo is safe here because this
2589 // method uses a sync runnable to get the channel from the main thread.
2590 rv = ChannelFromScriptURLWorkerThread(aCx, aParent, aScriptURL, loadInfo);
2591 if (NS_FAILED(rv)) {
2592 MOZ_ALWAYS_TRUE(loadInfo.ProxyReleaseMainThreadObjects(aParent));
2593 return rv;
2594 }
2595
2596 // Now that we've spun the loop there's no guarantee that our parent is
2597 // still alive. We may have received control messages initiating shutdown.
2598 {
2599 MutexAutoLock lock(aParent->mMutex);
2600 parentStatus = aParent->mStatus;
2601 }
2602
2603 if (parentStatus > Running) {
2604 MOZ_ALWAYS_TRUE(loadInfo.ProxyReleaseMainThreadObjects(aParent));
2605 return NS_ERROR_FAILURE;
2606 }
2607
2608 loadInfo.mDomain = aParent->Domain();
2609 loadInfo.mFromWindow = aParent->IsFromWindow();
2610 loadInfo.mWindowID = aParent->WindowID();
2611 loadInfo.mStorageAccess = aParent->StorageAccess();
2612 loadInfo.mUseRegularPrincipal = aParent->UseRegularPrincipal();
2613 loadInfo.mHasStorageAccessPermissionGranted =
2614 aParent->HasStorageAccessPermissionGranted();
2615 loadInfo.mCookieJarSettings = aParent->CookieJarSettings();
2616 loadInfo.mOriginAttributes = aParent->GetOriginAttributes();
2617 loadInfo.mServiceWorkersTestingInWindow =
2618 aParent->ServiceWorkersTestingInWindow();
2619 loadInfo.mParentController = aParent->GlobalScope()->GetController();
2620 loadInfo.mWatchedByDevTools = aParent->IsWatchedByDevTools();
2621 } else {
2622 AssertIsOnMainThread();
2623
2624 // Make sure that the IndexedDatabaseManager is set up
2625 Unused << NS_WARN_IF(!IndexedDatabaseManager::GetOrCreate());
2626
2627 nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
2628 MOZ_ASSERT(ssm);
2629
2630 bool isChrome = nsContentUtils::IsSystemCaller(aCx);
2631
2632 // First check to make sure the caller has permission to make a privileged
2633 // worker if they called the ChromeWorker/ChromeSharedWorker constructor.
2634 if (aIsChromeWorker && !isChrome) {
2635 return NS_ERROR_DOM_SECURITY_ERR;
2636 }
2637
2638 // Chrome callers (whether creating a ChromeWorker or Worker) always get the
2639 // system principal here as they're allowed to load anything. The script
2640 // loader will refuse to run any script that does not also have the system
2641 // principal.
2642 if (isChrome) {
2643 rv = ssm->GetSystemPrincipal(getter_AddRefs(loadInfo.mLoadingPrincipal));
2644 NS_ENSURE_SUCCESS(rv, rv);
2645
2646 loadInfo.mPrincipalIsSystem = true;
2647 }
2648
2649 // See if we're being called from a window.
2650 nsCOMPtr<nsPIDOMWindowInner> globalWindow = aWindow;
2651 if (!globalWindow) {
2652 globalWindow = xpc::CurrentWindowOrNull(aCx);
2653 }
2654
2655 nsCOMPtr<Document> document;
2656 Maybe<ClientInfo> clientInfo;
2657
2658 if (globalWindow) {
2659 // Only use the current inner window, and only use it if the caller can
2660 // access it.
2661 if (nsPIDOMWindowOuter* outerWindow = globalWindow->GetOuterWindow()) {
2662 loadInfo.mWindow = outerWindow->GetCurrentInnerWindow();
2663 }
2664
2665 BrowsingContext* browsingContext = globalWindow->GetBrowsingContext();
2666
2667 // TODO: fix this for SharedWorkers with multiple documents (bug
2668 // 1177935)
2669 loadInfo.mServiceWorkersTestingInWindow =
2670 browsingContext &&
2671 browsingContext->Top()->ServiceWorkersTestingEnabled();
2672
2673 if (!loadInfo.mWindow ||
2674 (globalWindow != loadInfo.mWindow &&
2675 !nsContentUtils::CanCallerAccess(loadInfo.mWindow))) {
2676 return NS_ERROR_DOM_SECURITY_ERR;
2677 }
2678
2679 nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(loadInfo.mWindow);
2680 MOZ_ASSERT(sgo);
2681
2682 loadInfo.mScriptContext = sgo->GetContext();
2683 NS_ENSURE_TRUE(loadInfo.mScriptContext, NS_ERROR_FAILURE);
2684
2685 // If we're called from a window then we can dig out the principal and URI
2686 // from the document.
2687 document = loadInfo.mWindow->GetExtantDoc();
2688 NS_ENSURE_TRUE(document, NS_ERROR_FAILURE);
2689
2690 loadInfo.mBaseURI = document->GetDocBaseURI();
2691 loadInfo.mLoadGroup = document->GetDocumentLoadGroup();
2692 NS_ENSURE_TRUE(loadInfo.mLoadGroup, NS_ERROR_FAILURE);
2693
2694 clientInfo = globalWindow->GetClientInfo();
2695
2696 // Use the document's NodePrincipal as loading principal if we're not
2697 // being called from chrome.
2698 if (!loadInfo.mLoadingPrincipal) {
2699 loadInfo.mLoadingPrincipal = document->NodePrincipal();
2700 NS_ENSURE_TRUE(loadInfo.mLoadingPrincipal, NS_ERROR_FAILURE);
2701
2702 // We use the document's base domain to limit the number of workers
2703 // each domain can create. For sandboxed documents, we use the domain
2704 // of their first non-sandboxed document, walking up until we find
2705 // one. If we can't find one, we fall back to using the GUID of the
2706 // null principal as the base domain.
2707 if (document->GetSandboxFlags() & SANDBOXED_ORIGIN) {
2708 nsCOMPtr<Document> tmpDoc = document;
2709 do {
2710 tmpDoc = tmpDoc->GetInProcessParentDocument();
2711 } while (tmpDoc && tmpDoc->GetSandboxFlags() & SANDBOXED_ORIGIN);
2712
2713 if (tmpDoc) {
2714 // There was an unsandboxed ancestor, yay!
2715 nsCOMPtr<nsIPrincipal> tmpPrincipal = tmpDoc->NodePrincipal();
2716 rv = tmpPrincipal->GetBaseDomain(loadInfo.mDomain);
2717 NS_ENSURE_SUCCESS(rv, rv);
2718 } else {
2719 // No unsandboxed ancestor, use our GUID.
2720 rv = loadInfo.mLoadingPrincipal->GetBaseDomain(loadInfo.mDomain);
2721 NS_ENSURE_SUCCESS(rv, rv);
2722 }
2723 } else {
2724 // Document creating the worker is not sandboxed.
2725 rv = loadInfo.mLoadingPrincipal->GetBaseDomain(loadInfo.mDomain);
2726 NS_ENSURE_SUCCESS(rv, rv);
2727 }
2728 }
2729
2730 NS_ENSURE_TRUE(NS_LoadGroupMatchesPrincipal(loadInfo.mLoadGroup,
2731 loadInfo.mLoadingPrincipal),
2732 NS_ERROR_FAILURE);
2733
2734 nsCOMPtr<nsIPermissionManager> permMgr =
2735 do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
2736 NS_ENSURE_SUCCESS(rv, rv);
2737
2738 uint32_t perm;
2739 rv = permMgr->TestPermissionFromPrincipal(loadInfo.mLoadingPrincipal,
2740 "systemXHR"_ns, &perm);
2741 NS_ENSURE_SUCCESS(rv, rv);
2742
2743 loadInfo.mXHRParamsAllowed = perm == nsIPermissionManager::ALLOW_ACTION;
2744
2745 loadInfo.mWatchedByDevTools =
2746 browsingContext && browsingContext->WatchedByDevTools();
2747
2748 loadInfo.mReferrerInfo =
2749 ReferrerInfo::CreateForFetch(loadInfo.mLoadingPrincipal, document);
2750 loadInfo.mFromWindow = true;
2751 loadInfo.mWindowID = globalWindow->WindowID();
2752 loadInfo.mStorageAccess = StorageAllowedForWindow(globalWindow);
2753 loadInfo.mUseRegularPrincipal = document->UseRegularPrincipal();
2754 loadInfo.mHasStorageAccessPermissionGranted =
2755 document->HasStorageAccessPermissionGranted();
2756
2757 // This is an hack to deny the storage-access-permission for workers of
2758 // sub-iframes.
2759 if (loadInfo.mHasStorageAccessPermissionGranted &&
2760 StorageAllowedForDocument(document) != StorageAccess::eAllow) {
2761 loadInfo.mHasStorageAccessPermissionGranted = false;
2762 }
2763 loadInfo.mCookieJarSettings = document->CookieJarSettings();
2764 StoragePrincipalHelper::GetRegularPrincipalOriginAttributes(
2765 document, loadInfo.mOriginAttributes);
2766 loadInfo.mParentController = globalWindow->GetController();
2767 loadInfo.mSecureContext = loadInfo.mWindow->IsSecureContext()
2768 ? WorkerLoadInfo::eSecureContext
2769 : WorkerLoadInfo::eInsecureContext;
2770 } else {
2771 // Not a window
2772 MOZ_ASSERT(isChrome);
2773
2774 // We're being created outside of a window. Need to figure out the script
2775 // that is creating us in order for us to use relative URIs later on.
2776 JS::AutoFilename fileName;
2777 if (JS::DescribeScriptedCaller(aCx, &fileName)) {
2778 // In most cases, fileName is URI. In a few other cases
2779 // (e.g. xpcshell), fileName is a file path. Ideally, we would
2780 // prefer testing whether fileName parses as an URI and fallback
2781 // to file path in case of error, but Windows file paths have
2782 // the interesting property that they can be parsed as bogus
2783 // URIs (e.g. C:/Windows/Tmp is interpreted as scheme "C",
2784 // hostname "Windows", path "Tmp"), which defeats this algorithm.
2785 // Therefore, we adopt the opposite convention.
2786 nsCOMPtr<nsIFile> scriptFile =
2787 do_CreateInstance("@mozilla.org/file/local;1", &rv);
2788 if (NS_FAILED(rv)) {
2789 return rv;
2790 }
2791
2792 rv = scriptFile->InitWithPath(NS_ConvertUTF8toUTF16(fileName.get()));
2793 if (NS_SUCCEEDED(rv)) {
2794 rv = NS_NewFileURI(getter_AddRefs(loadInfo.mBaseURI), scriptFile);
2795 }
2796 if (NS_FAILED(rv)) {
2797 // As expected, fileName is not a path, so proceed with
2798 // a uri.
2799 rv = NS_NewURI(getter_AddRefs(loadInfo.mBaseURI), fileName.get());
2800 }
2801 if (NS_FAILED(rv)) {
2802 return rv;
2803 }
2804 }
2805 loadInfo.mXHRParamsAllowed = true;
2806 loadInfo.mFromWindow = false;
2807 loadInfo.mWindowID = UINT64_MAX;
2808 loadInfo.mStorageAccess = StorageAccess::eAllow;
2809 loadInfo.mUseRegularPrincipal = true;
2810 loadInfo.mHasStorageAccessPermissionGranted = false;
2811 loadInfo.mCookieJarSettings =
2812 mozilla::net::CookieJarSettings::Create(loadInfo.mLoadingPrincipal);
2813 MOZ_ASSERT(loadInfo.mCookieJarSettings);
2814
2815 loadInfo.mOriginAttributes = OriginAttributes();
2816 }
2817
2818 MOZ_ASSERT(loadInfo.mLoadingPrincipal);
2819 MOZ_ASSERT(isChrome || !loadInfo.mDomain.IsEmpty());
2820
2821 if (!loadInfo.mLoadGroup || aLoadGroupBehavior == OverrideLoadGroup) {
2822 OverrideLoadInfoLoadGroup(loadInfo, loadInfo.mLoadingPrincipal);
2823 }
2824 MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(loadInfo.mLoadGroup,
2825 loadInfo.mLoadingPrincipal));
2826
2827 // Top level workers' main script use the document charset for the script
2828 // uri encoding.
2829 nsCOMPtr<nsIURI> url;
2830 rv = nsContentUtils::NewURIWithDocumentCharset(
2831 getter_AddRefs(url), aScriptURL, document, loadInfo.mBaseURI);
2832 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
2833
2834 rv = ChannelFromScriptURLMainThread(
2835 loadInfo.mLoadingPrincipal, document, loadInfo.mLoadGroup, url,
2836 clientInfo, ContentPolicyType(aWorkerKind), loadInfo.mCookieJarSettings,
2837 loadInfo.mReferrerInfo, getter_AddRefs(loadInfo.mChannel));
2838 NS_ENSURE_SUCCESS(rv, rv);
2839
2840 rv = NS_GetFinalChannelURI(loadInfo.mChannel,
2841 getter_AddRefs(loadInfo.mResolvedScriptURI));
2842 NS_ENSURE_SUCCESS(rv, rv);
2843
2844 // We need the correct hasStoragePermission flag for the channel here since
2845 // we will do a content blocking check later when we set the storage
2846 // principal for the worker. The channel here won't be opened when we do the
2847 // check later, so the hasStoragePermission flag is incorrect. To address
2848 // this, We copy the hasStoragePermission flag from the document if there is
2849 // a window. The worker is created as the same origin of the window. So, the
2850 // worker is supposed to have the same storage permission as the window as
2851 // well as the hasStoragePermission flag.
2852 nsCOMPtr<nsILoadInfo> channelLoadInfo = loadInfo.mChannel->LoadInfo();
2853 rv = channelLoadInfo->SetHasStoragePermission(
2854 loadInfo.mHasStorageAccessPermissionGranted);
2855 NS_ENSURE_SUCCESS(rv, rv);
2856
2857 rv = loadInfo.SetPrincipalsAndCSPFromChannel(loadInfo.mChannel);
2858 NS_ENSURE_SUCCESS(rv, rv);
2859 }
2860
2861 MOZ_DIAGNOSTIC_ASSERT(loadInfo.mLoadingPrincipal);
2862 MOZ_DIAGNOSTIC_ASSERT(loadInfo.PrincipalIsValid());
2863
2864 *aLoadInfo = std::move(loadInfo);
2865 return NS_OK;
2866 }
2867
2868 // static
OverrideLoadInfoLoadGroup(WorkerLoadInfo & aLoadInfo,nsIPrincipal * aPrincipal)2869 void WorkerPrivate::OverrideLoadInfoLoadGroup(WorkerLoadInfo& aLoadInfo,
2870 nsIPrincipal* aPrincipal) {
2871 MOZ_ASSERT(!aLoadInfo.mInterfaceRequestor);
2872 MOZ_ASSERT(aLoadInfo.mLoadingPrincipal == aPrincipal);
2873
2874 aLoadInfo.mInterfaceRequestor =
2875 new WorkerLoadInfo::InterfaceRequestor(aPrincipal, aLoadInfo.mLoadGroup);
2876 aLoadInfo.mInterfaceRequestor->MaybeAddBrowserChild(aLoadInfo.mLoadGroup);
2877
2878 // NOTE: this defaults the load context to:
2879 // - private browsing = false
2880 // - content = true
2881 // - use remote tabs = false
2882 nsCOMPtr<nsILoadGroup> loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
2883
2884 nsresult rv =
2885 loadGroup->SetNotificationCallbacks(aLoadInfo.mInterfaceRequestor);
2886 MOZ_ALWAYS_SUCCEEDS(rv);
2887
2888 aLoadInfo.mLoadGroup = std::move(loadGroup);
2889
2890 MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(aLoadInfo.mLoadGroup, aPrincipal));
2891 }
2892
RunLoopNeverRan()2893 void WorkerPrivate::RunLoopNeverRan() {
2894 {
2895 MutexAutoLock lock(mMutex);
2896
2897 mStatus = Dead;
2898 }
2899
2900 // After mStatus is set to Dead there can be no more
2901 // WorkerControlRunnables so no need to lock here.
2902 if (!mControlQueue.IsEmpty()) {
2903 WorkerControlRunnable* runnable = nullptr;
2904 while (mControlQueue.Pop(runnable)) {
2905 runnable->Cancel();
2906 runnable->Release();
2907 }
2908 }
2909
2910 ScheduleDeletion(WorkerPrivate::WorkerRan);
2911 }
2912
DoRunLoop(JSContext * aCx)2913 void WorkerPrivate::DoRunLoop(JSContext* aCx) {
2914 auto data = mWorkerThreadAccessible.Access();
2915 MOZ_ASSERT(mThread);
2916
2917 MOZ_RELEASE_ASSERT(!GetExecutionManager());
2918
2919 {
2920 MutexAutoLock lock(mMutex);
2921 mJSContext = aCx;
2922
2923 MOZ_ASSERT(mStatus == Pending);
2924 mStatus = Running;
2925 }
2926
2927 // Now that we've done that, we can go ahead and set up our AutoJSAPI. We
2928 // can't before this point, because it can't find the right JSContext before
2929 // then, since it gets it from our mJSContext.
2930 AutoJSAPI jsapi;
2931 jsapi.Init();
2932 MOZ_ASSERT(jsapi.cx() == aCx);
2933
2934 EnableMemoryReporter();
2935
2936 InitializeGCTimers();
2937
2938 for (;;) {
2939 WorkerStatus currentStatus;
2940 bool debuggerRunnablesPending = false;
2941 bool normalRunnablesPending = false;
2942
2943 {
2944 MutexAutoLock lock(mMutex);
2945
2946 // Wait for a runnable to arrive that we can execute, or for it to be okay
2947 // to shutdown this worker once all holders have been removed.
2948 // Holders may be removed from inside normal runnables, but we don't check
2949 // for that after processing normal runnables, so we need to let control
2950 // flow to the shutdown logic without blocking.
2951 while (mControlQueue.IsEmpty() &&
2952 !(debuggerRunnablesPending = !mDebuggerQueue.IsEmpty()) &&
2953 !(normalRunnablesPending = NS_HasPendingEvents(mThread)) &&
2954 !(mStatus != Running && !HasActiveWorkerRefs())) {
2955 // We pop out to this loop when there are no pending events.
2956 // If we don't reset these, we may not re-enter ProcessNextEvent()
2957 // until we have events to process, and it may seem like we have
2958 // an event running for a very long time.
2959 mThread->SetRunningEventDelay(TimeDuration(), TimeStamp());
2960
2961 WaitForWorkerEvents();
2962 }
2963
2964 auto result = ProcessAllControlRunnablesLocked();
2965 if (result != ProcessAllControlRunnablesResult::Nothing) {
2966 // NB: There's no JS on the stack here, so Abort vs MayContinue is
2967 // irrelevant
2968
2969 // The state of the world may have changed, recheck it.
2970 normalRunnablesPending = NS_HasPendingEvents(mThread);
2971 // The debugger queue doesn't get cleared, so we can ignore that.
2972 }
2973
2974 currentStatus = mStatus;
2975 }
2976
2977 // if all holders are done then we can kill this thread.
2978 if (currentStatus != Running && !HasActiveWorkerRefs()) {
2979 // Now we are ready to kill the worker thread.
2980 if (currentStatus == Canceling) {
2981 NotifyInternal(Killing);
2982
2983 #ifdef DEBUG
2984 {
2985 MutexAutoLock lock(mMutex);
2986 currentStatus = mStatus;
2987 }
2988 MOZ_ASSERT(currentStatus == Killing);
2989 #else
2990 currentStatus = Killing;
2991 #endif
2992 }
2993
2994 // If we're supposed to die then we should exit the loop.
2995 if (currentStatus == Killing) {
2996 // We are about to destroy worker, report all use counters.
2997 ReportUseCounters();
2998
2999 // Flush uncaught rejections immediately, without
3000 // waiting for a next tick.
3001 PromiseDebugging::FlushUncaughtRejections();
3002
3003 ShutdownGCTimers();
3004
3005 DisableMemoryReporter();
3006
3007 {
3008 MutexAutoLock lock(mMutex);
3009
3010 mStatus = Dead;
3011 mJSContext = nullptr;
3012 }
3013
3014 // After mStatus is set to Dead there can be no more
3015 // WorkerControlRunnables so no need to lock here.
3016 if (!mControlQueue.IsEmpty()) {
3017 WorkerControlRunnable* runnable = nullptr;
3018 while (mControlQueue.Pop(runnable)) {
3019 runnable->Cancel();
3020 runnable->Release();
3021 }
3022 }
3023
3024 // Unroot the globals
3025 data->mScope = nullptr;
3026 data->mDebuggerScope = nullptr;
3027
3028 return;
3029 }
3030 }
3031
3032 if (debuggerRunnablesPending || normalRunnablesPending) {
3033 // Start the periodic GC timer if it is not already running.
3034 SetGCTimerMode(PeriodicTimer);
3035 }
3036
3037 if (debuggerRunnablesPending) {
3038 WorkerRunnable* runnable = nullptr;
3039
3040 {
3041 MutexAutoLock lock(mMutex);
3042
3043 mDebuggerQueue.Pop(runnable);
3044 debuggerRunnablesPending = !mDebuggerQueue.IsEmpty();
3045 }
3046
3047 MOZ_ASSERT(runnable);
3048 static_cast<nsIRunnable*>(runnable)->Run();
3049 runnable->Release();
3050
3051 CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
3052 ccjs->PerformDebuggerMicroTaskCheckpoint();
3053
3054 if (debuggerRunnablesPending) {
3055 WorkerDebuggerGlobalScope* globalScope = DebuggerGlobalScope();
3056 MOZ_ASSERT(globalScope);
3057
3058 // Now *might* be a good time to GC. Let the JS engine make the
3059 // decision.
3060 JSAutoRealm ar(aCx, globalScope->GetGlobalJSObject());
3061 JS_MaybeGC(aCx);
3062 }
3063 } else if (normalRunnablesPending) {
3064 // Process a single runnable from the main queue.
3065 NS_ProcessNextEvent(mThread, false);
3066
3067 normalRunnablesPending = NS_HasPendingEvents(mThread);
3068 if (normalRunnablesPending && GlobalScope()) {
3069 // Now *might* be a good time to GC. Let the JS engine make the
3070 // decision.
3071 JSAutoRealm ar(aCx, GlobalScope()->GetGlobalJSObject());
3072 JS_MaybeGC(aCx);
3073 }
3074 }
3075
3076 if (!debuggerRunnablesPending && !normalRunnablesPending) {
3077 // Both the debugger event queue and the normal event queue has been
3078 // exhausted, cancel the periodic GC timer and schedule the idle GC timer.
3079 SetGCTimerMode(IdleTimer);
3080 }
3081
3082 // If the worker thread is spamming the main thread faster than it can
3083 // process the work, then pause the worker thread until the main thread
3084 // catches up.
3085 size_t queuedEvents = mMainThreadEventTargetForMessaging->Length() +
3086 mMainThreadDebuggeeEventTarget->Length();
3087 if (queuedEvents > 5000) {
3088 // Note, postMessage uses mMainThreadDebuggeeEventTarget!
3089 mMainThreadDebuggeeEventTarget->AwaitIdle();
3090 }
3091 }
3092
3093 MOZ_CRASH("Shouldn't get here!");
3094 }
3095
3096 namespace {
3097 /**
3098 * If there is a current CycleCollectedJSContext, return its recursion depth,
3099 * otherwise return 1.
3100 *
3101 * In the edge case where a worker is starting up so late that PBackground is
3102 * already shutting down, the cycle collected context will never be created,
3103 * but we will need to drain the event loop in ClearMainEventQueue. This will
3104 * result in a normal NS_ProcessPendingEvents invocation which will call
3105 * WorkerPrivate::OnProcessNextEvent and WorkerPrivate::AfterProcessNextEvent
3106 * which want to handle the need to process control runnables and perform a
3107 * sanity check assertion, respectively.
3108 *
3109 * We claim a depth of 1 when there's no CCJS because this most corresponds to
3110 * reality, but this doesn't meant that other code might want to drain various
3111 * runnable queues as part of this cleanup.
3112 */
GetEffectiveEventLoopRecursionDepth()3113 uint32_t GetEffectiveEventLoopRecursionDepth() {
3114 auto ccjs = CycleCollectedJSContext::Get();
3115 if (ccjs) {
3116 return ccjs->RecursionDepth();
3117 }
3118
3119 return 1;
3120 }
3121
3122 } // namespace
3123
OnProcessNextEvent()3124 void WorkerPrivate::OnProcessNextEvent() {
3125 AssertIsOnWorkerThread();
3126
3127 uint32_t recursionDepth = GetEffectiveEventLoopRecursionDepth();
3128 MOZ_ASSERT(recursionDepth);
3129
3130 // Normally we process control runnables in DoRunLoop or RunCurrentSyncLoop.
3131 // However, it's possible that non-worker C++ could spin its own nested event
3132 // loop, and in that case we must ensure that we continue to process control
3133 // runnables here.
3134 if (recursionDepth > 1 && mSyncLoopStack.Length() < recursionDepth - 1) {
3135 Unused << ProcessAllControlRunnables();
3136 // There's no running JS, and no state to revalidate, so we can ignore the
3137 // return value.
3138 }
3139 }
3140
AfterProcessNextEvent()3141 void WorkerPrivate::AfterProcessNextEvent() {
3142 AssertIsOnWorkerThread();
3143 MOZ_ASSERT(GetEffectiveEventLoopRecursionDepth());
3144 }
3145
MainThreadEventTargetForMessaging()3146 nsIEventTarget* WorkerPrivate::MainThreadEventTargetForMessaging() {
3147 return mMainThreadEventTargetForMessaging;
3148 }
3149
DispatchToMainThreadForMessaging(nsIRunnable * aRunnable,uint32_t aFlags)3150 nsresult WorkerPrivate::DispatchToMainThreadForMessaging(nsIRunnable* aRunnable,
3151 uint32_t aFlags) {
3152 nsCOMPtr<nsIRunnable> r = aRunnable;
3153 return DispatchToMainThreadForMessaging(r.forget(), aFlags);
3154 }
3155
DispatchToMainThreadForMessaging(already_AddRefed<nsIRunnable> aRunnable,uint32_t aFlags)3156 nsresult WorkerPrivate::DispatchToMainThreadForMessaging(
3157 already_AddRefed<nsIRunnable> aRunnable, uint32_t aFlags) {
3158 return mMainThreadEventTargetForMessaging->Dispatch(std::move(aRunnable),
3159 aFlags);
3160 }
3161
MainThreadEventTarget()3162 nsIEventTarget* WorkerPrivate::MainThreadEventTarget() {
3163 return mMainThreadEventTarget;
3164 }
3165
DispatchToMainThread(nsIRunnable * aRunnable,uint32_t aFlags)3166 nsresult WorkerPrivate::DispatchToMainThread(nsIRunnable* aRunnable,
3167 uint32_t aFlags) {
3168 nsCOMPtr<nsIRunnable> r = aRunnable;
3169 return DispatchToMainThread(r.forget(), aFlags);
3170 }
3171
DispatchToMainThread(already_AddRefed<nsIRunnable> aRunnable,uint32_t aFlags)3172 nsresult WorkerPrivate::DispatchToMainThread(
3173 already_AddRefed<nsIRunnable> aRunnable, uint32_t aFlags) {
3174 return mMainThreadEventTarget->Dispatch(std::move(aRunnable), aFlags);
3175 }
3176
DispatchDebuggeeToMainThread(already_AddRefed<WorkerDebuggeeRunnable> aRunnable,uint32_t aFlags)3177 nsresult WorkerPrivate::DispatchDebuggeeToMainThread(
3178 already_AddRefed<WorkerDebuggeeRunnable> aRunnable, uint32_t aFlags) {
3179 return mMainThreadDebuggeeEventTarget->Dispatch(std::move(aRunnable), aFlags);
3180 }
3181
ControlEventTarget()3182 nsISerialEventTarget* WorkerPrivate::ControlEventTarget() {
3183 return mWorkerControlEventTarget;
3184 }
3185
HybridEventTarget()3186 nsISerialEventTarget* WorkerPrivate::HybridEventTarget() {
3187 return mWorkerHybridEventTarget;
3188 }
3189
GetClientType() const3190 ClientType WorkerPrivate::GetClientType() const {
3191 switch (Kind()) {
3192 case WorkerKindDedicated:
3193 return ClientType::Worker;
3194 case WorkerKindShared:
3195 return ClientType::Sharedworker;
3196 case WorkerKindService:
3197 return ClientType::Serviceworker;
3198 default:
3199 MOZ_CRASH("unknown worker type!");
3200 }
3201 }
3202
CreateClientSource()3203 UniquePtr<ClientSource> WorkerPrivate::CreateClientSource() {
3204 auto data = mWorkerThreadAccessible.Access();
3205 MOZ_ASSERT(!data->mScope, "Client should be created before the global");
3206
3207 auto clientSource = ClientManager::CreateSource(
3208 GetClientType(), mWorkerHybridEventTarget, GetPrincipalInfo());
3209 MOZ_DIAGNOSTIC_ASSERT(clientSource);
3210
3211 clientSource->SetAgentClusterId(mAgentClusterId);
3212
3213 if (data->mFrozen) {
3214 clientSource->Freeze();
3215 }
3216
3217 // Shortly after the client is reserved we will try loading the main script
3218 // for the worker. This may get intercepted by the ServiceWorkerManager
3219 // which will then try to create a ClientHandle. Its actually possible for
3220 // the main thread to create this ClientHandle before our IPC message creating
3221 // the ClientSource completes. To avoid this race we synchronously ping our
3222 // parent Client actor here. This ensure the worker ClientSource is created
3223 // in the parent before the main thread might try reaching it with a
3224 // ClientHandle.
3225 //
3226 // An alternative solution would have been to handle the out-of-order
3227 // operations on the parent side. We could have created a small window where
3228 // we allow ClientHandle objects to exist without a ClientSource. We would
3229 // then time out these handles if they stayed orphaned for too long. This
3230 // approach would be much more complex, but also avoid this extra bit of
3231 // latency when starting workers.
3232 //
3233 // Note, we only have to do this for workers that can be controlled by a
3234 // service worker. So avoid the sync overhead here if we are starting a
3235 // service worker or a chrome worker.
3236 if (Kind() != WorkerKindService && !IsChromeWorker()) {
3237 clientSource->WorkerSyncPing(this);
3238 }
3239
3240 return clientSource;
3241 }
3242
EnsureCSPEventListener()3243 bool WorkerPrivate::EnsureCSPEventListener() {
3244 if (!mCSPEventListener) {
3245 mCSPEventListener = WorkerCSPEventListener::Create(this);
3246 if (NS_WARN_IF(!mCSPEventListener)) {
3247 return false;
3248 }
3249 }
3250 return true;
3251 }
3252
CSPEventListener() const3253 nsICSPEventListener* WorkerPrivate::CSPEventListener() const {
3254 MOZ_ASSERT(mCSPEventListener);
3255 return mCSPEventListener;
3256 }
3257
EnsurePerformanceStorage()3258 void WorkerPrivate::EnsurePerformanceStorage() {
3259 AssertIsOnWorkerThread();
3260
3261 if (!mPerformanceStorage) {
3262 mPerformanceStorage = PerformanceStorageWorker::Create(this);
3263 }
3264 }
3265
GetExecutionGranted() const3266 bool WorkerPrivate::GetExecutionGranted() const {
3267 auto data = mWorkerThreadAccessible.Access();
3268 return data->mJSThreadExecutionGranted;
3269 }
3270
SetExecutionGranted(bool aGranted)3271 void WorkerPrivate::SetExecutionGranted(bool aGranted) {
3272 auto data = mWorkerThreadAccessible.Access();
3273 data->mJSThreadExecutionGranted = aGranted;
3274 }
3275
ScheduleTimeSliceExpiration(uint32_t aDelay)3276 void WorkerPrivate::ScheduleTimeSliceExpiration(uint32_t aDelay) {
3277 auto data = mWorkerThreadAccessible.Access();
3278
3279 if (!data->mTSTimer) {
3280 data->mTSTimer = NS_NewTimer();
3281 MOZ_ALWAYS_SUCCEEDS(data->mTSTimer->SetTarget(mWorkerControlEventTarget));
3282 }
3283
3284 // Whenever an event is scheduled on the WorkerControlEventTarget an
3285 // interrupt is automatically requested which causes us to yield JS execution
3286 // and the next JS execution in the queue to execute.
3287 // This allows for simple code reuse of the existing interrupt callback code
3288 // used for control events.
3289 MOZ_ALWAYS_SUCCEEDS(data->mTSTimer->InitWithNamedFuncCallback(
3290 [](nsITimer* Timer, void* aClosure) { return; }, nullptr, aDelay,
3291 nsITimer::TYPE_ONE_SHOT, "TimeSliceExpirationTimer"));
3292 }
3293
CancelTimeSliceExpiration()3294 void WorkerPrivate::CancelTimeSliceExpiration() {
3295 auto data = mWorkerThreadAccessible.Access();
3296 MOZ_ALWAYS_SUCCEEDS(data->mTSTimer->Cancel());
3297 }
3298
GetExecutionManager() const3299 JSExecutionManager* WorkerPrivate::GetExecutionManager() const {
3300 auto data = mWorkerThreadAccessible.Access();
3301 return data->mExecutionManager.get();
3302 }
3303
SetExecutionManager(JSExecutionManager * aManager)3304 void WorkerPrivate::SetExecutionManager(JSExecutionManager* aManager) {
3305 auto data = mWorkerThreadAccessible.Access();
3306 data->mExecutionManager = aManager;
3307 }
3308
ExecutionReady()3309 void WorkerPrivate::ExecutionReady() {
3310 auto data = mWorkerThreadAccessible.Access();
3311 {
3312 MutexAutoLock lock(mMutex);
3313 if (mStatus >= Canceling) {
3314 return;
3315 }
3316 }
3317
3318 data->mScope->MutableClientSourceRef().WorkerExecutionReady(this);
3319 }
3320
InitializeGCTimers()3321 void WorkerPrivate::InitializeGCTimers() {
3322 auto data = mWorkerThreadAccessible.Access();
3323
3324 // We need a timer for GC. The basic plan is to run a non-shrinking GC
3325 // periodically (PERIODIC_GC_TIMER_DELAY_SEC) while the worker is running.
3326 // Once the worker goes idle we set a short (IDLE_GC_TIMER_DELAY_SEC) timer to
3327 // run a shrinking GC. If the worker receives more messages then the short
3328 // timer is canceled and the periodic timer resumes.
3329 data->mGCTimer = NS_NewTimer();
3330 MOZ_ASSERT(data->mGCTimer);
3331
3332 data->mPeriodicGCTimerRunning = false;
3333 data->mIdleGCTimerRunning = false;
3334 }
3335
SetGCTimerMode(GCTimerMode aMode)3336 void WorkerPrivate::SetGCTimerMode(GCTimerMode aMode) {
3337 auto data = mWorkerThreadAccessible.Access();
3338 MOZ_ASSERT(data->mGCTimer);
3339
3340 if ((aMode == PeriodicTimer && data->mPeriodicGCTimerRunning) ||
3341 (aMode == IdleTimer && data->mIdleGCTimerRunning)) {
3342 return;
3343 }
3344
3345 MOZ_ALWAYS_SUCCEEDS(data->mGCTimer->Cancel());
3346
3347 data->mPeriodicGCTimerRunning = false;
3348 data->mIdleGCTimerRunning = false;
3349 LOG(WorkerLog(), ("Worker %p canceled GC timer because %s\n", this,
3350 aMode == PeriodicTimer ? "periodic"
3351 : aMode == IdleTimer ? "idle"
3352 : "none"));
3353
3354 if (aMode == NoTimer) {
3355 return;
3356 }
3357
3358 MOZ_ASSERT(aMode == PeriodicTimer || aMode == IdleTimer);
3359
3360 uint32_t delay = 0;
3361 int16_t type = nsITimer::TYPE_ONE_SHOT;
3362 nsTimerCallbackFunc callback = nullptr;
3363 const char* name = nullptr;
3364
3365 if (aMode == PeriodicTimer) {
3366 delay = PERIODIC_GC_TIMER_DELAY_SEC * 1000;
3367 type = nsITimer::TYPE_REPEATING_SLACK;
3368 callback = PeriodicGCTimerCallback;
3369 name = "dom::PeriodicGCTimerCallback";
3370 } else {
3371 delay = IDLE_GC_TIMER_DELAY_SEC * 1000;
3372 type = nsITimer::TYPE_ONE_SHOT;
3373 callback = IdleGCTimerCallback;
3374 name = "dom::IdleGCTimerCallback";
3375 }
3376
3377 MOZ_ALWAYS_SUCCEEDS(data->mGCTimer->SetTarget(mWorkerControlEventTarget));
3378 MOZ_ALWAYS_SUCCEEDS(data->mGCTimer->InitWithNamedFuncCallback(
3379 callback, this, delay, type, name));
3380
3381 if (aMode == PeriodicTimer) {
3382 LOG(WorkerLog(), ("Worker %p scheduled periodic GC timer\n", this));
3383 data->mPeriodicGCTimerRunning = true;
3384 } else {
3385 LOG(WorkerLog(), ("Worker %p scheduled idle GC timer\n", this));
3386 data->mIdleGCTimerRunning = true;
3387 }
3388 }
3389
ShutdownGCTimers()3390 void WorkerPrivate::ShutdownGCTimers() {
3391 auto data = mWorkerThreadAccessible.Access();
3392
3393 MOZ_ASSERT(data->mGCTimer);
3394
3395 // Always make sure the timer is canceled.
3396 MOZ_ALWAYS_SUCCEEDS(data->mGCTimer->Cancel());
3397
3398 LOG(WorkerLog(), ("Worker %p killed the GC timer\n", this));
3399
3400 data->mGCTimer = nullptr;
3401 data->mPeriodicGCTimerRunning = false;
3402 data->mIdleGCTimerRunning = false;
3403 }
3404
InterruptCallback(JSContext * aCx)3405 bool WorkerPrivate::InterruptCallback(JSContext* aCx) {
3406 auto data = mWorkerThreadAccessible.Access();
3407
3408 AutoYieldJSThreadExecution yield;
3409
3410 // If we are here it's because a WorkerControlRunnable has been dispatched.
3411 // The runnable could be processed here or it could have already been
3412 // processed by a sync event loop.
3413 // The most important thing this method must do, is to decide if the JS
3414 // execution should continue or not. If the runnable returns an error or if
3415 // the worker status is >= Canceling, we should stop the JS execution.
3416
3417 MOZ_ASSERT(!JS_IsExceptionPending(aCx));
3418
3419 bool mayContinue = true;
3420 bool scheduledIdleGC = false;
3421
3422 for (;;) {
3423 // Run all control events now.
3424 auto result = ProcessAllControlRunnables();
3425 if (result == ProcessAllControlRunnablesResult::Abort) {
3426 mayContinue = false;
3427 }
3428
3429 bool mayFreeze = data->mFrozen;
3430
3431 {
3432 MutexAutoLock lock(mMutex);
3433
3434 if (mayFreeze) {
3435 mayFreeze = mStatus <= Running;
3436 }
3437
3438 if (mStatus >= Canceling) {
3439 mayContinue = false;
3440 }
3441 }
3442
3443 if (!mayContinue || !mayFreeze) {
3444 break;
3445 }
3446
3447 // Cancel the periodic GC timer here before freezing. The idle GC timer
3448 // will clean everything up once it runs.
3449 if (!scheduledIdleGC) {
3450 SetGCTimerMode(IdleTimer);
3451 scheduledIdleGC = true;
3452 }
3453
3454 while ((mayContinue = MayContinueRunning())) {
3455 MutexAutoLock lock(mMutex);
3456 if (!mControlQueue.IsEmpty()) {
3457 break;
3458 }
3459
3460 WaitForWorkerEvents();
3461 }
3462 }
3463
3464 if (!mayContinue) {
3465 // We want only uncatchable exceptions here.
3466 NS_ASSERTION(!JS_IsExceptionPending(aCx),
3467 "Should not have an exception set here!");
3468 return false;
3469 }
3470
3471 // Make sure the periodic timer gets turned back on here.
3472 SetGCTimerMode(PeriodicTimer);
3473
3474 return true;
3475 }
3476
CloseInternal()3477 void WorkerPrivate::CloseInternal() {
3478 AssertIsOnWorkerThread();
3479 NotifyInternal(Closing);
3480 }
3481
IsOnCurrentThread()3482 bool WorkerPrivate::IsOnCurrentThread() {
3483 // May be called on any thread!
3484
3485 MOZ_ASSERT(mPRThread);
3486 return PR_GetCurrentThread() == mPRThread;
3487 }
3488
ScheduleDeletion(WorkerRanOrNot aRanOrNot)3489 void WorkerPrivate::ScheduleDeletion(WorkerRanOrNot aRanOrNot) {
3490 AssertIsOnWorkerThread();
3491 {
3492 // mWorkerThreadAccessible's accessor must be destructed before
3493 // the scheduled Runnable gets to run.
3494 auto data = mWorkerThreadAccessible.Access();
3495 MOZ_ASSERT(data->mChildWorkers.IsEmpty());
3496
3497 MOZ_RELEASE_ASSERT(!data->mDeletionScheduled);
3498 data->mDeletionScheduled.Flip();
3499 }
3500 MOZ_ASSERT(mSyncLoopStack.IsEmpty());
3501 MOZ_ASSERT(mPostSyncLoopOperations == 0);
3502
3503 ClearMainEventQueue(aRanOrNot);
3504 #ifdef DEBUG
3505 if (WorkerRan == aRanOrNot) {
3506 nsIThread* currentThread = NS_GetCurrentThread();
3507 MOZ_ASSERT(currentThread);
3508 MOZ_ASSERT(!NS_HasPendingEvents(currentThread));
3509 }
3510 #endif
3511
3512 if (WorkerPrivate* parent = GetParent()) {
3513 RefPtr<WorkerFinishedRunnable> runnable =
3514 new WorkerFinishedRunnable(parent, this);
3515 if (!runnable->Dispatch()) {
3516 NS_WARNING("Failed to dispatch runnable!");
3517 }
3518 } else {
3519 // Note, this uses the lower priority DispatchToMainThreadForMessaging for
3520 // dispatching TopLevelWorkerFinishedRunnable to the main thread so that
3521 // other relevant runnables are guaranteed to run before it.
3522 RefPtr<TopLevelWorkerFinishedRunnable> runnable =
3523 new TopLevelWorkerFinishedRunnable(this);
3524 if (NS_FAILED(DispatchToMainThreadForMessaging(runnable.forget()))) {
3525 NS_WARNING("Failed to dispatch runnable!");
3526 }
3527 }
3528 }
3529
CollectRuntimeStats(JS::RuntimeStats * aRtStats,bool aAnonymize)3530 bool WorkerPrivate::CollectRuntimeStats(JS::RuntimeStats* aRtStats,
3531 bool aAnonymize) {
3532 AssertIsOnWorkerThread();
3533 NS_ASSERTION(aRtStats, "Null RuntimeStats!");
3534 NS_ASSERTION(mJSContext, "This must never be null!");
3535
3536 return JS::CollectRuntimeStats(mJSContext, aRtStats, nullptr, aAnonymize);
3537 }
3538
EnableMemoryReporter()3539 void WorkerPrivate::EnableMemoryReporter() {
3540 auto data = mWorkerThreadAccessible.Access();
3541 MOZ_ASSERT(!data->mMemoryReporter);
3542
3543 // No need to lock here since the main thread can't race until we've
3544 // successfully registered the reporter.
3545 data->mMemoryReporter = new MemoryReporter(this);
3546
3547 if (NS_FAILED(RegisterWeakAsyncMemoryReporter(data->mMemoryReporter))) {
3548 NS_WARNING("Failed to register memory reporter!");
3549 // No need to lock here since a failed registration means our memory
3550 // reporter can't start running. Just clean up.
3551 data->mMemoryReporter = nullptr;
3552 }
3553 }
3554
DisableMemoryReporter()3555 void WorkerPrivate::DisableMemoryReporter() {
3556 auto data = mWorkerThreadAccessible.Access();
3557
3558 RefPtr<MemoryReporter> memoryReporter;
3559 {
3560 // Mutex protectes MemoryReporter::mWorkerPrivate which is cleared by
3561 // MemoryReporter::Disable() below.
3562 MutexAutoLock lock(mMutex);
3563
3564 // There is nothing to do here if the memory reporter was never successfully
3565 // registered.
3566 if (!data->mMemoryReporter) {
3567 return;
3568 }
3569
3570 // We don't need this set any longer. Swap it out so that we can unregister
3571 // below.
3572 data->mMemoryReporter.swap(memoryReporter);
3573
3574 // Next disable the memory reporter so that the main thread stops trying to
3575 // signal us.
3576 memoryReporter->Disable();
3577 }
3578
3579 // Finally unregister the memory reporter.
3580 if (NS_FAILED(UnregisterWeakMemoryReporter(memoryReporter))) {
3581 NS_WARNING("Failed to unregister memory reporter!");
3582 }
3583 }
3584
WaitForWorkerEvents()3585 void WorkerPrivate::WaitForWorkerEvents() {
3586 AUTO_PROFILER_LABEL("WorkerPrivate::WaitForWorkerEvents", IDLE);
3587
3588 AssertIsOnWorkerThread();
3589 mMutex.AssertCurrentThreadOwns();
3590
3591 // Wait for a worker event.
3592 mCondVar.Wait();
3593 }
3594
3595 WorkerPrivate::ProcessAllControlRunnablesResult
ProcessAllControlRunnablesLocked()3596 WorkerPrivate::ProcessAllControlRunnablesLocked() {
3597 AssertIsOnWorkerThread();
3598 mMutex.AssertCurrentThreadOwns();
3599
3600 AutoYieldJSThreadExecution yield;
3601
3602 auto result = ProcessAllControlRunnablesResult::Nothing;
3603
3604 for (;;) {
3605 WorkerControlRunnable* event;
3606 if (!mControlQueue.Pop(event)) {
3607 break;
3608 }
3609
3610 MutexAutoUnlock unlock(mMutex);
3611
3612 MOZ_ASSERT(event);
3613 if (NS_FAILED(static_cast<nsIRunnable*>(event)->Run())) {
3614 result = ProcessAllControlRunnablesResult::Abort;
3615 }
3616
3617 if (result == ProcessAllControlRunnablesResult::Nothing) {
3618 // We ran at least one thing.
3619 result = ProcessAllControlRunnablesResult::MayContinue;
3620 }
3621 event->Release();
3622 }
3623
3624 return result;
3625 }
3626
ClearMainEventQueue(WorkerRanOrNot aRanOrNot)3627 void WorkerPrivate::ClearMainEventQueue(WorkerRanOrNot aRanOrNot) {
3628 AssertIsOnWorkerThread();
3629
3630 MOZ_ASSERT((mPostSyncLoopOperations & ePendingEventQueueClearing)
3631 ? (mSyncLoopStack.Length() == 1)
3632 : mSyncLoopStack.IsEmpty());
3633 MOZ_ASSERT(!mCancelAllPendingRunnables);
3634
3635 mCancelAllPendingRunnables = true;
3636 WorkerGlobalScope* globalScope = GlobalScope();
3637 if (globalScope) {
3638 // It's appropriate to disconnect event targets at the point that it's no
3639 // longer possible for new tasks to be dispatched at the global, and this is
3640 // that point.
3641 globalScope->DisconnectEventTargetObjects();
3642
3643 globalScope->WorkerPrivateSaysForbidScript();
3644 }
3645
3646 if (WorkerNeverRan == aRanOrNot) {
3647 for (uint32_t count = mPreStartRunnables.Length(), index = 0; index < count;
3648 index++) {
3649 RefPtr<WorkerRunnable> runnable = std::move(mPreStartRunnables[index]);
3650 static_cast<nsIRunnable*>(runnable.get())->Run();
3651 }
3652 } else {
3653 nsIThread* currentThread = NS_GetCurrentThread();
3654 MOZ_ASSERT(currentThread);
3655
3656 NS_ProcessPendingEvents(currentThread);
3657 }
3658
3659 if (globalScope) {
3660 globalScope->WorkerPrivateSaysAllowScript();
3661 }
3662 MOZ_ASSERT(mCancelAllPendingRunnables);
3663 mCancelAllPendingRunnables = false;
3664 }
3665
ClearDebuggerEventQueue()3666 void WorkerPrivate::ClearDebuggerEventQueue() {
3667 while (!mDebuggerQueue.IsEmpty()) {
3668 WorkerRunnable* runnable = nullptr;
3669 mDebuggerQueue.Pop(runnable);
3670 // It should be ok to simply release the runnable, without running it.
3671 runnable->Release();
3672 }
3673 }
3674
FreezeInternal()3675 bool WorkerPrivate::FreezeInternal() {
3676 auto data = mWorkerThreadAccessible.Access();
3677 NS_ASSERTION(!data->mFrozen, "Already frozen!");
3678
3679 AutoYieldJSThreadExecution yield;
3680
3681 // The worker can freeze even if it failed to run (and doesn't have a global).
3682 if (data->mScope) {
3683 data->mScope->MutableClientSourceRef().Freeze();
3684 }
3685
3686 data->mFrozen = true;
3687
3688 for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
3689 data->mChildWorkers[index]->Freeze(nullptr);
3690 }
3691
3692 return true;
3693 }
3694
ThawInternal()3695 bool WorkerPrivate::ThawInternal() {
3696 auto data = mWorkerThreadAccessible.Access();
3697 NS_ASSERTION(data->mFrozen, "Not yet frozen!");
3698
3699 for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
3700 data->mChildWorkers[index]->Thaw(nullptr);
3701 }
3702
3703 data->mFrozen = false;
3704
3705 // The worker can thaw even if it failed to run (and doesn't have a global).
3706 if (data->mScope) {
3707 data->mScope->MutableClientSourceRef().Thaw();
3708 }
3709
3710 return true;
3711 }
3712
PropagateStorageAccessPermissionGrantedInternal()3713 void WorkerPrivate::PropagateStorageAccessPermissionGrantedInternal() {
3714 auto data = mWorkerThreadAccessible.Access();
3715
3716 mLoadInfo.mUseRegularPrincipal = true;
3717 mLoadInfo.mHasStorageAccessPermissionGranted = true;
3718
3719 WorkerGlobalScope* globalScope = GlobalScope();
3720 if (globalScope) {
3721 globalScope->StorageAccessPermissionGranted();
3722 }
3723
3724 for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
3725 data->mChildWorkers[index]->PropagateStorageAccessPermissionGranted();
3726 }
3727 }
3728
TraverseTimeouts(nsCycleCollectionTraversalCallback & cb)3729 void WorkerPrivate::TraverseTimeouts(nsCycleCollectionTraversalCallback& cb) {
3730 auto data = mWorkerThreadAccessible.Access();
3731 for (uint32_t i = 0; i < data->mTimeouts.Length(); ++i) {
3732 // TODO(erahm): No idea what's going on here.
3733 TimeoutInfo* tmp = data->mTimeouts[i].get();
3734 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHandler)
3735 }
3736 }
3737
UnlinkTimeouts()3738 void WorkerPrivate::UnlinkTimeouts() {
3739 auto data = mWorkerThreadAccessible.Access();
3740 data->mTimeouts.Clear();
3741 }
3742
ModifyBusyCountFromWorker(bool aIncrease)3743 bool WorkerPrivate::ModifyBusyCountFromWorker(bool aIncrease) {
3744 AssertIsOnWorkerThread();
3745
3746 {
3747 MutexAutoLock lock(mMutex);
3748
3749 // If we're in shutdown then the busy count is no longer being considered so
3750 // just return now.
3751 if (mStatus >= Killing) {
3752 return true;
3753 }
3754 }
3755
3756 RefPtr<ModifyBusyCountRunnable> runnable =
3757 new ModifyBusyCountRunnable(this, aIncrease);
3758 return runnable->Dispatch();
3759 }
3760
AddChildWorker(WorkerPrivate * aChildWorker)3761 bool WorkerPrivate::AddChildWorker(WorkerPrivate* aChildWorker) {
3762 auto data = mWorkerThreadAccessible.Access();
3763
3764 #ifdef DEBUG
3765 {
3766 WorkerStatus currentStatus;
3767 {
3768 MutexAutoLock lock(mMutex);
3769 currentStatus = mStatus;
3770 }
3771
3772 MOZ_ASSERT(currentStatus == Running);
3773 }
3774 #endif
3775
3776 NS_ASSERTION(!data->mChildWorkers.Contains(aChildWorker),
3777 "Already know about this one!");
3778 data->mChildWorkers.AppendElement(aChildWorker);
3779
3780 return data->mChildWorkers.Length() == 1 ? ModifyBusyCountFromWorker(true)
3781 : true;
3782 }
3783
RemoveChildWorker(WorkerPrivate * aChildWorker)3784 void WorkerPrivate::RemoveChildWorker(WorkerPrivate* aChildWorker) {
3785 auto data = mWorkerThreadAccessible.Access();
3786
3787 NS_ASSERTION(data->mChildWorkers.Contains(aChildWorker),
3788 "Didn't know about this one!");
3789 data->mChildWorkers.RemoveElement(aChildWorker);
3790
3791 if (data->mChildWorkers.IsEmpty() && !ModifyBusyCountFromWorker(false)) {
3792 NS_WARNING("Failed to modify busy count!");
3793 }
3794 }
3795
AddWorkerRef(WorkerRef * aWorkerRef,WorkerStatus aFailStatus)3796 bool WorkerPrivate::AddWorkerRef(WorkerRef* aWorkerRef,
3797 WorkerStatus aFailStatus) {
3798 MOZ_ASSERT(aWorkerRef);
3799 auto data = mWorkerThreadAccessible.Access();
3800
3801 {
3802 MutexAutoLock lock(mMutex);
3803
3804 if (mStatus >= aFailStatus) {
3805 return false;
3806 }
3807
3808 // We shouldn't create strong references to workers before their main loop
3809 // begins running. Strong references must be disposed of on the worker
3810 // thread, so strong references from other threads use a control runnable
3811 // for that purpose. If the worker fails to reach the main loop stage then
3812 // no control runnables get run and it would be impossible to get rid of the
3813 // reference properly.
3814 MOZ_DIAGNOSTIC_ASSERT_IF(aWorkerRef->IsPreventingShutdown(),
3815 mStatus >= WorkerStatus::Running);
3816 }
3817
3818 MOZ_ASSERT(!data->mWorkerRefs.Contains(aWorkerRef),
3819 "Already know about this one!");
3820
3821 if (aWorkerRef->IsPreventingShutdown()) {
3822 if (!data->mNumWorkerRefsPreventingShutdownStart &&
3823 !ModifyBusyCountFromWorker(true)) {
3824 return false;
3825 }
3826 data->mNumWorkerRefsPreventingShutdownStart += 1;
3827 }
3828
3829 data->mWorkerRefs.AppendElement(aWorkerRef);
3830 return true;
3831 }
3832
RemoveWorkerRef(WorkerRef * aWorkerRef)3833 void WorkerPrivate::RemoveWorkerRef(WorkerRef* aWorkerRef) {
3834 MOZ_ASSERT(aWorkerRef);
3835 auto data = mWorkerThreadAccessible.Access();
3836
3837 MOZ_ASSERT(data->mWorkerRefs.Contains(aWorkerRef),
3838 "Didn't know about this one!");
3839 data->mWorkerRefs.RemoveElement(aWorkerRef);
3840
3841 if (aWorkerRef->IsPreventingShutdown()) {
3842 data->mNumWorkerRefsPreventingShutdownStart -= 1;
3843 if (!data->mNumWorkerRefsPreventingShutdownStart &&
3844 !ModifyBusyCountFromWorker(false)) {
3845 NS_WARNING("Failed to modify busy count!");
3846 }
3847 }
3848 }
3849
NotifyWorkerRefs(WorkerStatus aStatus)3850 void WorkerPrivate::NotifyWorkerRefs(WorkerStatus aStatus) {
3851 auto data = mWorkerThreadAccessible.Access();
3852
3853 NS_ASSERTION(aStatus > Closing, "Bad status!");
3854
3855 for (auto* workerRef : data->mWorkerRefs.ForwardRange()) {
3856 workerRef->Notify();
3857 }
3858
3859 AutoTArray<WorkerPrivate*, 10> children;
3860 children.AppendElements(data->mChildWorkers);
3861
3862 for (uint32_t index = 0; index < children.Length(); index++) {
3863 if (!children[index]->Notify(aStatus)) {
3864 NS_WARNING("Failed to notify child worker!");
3865 }
3866 }
3867 }
3868
CancelAllTimeouts()3869 void WorkerPrivate::CancelAllTimeouts() {
3870 auto data = mWorkerThreadAccessible.Access();
3871
3872 LOG(TimeoutsLog(), ("Worker %p CancelAllTimeouts.\n", this));
3873
3874 if (data->mTimerRunning) {
3875 NS_ASSERTION(data->mTimer && data->mTimerRunnable, "Huh?!");
3876 NS_ASSERTION(!data->mTimeouts.IsEmpty(), "Huh?!");
3877
3878 if (NS_FAILED(data->mTimer->Cancel())) {
3879 NS_WARNING("Failed to cancel timer!");
3880 }
3881
3882 for (uint32_t index = 0; index < data->mTimeouts.Length(); index++) {
3883 data->mTimeouts[index]->mCanceled = true;
3884 }
3885
3886 // If mRunningExpiredTimeouts, then the fact that they are all canceled now
3887 // means that the currently executing RunExpiredTimeouts will deal with
3888 // them. Otherwise, we need to clean them up ourselves.
3889 if (!data->mRunningExpiredTimeouts) {
3890 data->mTimeouts.Clear();
3891 ModifyBusyCountFromWorker(false);
3892 }
3893
3894 // Set mTimerRunning false even if mRunningExpiredTimeouts is true, so that
3895 // if we get reentered under this same RunExpiredTimeouts call we don't
3896 // assert above that !mTimeouts().IsEmpty(), because that's clearly false
3897 // now.
3898 data->mTimerRunning = false;
3899 }
3900 #ifdef DEBUG
3901 else if (!data->mRunningExpiredTimeouts) {
3902 NS_ASSERTION(data->mTimeouts.IsEmpty(), "Huh?!");
3903 }
3904 #endif
3905
3906 data->mTimer = nullptr;
3907 data->mTimerRunnable = nullptr;
3908 }
3909
CreateNewSyncLoop(WorkerStatus aFailStatus)3910 already_AddRefed<nsIEventTarget> WorkerPrivate::CreateNewSyncLoop(
3911 WorkerStatus aFailStatus) {
3912 AssertIsOnWorkerThread();
3913 MOZ_ASSERT(
3914 aFailStatus >= Canceling,
3915 "Sync loops can be created when the worker is in Running/Closing state!");
3916
3917 {
3918 MutexAutoLock lock(mMutex);
3919
3920 if (mStatus >= aFailStatus) {
3921 return nullptr;
3922 }
3923 }
3924
3925 auto queue = static_cast<ThreadEventQueue*>(mThread->EventQueue());
3926 nsCOMPtr<nsISerialEventTarget> realEventTarget = queue->PushEventQueue();
3927 MOZ_ASSERT(realEventTarget);
3928
3929 RefPtr<EventTarget> workerEventTarget =
3930 new EventTarget(this, realEventTarget);
3931
3932 {
3933 // Modifications must be protected by mMutex in DEBUG builds, see comment
3934 // about mSyncLoopStack in WorkerPrivate.h.
3935 #ifdef DEBUG
3936 MutexAutoLock lock(mMutex);
3937 #endif
3938
3939 mSyncLoopStack.AppendElement(new SyncLoopInfo(workerEventTarget));
3940 }
3941
3942 return workerEventTarget.forget();
3943 }
3944
RunCurrentSyncLoop()3945 bool WorkerPrivate::RunCurrentSyncLoop() {
3946 AssertIsOnWorkerThread();
3947
3948 JSContext* cx = GetJSContext();
3949 MOZ_ASSERT(cx);
3950
3951 AutoPushEventLoopGlobal eventLoopGlobal(this, cx);
3952
3953 // This should not change between now and the time we finish running this sync
3954 // loop.
3955 uint32_t currentLoopIndex = mSyncLoopStack.Length() - 1;
3956
3957 SyncLoopInfo* loopInfo = mSyncLoopStack[currentLoopIndex].get();
3958
3959 AutoYieldJSThreadExecution yield;
3960
3961 MOZ_ASSERT(loopInfo);
3962 MOZ_ASSERT(!loopInfo->mHasRun);
3963 MOZ_ASSERT(!loopInfo->mCompleted);
3964
3965 #ifdef DEBUG
3966 loopInfo->mHasRun = true;
3967 #endif
3968
3969 while (!loopInfo->mCompleted) {
3970 bool normalRunnablesPending = false;
3971
3972 // Don't block with the periodic GC timer running.
3973 if (!NS_HasPendingEvents(mThread)) {
3974 SetGCTimerMode(IdleTimer);
3975 }
3976
3977 // Wait for something to do.
3978 {
3979 MutexAutoLock lock(mMutex);
3980
3981 for (;;) {
3982 while (mControlQueue.IsEmpty() && !normalRunnablesPending &&
3983 !(normalRunnablesPending = NS_HasPendingEvents(mThread))) {
3984 WaitForWorkerEvents();
3985 }
3986
3987 auto result = ProcessAllControlRunnablesLocked();
3988 if (result != ProcessAllControlRunnablesResult::Nothing) {
3989 // The state of the world may have changed. Recheck it if we need to
3990 // continue.
3991 normalRunnablesPending =
3992 result == ProcessAllControlRunnablesResult::MayContinue &&
3993 NS_HasPendingEvents(mThread);
3994
3995 // NB: If we processed a NotifyRunnable, we might have run
3996 // non-control runnables, one of which may have shut down the
3997 // sync loop.
3998 if (loopInfo->mCompleted) {
3999 break;
4000 }
4001 }
4002
4003 // If we *didn't* run any control runnables, this should be unchanged.
4004 MOZ_ASSERT(!loopInfo->mCompleted);
4005
4006 if (normalRunnablesPending) {
4007 break;
4008 }
4009 }
4010 }
4011
4012 if (normalRunnablesPending) {
4013 // Make sure the periodic timer is running before we continue.
4014 SetGCTimerMode(PeriodicTimer);
4015
4016 MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(mThread, false));
4017
4018 // Now *might* be a good time to GC. Let the JS engine make the decision.
4019 if (GetCurrentEventLoopGlobal()) {
4020 // If GetCurrentEventLoopGlobal() is non-null, our JSContext is in a
4021 // Realm, so it's safe to try to GC.
4022 MOZ_ASSERT(JS::CurrentGlobalOrNull(cx));
4023 JS_MaybeGC(cx);
4024 }
4025 }
4026 }
4027
4028 // Make sure that the stack didn't change underneath us.
4029 MOZ_ASSERT(mSyncLoopStack[currentLoopIndex].get() == loopInfo);
4030
4031 return DestroySyncLoop(currentLoopIndex);
4032 }
4033
DestroySyncLoop(uint32_t aLoopIndex)4034 bool WorkerPrivate::DestroySyncLoop(uint32_t aLoopIndex) {
4035 MOZ_ASSERT(!mSyncLoopStack.IsEmpty());
4036 MOZ_ASSERT(mSyncLoopStack.Length() - 1 == aLoopIndex);
4037
4038 AutoYieldJSThreadExecution yield;
4039
4040 // We're about to delete the loop, stash its event target and result.
4041 const auto& loopInfo = mSyncLoopStack[aLoopIndex];
4042 nsIEventTarget* nestedEventTarget =
4043 loopInfo->mEventTarget->GetWeakNestedEventTarget();
4044 MOZ_ASSERT(nestedEventTarget);
4045
4046 bool result = loopInfo->mResult;
4047
4048 auto queue = static_cast<ThreadEventQueue*>(mThread->EventQueue());
4049 queue->PopEventQueue(nestedEventTarget);
4050
4051 // Are we making a 1 -> 0 transition here?
4052 if (mSyncLoopStack.Length() == 1) {
4053 if ((mPostSyncLoopOperations & ePendingEventQueueClearing)) {
4054 ClearMainEventQueue(WorkerRan);
4055 }
4056
4057 if ((mPostSyncLoopOperations & eDispatchCancelingRunnable)) {
4058 DispatchCancelingRunnable();
4059 }
4060
4061 mPostSyncLoopOperations = 0;
4062 }
4063
4064 {
4065 // Modifications must be protected by mMutex in DEBUG builds, see comment
4066 // about mSyncLoopStack in WorkerPrivate.h.
4067 #ifdef DEBUG
4068 MutexAutoLock lock(mMutex);
4069 #endif
4070
4071 // This will delete |loopInfo|!
4072 mSyncLoopStack.RemoveElementAt(aLoopIndex);
4073 }
4074
4075 return result;
4076 }
4077
DispatchCancelingRunnable()4078 void WorkerPrivate::DispatchCancelingRunnable() {
4079 // Here we use a normal runnable to know when the current JS chunk of code
4080 // is finished. We cannot use a WorkerRunnable because they are not
4081 // accepted any more by the worker, and we do not want to use a
4082 // WorkerControlRunnable because they are immediately executed.
4083 RefPtr<CancelingRunnable> r = new CancelingRunnable();
4084 mThread->nsThread::Dispatch(r.forget(), NS_DISPATCH_NORMAL);
4085
4086 // At the same time, we want to be sure that we interrupt infinite loops.
4087 // The following runnable starts a timer that cancel the worker, from the
4088 // parent thread, after CANCELING_TIMEOUT millseconds.
4089 RefPtr<CancelingWithTimeoutOnParentRunnable> rr =
4090 new CancelingWithTimeoutOnParentRunnable(this);
4091 rr->Dispatch();
4092 }
4093
ReportUseCounters()4094 void WorkerPrivate::ReportUseCounters() {
4095 AssertIsOnWorkerThread();
4096
4097 static const bool kDebugUseCounters = false;
4098
4099 if (mReportedUseCounters) {
4100 return;
4101 }
4102 mReportedUseCounters = true;
4103
4104 if (Telemetry::HistogramUseCounterWorkerCount <= 0 || IsChromeWorker()) {
4105 return;
4106 }
4107
4108 const size_t kind = Kind();
4109 switch (kind) {
4110 case WorkerKindDedicated:
4111 Telemetry::Accumulate(Telemetry::DEDICATED_WORKER_DESTROYED, 1);
4112 break;
4113 case WorkerKindShared:
4114 Telemetry::Accumulate(Telemetry::SHARED_WORKER_DESTROYED, 1);
4115 break;
4116 case WorkerKindService:
4117 Telemetry::Accumulate(Telemetry::SERVICE_WORKER_DESTROYED, 1);
4118 break;
4119 default:
4120 MOZ_ASSERT(false, "Unknown worker kind");
4121 return;
4122 }
4123
4124 if (kDebugUseCounters) {
4125 nsAutoCString path(Domain());
4126 path.AppendLiteral("(");
4127 NS_ConvertUTF16toUTF8 script(ScriptURL());
4128 path.Append(script);
4129 path.AppendPrintf(", 0x%p)", static_cast<void*>(this));
4130 printf("-- Worker use counters for %s --\n", path.get());
4131 }
4132
4133 static_assert(
4134 static_cast<size_t>(UseCounterWorker::Count) * 3 ==
4135 static_cast<size_t>(Telemetry::HistogramUseCounterWorkerCount),
4136 "There should be three histograms (dedicated and shared and "
4137 "servie) for each worker use counter");
4138 const size_t count = static_cast<size_t>(UseCounterWorker::Count);
4139 const size_t factor =
4140 static_cast<size_t>(Telemetry::HistogramUseCounterWorkerCount) / count;
4141 MOZ_ASSERT(factor > kind);
4142
4143 for (size_t c = 0; c < count; ++c) {
4144 // Histograms for worker use counters use the same order as the worker kinds
4145 // , so we can use the worker kind to index to corresponding histogram.
4146 Telemetry::HistogramID id = static_cast<Telemetry::HistogramID>(
4147 Telemetry::HistogramFirstUseCounterWorker + c * factor + kind);
4148 MOZ_ASSERT(id <= Telemetry::HistogramLastUseCounterWorker);
4149
4150 if (bool value = GetUseCounter(static_cast<UseCounterWorker>(c))) {
4151 Telemetry::Accumulate(id, 1);
4152
4153 if (kDebugUseCounters) {
4154 const char* name = Telemetry::GetHistogramName(id);
4155 printf(" %s #%d: %d\n", name, id, value);
4156 }
4157 }
4158 }
4159 }
4160
StopSyncLoop(nsIEventTarget * aSyncLoopTarget,bool aResult)4161 void WorkerPrivate::StopSyncLoop(nsIEventTarget* aSyncLoopTarget,
4162 bool aResult) {
4163 AssertIsOnWorkerThread();
4164 AssertValidSyncLoop(aSyncLoopTarget);
4165
4166 MOZ_ASSERT(!mSyncLoopStack.IsEmpty());
4167
4168 for (uint32_t index = mSyncLoopStack.Length(); index > 0; index--) {
4169 const auto& loopInfo = mSyncLoopStack[index - 1];
4170 MOZ_ASSERT(loopInfo);
4171 MOZ_ASSERT(loopInfo->mEventTarget);
4172
4173 if (loopInfo->mEventTarget == aSyncLoopTarget) {
4174 // Can't assert |loop->mHasRun| here because dispatch failures can cause
4175 // us to bail out early.
4176 MOZ_ASSERT(!loopInfo->mCompleted);
4177
4178 loopInfo->mResult = aResult;
4179 loopInfo->mCompleted = true;
4180
4181 loopInfo->mEventTarget->Disable();
4182
4183 return;
4184 }
4185
4186 MOZ_ASSERT(!SameCOMIdentity(loopInfo->mEventTarget, aSyncLoopTarget));
4187 }
4188
4189 MOZ_CRASH("Unknown sync loop!");
4190 }
4191
4192 #ifdef DEBUG
AssertValidSyncLoop(nsIEventTarget * aSyncLoopTarget)4193 void WorkerPrivate::AssertValidSyncLoop(nsIEventTarget* aSyncLoopTarget) {
4194 MOZ_ASSERT(aSyncLoopTarget);
4195
4196 EventTarget* workerTarget;
4197 nsresult rv = aSyncLoopTarget->QueryInterface(
4198 kDEBUGWorkerEventTargetIID, reinterpret_cast<void**>(&workerTarget));
4199 MOZ_ASSERT(NS_SUCCEEDED(rv));
4200 MOZ_ASSERT(workerTarget);
4201
4202 bool valid = false;
4203
4204 {
4205 MutexAutoLock lock(mMutex);
4206
4207 for (uint32_t index = 0; index < mSyncLoopStack.Length(); index++) {
4208 const auto& loopInfo = mSyncLoopStack[index];
4209 MOZ_ASSERT(loopInfo);
4210 MOZ_ASSERT(loopInfo->mEventTarget);
4211
4212 if (loopInfo->mEventTarget == aSyncLoopTarget) {
4213 valid = true;
4214 break;
4215 }
4216
4217 MOZ_ASSERT(!SameCOMIdentity(loopInfo->mEventTarget, aSyncLoopTarget));
4218 }
4219 }
4220
4221 MOZ_ASSERT(valid);
4222 }
4223 #endif
4224
PostMessageToParent(JSContext * aCx,JS::Handle<JS::Value> aMessage,const Sequence<JSObject * > & aTransferable,ErrorResult & aRv)4225 void WorkerPrivate::PostMessageToParent(
4226 JSContext* aCx, JS::Handle<JS::Value> aMessage,
4227 const Sequence<JSObject*>& aTransferable, ErrorResult& aRv) {
4228 AssertIsOnWorkerThread();
4229 MOZ_DIAGNOSTIC_ASSERT(IsDedicatedWorker());
4230
4231 JS::Rooted<JS::Value> transferable(aCx, JS::UndefinedValue());
4232
4233 aRv = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, aTransferable,
4234 &transferable);
4235 if (NS_WARN_IF(aRv.Failed())) {
4236 return;
4237 }
4238
4239 RefPtr<MessageEventRunnable> runnable = new MessageEventRunnable(
4240 this, WorkerRunnable::ParentThreadUnchangedBusyCount);
4241
4242 UniquePtr<AbstractTimelineMarker> start;
4243 UniquePtr<AbstractTimelineMarker> end;
4244 RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
4245 bool isTimelineRecording = timelines && !timelines->IsEmpty();
4246
4247 if (isTimelineRecording) {
4248 start = MakeUnique<WorkerTimelineMarker>(
4249 NS_IsMainThread()
4250 ? ProfileTimelineWorkerOperationType::SerializeDataOnMainThread
4251 : ProfileTimelineWorkerOperationType::SerializeDataOffMainThread,
4252 MarkerTracingType::START);
4253 }
4254
4255 JS::CloneDataPolicy clonePolicy;
4256
4257 // Parent and dedicated workers are always part of the same cluster.
4258 clonePolicy.allowIntraClusterClonableSharedObjects();
4259
4260 if (IsSharedMemoryAllowed()) {
4261 clonePolicy.allowSharedMemoryObjects();
4262 }
4263
4264 runnable->Write(aCx, aMessage, transferable, clonePolicy, aRv);
4265
4266 if (isTimelineRecording) {
4267 end = MakeUnique<WorkerTimelineMarker>(
4268 NS_IsMainThread()
4269 ? ProfileTimelineWorkerOperationType::SerializeDataOnMainThread
4270 : ProfileTimelineWorkerOperationType::SerializeDataOffMainThread,
4271 MarkerTracingType::END);
4272 timelines->AddMarkerForAllObservedDocShells(start);
4273 timelines->AddMarkerForAllObservedDocShells(end);
4274 }
4275
4276 if (NS_WARN_IF(aRv.Failed())) {
4277 return;
4278 }
4279
4280 if (!runnable->Dispatch()) {
4281 aRv = NS_ERROR_FAILURE;
4282 }
4283 }
4284
EnterDebuggerEventLoop()4285 void WorkerPrivate::EnterDebuggerEventLoop() {
4286 auto data = mWorkerThreadAccessible.Access();
4287
4288 JSContext* cx = GetJSContext();
4289 MOZ_ASSERT(cx);
4290
4291 AutoPushEventLoopGlobal eventLoopGlobal(this, cx);
4292 AutoYieldJSThreadExecution yield;
4293
4294 CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get();
4295
4296 uint32_t currentEventLoopLevel = ++data->mDebuggerEventLoopLevel;
4297
4298 while (currentEventLoopLevel <= data->mDebuggerEventLoopLevel) {
4299 bool debuggerRunnablesPending = false;
4300
4301 {
4302 MutexAutoLock lock(mMutex);
4303
4304 debuggerRunnablesPending = !mDebuggerQueue.IsEmpty();
4305 }
4306
4307 // Don't block with the periodic GC timer running.
4308 if (!debuggerRunnablesPending) {
4309 SetGCTimerMode(IdleTimer);
4310 }
4311
4312 // Wait for something to do
4313 {
4314 MutexAutoLock lock(mMutex);
4315
4316 std::deque<RefPtr<MicroTaskRunnable>>& debuggerMtQueue =
4317 ccjscx->GetDebuggerMicroTaskQueue();
4318 while (mControlQueue.IsEmpty() &&
4319 !(debuggerRunnablesPending = !mDebuggerQueue.IsEmpty()) &&
4320 debuggerMtQueue.empty()) {
4321 WaitForWorkerEvents();
4322 }
4323
4324 ProcessAllControlRunnablesLocked();
4325
4326 // XXXkhuey should we abort JS on the stack here if we got Abort above?
4327 }
4328 ccjscx->PerformDebuggerMicroTaskCheckpoint();
4329 if (debuggerRunnablesPending) {
4330 // Start the periodic GC timer if it is not already running.
4331 SetGCTimerMode(PeriodicTimer);
4332
4333 WorkerRunnable* runnable = nullptr;
4334
4335 {
4336 MutexAutoLock lock(mMutex);
4337
4338 mDebuggerQueue.Pop(runnable);
4339 }
4340
4341 MOZ_ASSERT(runnable);
4342 static_cast<nsIRunnable*>(runnable)->Run();
4343 runnable->Release();
4344
4345 ccjscx->PerformDebuggerMicroTaskCheckpoint();
4346
4347 // Now *might* be a good time to GC. Let the JS engine make the decision.
4348 if (GetCurrentEventLoopGlobal()) {
4349 // If GetCurrentEventLoopGlobal() is non-null, our JSContext is in a
4350 // Realm, so it's safe to try to GC.
4351 MOZ_ASSERT(JS::CurrentGlobalOrNull(cx));
4352 JS_MaybeGC(cx);
4353 }
4354 }
4355 }
4356 }
4357
LeaveDebuggerEventLoop()4358 void WorkerPrivate::LeaveDebuggerEventLoop() {
4359 auto data = mWorkerThreadAccessible.Access();
4360
4361 // TODO: Why lock the mutex if we're accessing data accessible to one thread
4362 // only?
4363 MutexAutoLock lock(mMutex);
4364
4365 if (data->mDebuggerEventLoopLevel > 0) {
4366 --data->mDebuggerEventLoopLevel;
4367 }
4368 }
4369
PostMessageToDebugger(const nsAString & aMessage)4370 void WorkerPrivate::PostMessageToDebugger(const nsAString& aMessage) {
4371 mDebugger->PostMessageToDebugger(aMessage);
4372 }
4373
SetDebuggerImmediate(dom::Function & aHandler,ErrorResult & aRv)4374 void WorkerPrivate::SetDebuggerImmediate(dom::Function& aHandler,
4375 ErrorResult& aRv) {
4376 AssertIsOnWorkerThread();
4377
4378 RefPtr<DebuggerImmediateRunnable> runnable =
4379 new DebuggerImmediateRunnable(this, aHandler);
4380 if (!runnable->Dispatch()) {
4381 aRv.Throw(NS_ERROR_FAILURE);
4382 }
4383 }
4384
ReportErrorToDebugger(const nsAString & aFilename,uint32_t aLineno,const nsAString & aMessage)4385 void WorkerPrivate::ReportErrorToDebugger(const nsAString& aFilename,
4386 uint32_t aLineno,
4387 const nsAString& aMessage) {
4388 mDebugger->ReportErrorToDebugger(aFilename, aLineno, aMessage);
4389 }
4390
NotifyInternal(WorkerStatus aStatus)4391 bool WorkerPrivate::NotifyInternal(WorkerStatus aStatus) {
4392 auto data = mWorkerThreadAccessible.Access();
4393
4394 // Yield execution while notifying out-of-module WorkerRefs and cancelling
4395 // runnables.
4396 AutoYieldJSThreadExecution yield;
4397
4398 NS_ASSERTION(aStatus > Running && aStatus < Dead, "Bad status!");
4399
4400 RefPtr<EventTarget> eventTarget;
4401
4402 // Save the old status and set the new status.
4403 WorkerStatus previousStatus;
4404 {
4405 MutexAutoLock lock(mMutex);
4406
4407 if (mStatus >= aStatus) {
4408 return true;
4409 }
4410
4411 MOZ_ASSERT_IF(aStatus == Killing, mStatus == Canceling);
4412
4413 if (aStatus >= Canceling) {
4414 MutexAutoUnlock unlock(mMutex);
4415 if (data->mScope) {
4416 data->mScope->NoteTerminating();
4417 }
4418 }
4419
4420 // Make sure the hybrid event target stops dispatching runnables
4421 // once we reaching the killing state.
4422 if (aStatus == Killing) {
4423 // To avoid deadlock we always acquire the event target mutex before the
4424 // worker private mutex. (We do it in this order because this is what
4425 // workers best for event dispatching.) To enforce that order here we
4426 // need to unlock the worker private mutex before we lock the event target
4427 // mutex in ForgetWorkerPrivate.
4428 {
4429 MutexAutoUnlock unlock(mMutex);
4430 mWorkerHybridEventTarget->ForgetWorkerPrivate(this);
4431 }
4432
4433 // Check the status code again in case another NotifyInternal came in
4434 // while we were unlocked above.
4435 if (mStatus >= aStatus) {
4436 return true;
4437 }
4438 }
4439
4440 previousStatus = mStatus;
4441 mStatus = aStatus;
4442
4443 // Mark parent status as closing immediately to avoid new events being
4444 // dispatched after we clear the queue below.
4445 if (aStatus == Closing) {
4446 Close();
4447 }
4448 }
4449
4450 MOZ_ASSERT(previousStatus != Pending);
4451
4452 if (aStatus >= Closing) {
4453 CancelAllTimeouts();
4454 }
4455
4456 // Let all our holders know the new status.
4457 if (aStatus > Closing) {
4458 NotifyWorkerRefs(aStatus);
4459 }
4460
4461 // If this is the first time our status has changed then we need to clear the
4462 // main event queue.
4463 if (previousStatus == Running) {
4464 // NB: If we're in a sync loop, we can't clear the queue immediately,
4465 // because this is the wrong queue. So we have to defer it until later.
4466 if (!mSyncLoopStack.IsEmpty()) {
4467 mPostSyncLoopOperations |= ePendingEventQueueClearing;
4468 } else {
4469 ClearMainEventQueue(WorkerRan);
4470 }
4471 }
4472
4473 // If the worker script never ran, or failed to compile, we don't need to do
4474 // anything else.
4475 if (!GlobalScope()) {
4476 return true;
4477 }
4478
4479 // Don't abort the script now, but we dispatch a runnable to do it when the
4480 // current JS frame is executed.
4481 if (aStatus == Closing) {
4482 if (!mSyncLoopStack.IsEmpty()) {
4483 mPostSyncLoopOperations |= eDispatchCancelingRunnable;
4484 } else {
4485 DispatchCancelingRunnable();
4486 }
4487 return true;
4488 }
4489
4490 MOZ_ASSERT(aStatus == Canceling || aStatus == Killing);
4491
4492 // Always abort the script.
4493 return false;
4494 }
4495
ReportError(JSContext * aCx,JS::ConstUTF8CharsZ aToStringResult,JSErrorReport * aReport)4496 void WorkerPrivate::ReportError(JSContext* aCx,
4497 JS::ConstUTF8CharsZ aToStringResult,
4498 JSErrorReport* aReport) {
4499 auto data = mWorkerThreadAccessible.Access();
4500
4501 if (!MayContinueRunning() || data->mErrorHandlerRecursionCount == 2) {
4502 return;
4503 }
4504
4505 NS_ASSERTION(data->mErrorHandlerRecursionCount == 0 ||
4506 data->mErrorHandlerRecursionCount == 1,
4507 "Bad recursion logic!");
4508
4509 UniquePtr<WorkerErrorReport> report = MakeUnique<WorkerErrorReport>();
4510 if (aReport) {
4511 report->AssignErrorReport(aReport);
4512 }
4513
4514 JS::ExceptionStack exnStack(aCx);
4515 if (JS_IsExceptionPending(aCx)) {
4516 if (!JS::StealPendingExceptionStack(aCx, &exnStack)) {
4517 JS_ClearPendingException(aCx);
4518 return;
4519 }
4520
4521 JS::RootedObject stack(aCx), stackGlobal(aCx);
4522 xpc::FindExceptionStackForConsoleReport(
4523 nullptr, exnStack.exception(), exnStack.stack(), &stack, &stackGlobal);
4524
4525 if (stack) {
4526 JSAutoRealm ar(aCx, stackGlobal);
4527 report->SerializeWorkerStack(aCx, this, stack);
4528 }
4529 } else {
4530 // ReportError is also used for reporting warnings,
4531 // so there won't be a pending exception.
4532 MOZ_ASSERT(aReport->isWarning());
4533 }
4534
4535 if (report->mMessage.IsEmpty() && aToStringResult) {
4536 nsDependentCString toStringResult(aToStringResult.c_str());
4537 if (!AppendUTF8toUTF16(toStringResult, report->mMessage,
4538 mozilla::fallible)) {
4539 // Try again, with only a 1 KB string. Do this infallibly this time.
4540 // If the user doesn't have 1 KB to spare we're done anyways.
4541 size_t index = std::min<size_t>(1024, toStringResult.Length());
4542
4543 // Drop the last code point that may be cropped.
4544 index = RewindToPriorUTF8Codepoint(toStringResult.BeginReading(), index);
4545
4546 nsDependentCString truncatedToStringResult(aToStringResult.c_str(),
4547 index);
4548 AppendUTF8toUTF16(truncatedToStringResult, report->mMessage);
4549 }
4550 }
4551
4552 data->mErrorHandlerRecursionCount++;
4553
4554 // Don't want to run the scope's error handler if this is a recursive error or
4555 // if we ran out of memory.
4556 bool fireAtScope = data->mErrorHandlerRecursionCount == 1 &&
4557 report->mErrorNumber != JSMSG_OUT_OF_MEMORY &&
4558 JS::CurrentGlobalOrNull(aCx);
4559
4560 WorkerErrorReport::ReportError(aCx, this, fireAtScope, nullptr,
4561 std::move(report), 0, exnStack.exception());
4562
4563 data->mErrorHandlerRecursionCount--;
4564 }
4565
4566 // static
ReportErrorToConsole(const char * aMessage)4567 void WorkerPrivate::ReportErrorToConsole(const char* aMessage) {
4568 nsTArray<nsString> emptyParams;
4569 WorkerPrivate::ReportErrorToConsole(aMessage, emptyParams);
4570 }
4571
4572 // static
ReportErrorToConsole(const char * aMessage,const nsTArray<nsString> & aParams)4573 void WorkerPrivate::ReportErrorToConsole(const char* aMessage,
4574 const nsTArray<nsString>& aParams) {
4575 WorkerPrivate* wp = nullptr;
4576 if (!NS_IsMainThread()) {
4577 wp = GetCurrentThreadWorkerPrivate();
4578 }
4579
4580 ReportErrorToConsoleRunnable::Report(wp, aMessage, aParams);
4581 }
4582
SetTimeout(JSContext * aCx,TimeoutHandler * aHandler,int32_t aTimeout,bool aIsInterval,ErrorResult & aRv)4583 int32_t WorkerPrivate::SetTimeout(JSContext* aCx, TimeoutHandler* aHandler,
4584 int32_t aTimeout, bool aIsInterval,
4585 ErrorResult& aRv) {
4586 auto data = mWorkerThreadAccessible.Access();
4587 MOZ_ASSERT(aHandler);
4588
4589 const int32_t timerId = data->mNextTimeoutId++;
4590
4591 WorkerStatus currentStatus;
4592 {
4593 MutexAutoLock lock(mMutex);
4594 currentStatus = mStatus;
4595 }
4596
4597 // If the worker is trying to call setTimeout/setInterval and the parent
4598 // thread has initiated the close process then just silently fail.
4599 if (currentStatus >= Closing) {
4600 return timerId;
4601 }
4602
4603 auto newInfo = MakeUnique<TimeoutInfo>();
4604 newInfo->mOnChromeWorker = mIsChromeWorker;
4605 newInfo->mIsInterval = aIsInterval;
4606 newInfo->mId = timerId;
4607 newInfo->AccumulateNestingLevel(data->mCurrentTimerNestingLevel);
4608
4609 if (MOZ_UNLIKELY(timerId == INT32_MAX)) {
4610 NS_WARNING("Timeout ids overflowed!");
4611 data->mNextTimeoutId = 1;
4612 }
4613
4614 newInfo->mHandler = aHandler;
4615
4616 // See if any of the optional arguments were passed.
4617 aTimeout = std::max(0, aTimeout);
4618 newInfo->mInterval = TimeDuration::FromMilliseconds(aTimeout);
4619 newInfo->CalculateTargetTime();
4620
4621 const auto& insertedInfo = data->mTimeouts.InsertElementSorted(
4622 std::move(newInfo), GetUniquePtrComparator(data->mTimeouts));
4623
4624 LOG(TimeoutsLog(), ("Worker %p has new timeout: delay=%d interval=%s\n", this,
4625 aTimeout, aIsInterval ? "yes" : "no"));
4626
4627 // If the timeout we just made is set to fire next then we need to update the
4628 // timer, unless we're currently running timeouts.
4629 if (insertedInfo == data->mTimeouts.Elements() &&
4630 !data->mRunningExpiredTimeouts) {
4631 if (!data->mTimer) {
4632 data->mTimer = NS_NewTimer();
4633 if (!data->mTimer) {
4634 aRv.Throw(NS_ERROR_UNEXPECTED);
4635 return 0;
4636 }
4637
4638 data->mTimerRunnable = new TimerRunnable(this);
4639 }
4640
4641 if (!data->mTimerRunning) {
4642 if (!ModifyBusyCountFromWorker(true)) {
4643 aRv.Throw(NS_ERROR_FAILURE);
4644 return 0;
4645 }
4646 data->mTimerRunning = true;
4647 }
4648
4649 if (!RescheduleTimeoutTimer(aCx)) {
4650 aRv.Throw(NS_ERROR_FAILURE);
4651 return 0;
4652 }
4653 }
4654
4655 return timerId;
4656 }
4657
ClearTimeout(int32_t aId)4658 void WorkerPrivate::ClearTimeout(int32_t aId) {
4659 auto data = mWorkerThreadAccessible.Access();
4660
4661 if (!data->mTimeouts.IsEmpty()) {
4662 NS_ASSERTION(data->mTimerRunning, "Huh?!");
4663
4664 for (uint32_t index = 0; index < data->mTimeouts.Length(); index++) {
4665 const auto& info = data->mTimeouts[index];
4666 if (info->mId == aId) {
4667 info->mCanceled = true;
4668 break;
4669 }
4670 }
4671 }
4672 }
4673
RunExpiredTimeouts(JSContext * aCx)4674 bool WorkerPrivate::RunExpiredTimeouts(JSContext* aCx) {
4675 auto data = mWorkerThreadAccessible.Access();
4676
4677 // We may be called recursively (e.g. close() inside a timeout) or we could
4678 // have been canceled while this event was pending, bail out if there is
4679 // nothing to do.
4680 if (data->mRunningExpiredTimeouts || !data->mTimerRunning) {
4681 return true;
4682 }
4683
4684 NS_ASSERTION(data->mTimer && data->mTimerRunnable, "Must have a timer!");
4685 NS_ASSERTION(!data->mTimeouts.IsEmpty(), "Should have some work to do!");
4686
4687 bool retval = true;
4688
4689 auto comparator = GetUniquePtrComparator(data->mTimeouts);
4690 JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
4691
4692 // We want to make sure to run *something*, even if the timer fired a little
4693 // early. Fudge the value of now to at least include the first timeout.
4694 const TimeStamp actual_now = TimeStamp::Now();
4695 const TimeStamp now = std::max(actual_now, data->mTimeouts[0]->mTargetTime);
4696
4697 if (now != actual_now) {
4698 LOG(TimeoutsLog(), ("Worker %p fudged timeout by %f ms.\n", this,
4699 (now - actual_now).ToMilliseconds()));
4700 }
4701
4702 AutoTArray<TimeoutInfo*, 10> expiredTimeouts;
4703 for (uint32_t index = 0; index < data->mTimeouts.Length(); index++) {
4704 TimeoutInfo* info = data->mTimeouts[index].get();
4705 if (info->mTargetTime > now) {
4706 break;
4707 }
4708 expiredTimeouts.AppendElement(info);
4709 }
4710
4711 // Guard against recursion.
4712 data->mRunningExpiredTimeouts = true;
4713
4714 MOZ_DIAGNOSTIC_ASSERT(data->mCurrentTimerNestingLevel == 0);
4715
4716 // Run expired timeouts.
4717 for (uint32_t index = 0; index < expiredTimeouts.Length(); index++) {
4718 TimeoutInfo*& info = expiredTimeouts[index];
4719 AutoRestore<uint32_t> nestingLevel(data->mCurrentTimerNestingLevel);
4720
4721 if (info->mCanceled) {
4722 continue;
4723 }
4724
4725 // Set current timer nesting level to current running timer handler's
4726 // nesting level
4727 data->mCurrentTimerNestingLevel = info->mNestingLevel;
4728
4729 LOG(TimeoutsLog(),
4730 ("Worker %p executing timeout with original delay %f ms.\n", this,
4731 info->mInterval.ToMilliseconds()));
4732
4733 // Always check JS_IsExceptionPending if something fails, and if
4734 // JS_IsExceptionPending returns false (i.e. uncatchable exception) then
4735 // break out of the loop.
4736 const char* reason;
4737 if (info->mIsInterval) {
4738 reason = "setInterval handler";
4739 } else {
4740 reason = "setTimeout handler";
4741 }
4742
4743 RefPtr<TimeoutHandler> handler(info->mHandler);
4744
4745 RefPtr<WorkerGlobalScope> scope(this->GlobalScope());
4746 CallbackDebuggerNotificationGuard guard(
4747 scope, info->mIsInterval
4748 ? DebuggerNotificationType::SetIntervalCallback
4749 : DebuggerNotificationType::SetTimeoutCallback);
4750 if (!handler->Call(reason)) {
4751 retval = false;
4752 break;
4753 }
4754
4755 NS_ASSERTION(data->mRunningExpiredTimeouts, "Someone changed this!");
4756 }
4757
4758 // No longer possible to be called recursively.
4759 data->mRunningExpiredTimeouts = false;
4760
4761 // Now remove canceled and expired timeouts from the main list.
4762 // NB: The timeouts present in expiredTimeouts must have the same order
4763 // with respect to each other in mTimeouts. That is, mTimeouts is just
4764 // expiredTimeouts with extra elements inserted. There may be unexpired
4765 // timeouts that have been inserted between the expired timeouts if the
4766 // timeout event handler called setTimeout/setInterval.
4767 for (uint32_t index = 0, expiredTimeoutIndex = 0,
4768 expiredTimeoutLength = expiredTimeouts.Length();
4769 index < data->mTimeouts.Length();) {
4770 const auto& info = data->mTimeouts[index];
4771 if ((expiredTimeoutIndex < expiredTimeoutLength &&
4772 info == expiredTimeouts[expiredTimeoutIndex] &&
4773 ++expiredTimeoutIndex) ||
4774 info->mCanceled) {
4775 if (info->mIsInterval && !info->mCanceled) {
4776 // Reschedule intervals.
4777 // Reschedule a timeout, if needed, increase the nesting level.
4778 info->AccumulateNestingLevel(info->mNestingLevel);
4779 info->CalculateTargetTime();
4780 // Don't resort the list here, we'll do that at the end.
4781 ++index;
4782 } else {
4783 data->mTimeouts.RemoveElement(info);
4784 }
4785 } else {
4786 // If info did not match the current entry in expiredTimeouts, it
4787 // shouldn't be there at all.
4788 NS_ASSERTION(!expiredTimeouts.Contains(info),
4789 "Our timeouts are out of order!");
4790 ++index;
4791 }
4792 }
4793
4794 data->mTimeouts.Sort(comparator);
4795
4796 // Either signal the parent that we're no longer using timeouts or reschedule
4797 // the timer.
4798 if (data->mTimeouts.IsEmpty()) {
4799 if (!ModifyBusyCountFromWorker(false)) {
4800 retval = false;
4801 }
4802 data->mTimerRunning = false;
4803 } else if (retval && !RescheduleTimeoutTimer(aCx)) {
4804 retval = false;
4805 }
4806
4807 return retval;
4808 }
4809
RescheduleTimeoutTimer(JSContext * aCx)4810 bool WorkerPrivate::RescheduleTimeoutTimer(JSContext* aCx) {
4811 auto data = mWorkerThreadAccessible.Access();
4812 MOZ_ASSERT(!data->mRunningExpiredTimeouts);
4813 NS_ASSERTION(!data->mTimeouts.IsEmpty(), "Should have some timeouts!");
4814 NS_ASSERTION(data->mTimer && data->mTimerRunnable, "Should have a timer!");
4815
4816 // NB: This is important! The timer may have already fired, e.g. if a timeout
4817 // callback itself calls setTimeout for a short duration and then takes longer
4818 // than that to finish executing. If that has happened, it's very important
4819 // that we don't execute the event that is now pending in our event queue, or
4820 // our code in RunExpiredTimeouts to "fudge" the timeout value will unleash an
4821 // early timeout when we execute the event we're about to queue.
4822 data->mTimer->Cancel();
4823
4824 double delta =
4825 (data->mTimeouts[0]->mTargetTime - TimeStamp::Now()).ToMilliseconds();
4826 uint32_t delay = delta > 0 ? std::min(delta, double(UINT32_MAX)) : 0;
4827
4828 LOG(TimeoutsLog(),
4829 ("Worker %p scheduled timer for %d ms, %zu pending timeouts\n", this,
4830 delay, data->mTimeouts.Length()));
4831
4832 nsresult rv = data->mTimer->InitWithCallback(data->mTimerRunnable, delay,
4833 nsITimer::TYPE_ONE_SHOT);
4834 if (NS_FAILED(rv)) {
4835 JS_ReportErrorASCII(aCx, "Failed to start timer!");
4836 return false;
4837 }
4838
4839 return true;
4840 }
4841
StartCancelingTimer()4842 void WorkerPrivate::StartCancelingTimer() {
4843 AssertIsOnParentThread();
4844
4845 auto errorCleanup = MakeScopeExit([&] { mCancelingTimer = nullptr; });
4846
4847 MOZ_ASSERT(!mCancelingTimer);
4848
4849 if (WorkerPrivate* parent = GetParent()) {
4850 mCancelingTimer = NS_NewTimer(parent->ControlEventTarget());
4851 } else {
4852 mCancelingTimer = NS_NewTimer();
4853 }
4854
4855 if (NS_WARN_IF(!mCancelingTimer)) {
4856 return;
4857 }
4858
4859 // This is not needed if we are already in an advanced shutdown state.
4860 {
4861 MutexAutoLock lock(mMutex);
4862 if (ParentStatus() >= Canceling) {
4863 return;
4864 }
4865 }
4866
4867 uint32_t cancelingTimeoutMillis =
4868 StaticPrefs::dom_worker_canceling_timeoutMilliseconds();
4869
4870 RefPtr<CancelingTimerCallback> callback = new CancelingTimerCallback(this);
4871 nsresult rv = mCancelingTimer->InitWithCallback(
4872 callback, cancelingTimeoutMillis, nsITimer::TYPE_ONE_SHOT);
4873 if (NS_WARN_IF(NS_FAILED(rv))) {
4874 return;
4875 }
4876
4877 errorCleanup.release();
4878 }
4879
UpdateContextOptionsInternal(JSContext * aCx,const JS::ContextOptions & aContextOptions)4880 void WorkerPrivate::UpdateContextOptionsInternal(
4881 JSContext* aCx, const JS::ContextOptions& aContextOptions) {
4882 auto data = mWorkerThreadAccessible.Access();
4883
4884 JS::ContextOptionsRef(aCx) = aContextOptions;
4885
4886 for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
4887 data->mChildWorkers[index]->UpdateContextOptions(aContextOptions);
4888 }
4889 }
4890
UpdateLanguagesInternal(const nsTArray<nsString> & aLanguages)4891 void WorkerPrivate::UpdateLanguagesInternal(
4892 const nsTArray<nsString>& aLanguages) {
4893 WorkerGlobalScope* globalScope = GlobalScope();
4894 RefPtr<WorkerNavigator> nav = globalScope->GetExistingNavigator();
4895 if (nav) {
4896 nav->SetLanguages(aLanguages);
4897 }
4898
4899 auto data = mWorkerThreadAccessible.Access();
4900 for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
4901 data->mChildWorkers[index]->UpdateLanguages(aLanguages);
4902 }
4903
4904 RefPtr<Event> event = NS_NewDOMEvent(globalScope, nullptr, nullptr);
4905
4906 event->InitEvent(u"languagechange"_ns, false, false);
4907 event->SetTrusted(true);
4908
4909 globalScope->DispatchEvent(*event);
4910 }
4911
UpdateJSWorkerMemoryParameterInternal(JSContext * aCx,JSGCParamKey aKey,Maybe<uint32_t> aValue)4912 void WorkerPrivate::UpdateJSWorkerMemoryParameterInternal(
4913 JSContext* aCx, JSGCParamKey aKey, Maybe<uint32_t> aValue) {
4914 auto data = mWorkerThreadAccessible.Access();
4915
4916 if (aValue) {
4917 JS_SetGCParameter(aCx, aKey, *aValue);
4918 } else {
4919 JS_ResetGCParameter(aCx, aKey);
4920 }
4921
4922 for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
4923 data->mChildWorkers[index]->UpdateJSWorkerMemoryParameter(aKey, aValue);
4924 }
4925 }
4926
4927 #ifdef JS_GC_ZEAL
UpdateGCZealInternal(JSContext * aCx,uint8_t aGCZeal,uint32_t aFrequency)4928 void WorkerPrivate::UpdateGCZealInternal(JSContext* aCx, uint8_t aGCZeal,
4929 uint32_t aFrequency) {
4930 auto data = mWorkerThreadAccessible.Access();
4931
4932 JS_SetGCZeal(aCx, aGCZeal, aFrequency);
4933
4934 for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
4935 data->mChildWorkers[index]->UpdateGCZeal(aGCZeal, aFrequency);
4936 }
4937 }
4938 #endif
4939
SetLowMemoryStateInternal(JSContext * aCx,bool aState)4940 void WorkerPrivate::SetLowMemoryStateInternal(JSContext* aCx, bool aState) {
4941 auto data = mWorkerThreadAccessible.Access();
4942
4943 JS::SetLowMemoryState(aCx, aState);
4944
4945 for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
4946 data->mChildWorkers[index]->SetLowMemoryState(aState);
4947 }
4948 }
4949
SetCCCollectedAnything(bool collectedAnything)4950 void WorkerPrivate::SetCCCollectedAnything(bool collectedAnything) {
4951 mWorkerThreadAccessible.Access()->mCCCollectedAnything = collectedAnything;
4952 }
4953
GarbageCollectInternal(JSContext * aCx,bool aShrinking,bool aCollectChildren)4954 void WorkerPrivate::GarbageCollectInternal(JSContext* aCx, bool aShrinking,
4955 bool aCollectChildren) {
4956 // Perform GC followed by CC (the CC is triggered by
4957 // WorkerJSRuntime::CustomGCCallback at the end of the collection).
4958
4959 auto data = mWorkerThreadAccessible.Access();
4960
4961 if (!GlobalScope()) {
4962 // We haven't compiled anything yet. Just bail out.
4963 return;
4964 }
4965
4966 if (aShrinking || aCollectChildren) {
4967 JS::PrepareForFullGC(aCx);
4968
4969 if (aShrinking && mSyncLoopStack.IsEmpty()) {
4970 JS::NonIncrementalGC(aCx, JS::GCOptions::Shrink,
4971 JS::GCReason::DOM_WORKER);
4972
4973 // Check whether the CC collected anything and if so GC again. This is
4974 // necessary to collect all garbage.
4975 if (data->mCCCollectedAnything) {
4976 JS::NonIncrementalGC(aCx, JS::GCOptions::Normal,
4977 JS::GCReason::DOM_WORKER);
4978 }
4979
4980 if (!aCollectChildren) {
4981 LOG(WorkerLog(), ("Worker %p collected idle garbage\n", this));
4982 }
4983 } else {
4984 JS::NonIncrementalGC(aCx, JS::GCOptions::Normal,
4985 JS::GCReason::DOM_WORKER);
4986 LOG(WorkerLog(), ("Worker %p collected garbage\n", this));
4987 }
4988 } else {
4989 JS_MaybeGC(aCx);
4990 LOG(WorkerLog(), ("Worker %p collected periodic garbage\n", this));
4991 }
4992
4993 if (aCollectChildren) {
4994 for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
4995 data->mChildWorkers[index]->GarbageCollect(aShrinking);
4996 }
4997 }
4998 }
4999
CycleCollectInternal(bool aCollectChildren)5000 void WorkerPrivate::CycleCollectInternal(bool aCollectChildren) {
5001 auto data = mWorkerThreadAccessible.Access();
5002
5003 nsCycleCollector_collect(nullptr);
5004
5005 if (aCollectChildren) {
5006 for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
5007 data->mChildWorkers[index]->CycleCollect();
5008 }
5009 }
5010 }
5011
MemoryPressureInternal()5012 void WorkerPrivate::MemoryPressureInternal() {
5013 auto data = mWorkerThreadAccessible.Access();
5014
5015 if (data->mScope) {
5016 RefPtr<Console> console = data->mScope->GetConsoleIfExists();
5017 if (console) {
5018 console->ClearStorage();
5019 }
5020
5021 RefPtr<Performance> performance = data->mScope->GetPerformanceIfExists();
5022 if (performance) {
5023 performance->MemoryPressure();
5024 }
5025
5026 data->mScope->RemoveReportRecords();
5027 }
5028
5029 if (data->mDebuggerScope) {
5030 RefPtr<Console> console = data->mDebuggerScope->GetConsoleIfExists();
5031 if (console) {
5032 console->ClearStorage();
5033 }
5034 }
5035
5036 for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
5037 data->mChildWorkers[index]->MemoryPressure();
5038 }
5039 }
5040
SetThread(WorkerThread * aThread)5041 void WorkerPrivate::SetThread(WorkerThread* aThread) {
5042 if (aThread) {
5043 #ifdef DEBUG
5044 {
5045 bool isOnCurrentThread;
5046 MOZ_ASSERT(NS_SUCCEEDED(aThread->IsOnCurrentThread(&isOnCurrentThread)));
5047 MOZ_ASSERT(!isOnCurrentThread);
5048 }
5049 #endif
5050
5051 MOZ_ASSERT(!mPRThread);
5052 mPRThread = PRThreadFromThread(aThread);
5053 MOZ_ASSERT(mPRThread);
5054
5055 mWorkerThreadAccessible.Transfer(mPRThread);
5056 } else {
5057 MOZ_ASSERT(mPRThread);
5058 }
5059 }
5060
SetWorkerPrivateInWorkerThread(WorkerThread * const aThread)5061 void WorkerPrivate::SetWorkerPrivateInWorkerThread(
5062 WorkerThread* const aThread) {
5063 MutexAutoLock lock(mMutex);
5064
5065 MOZ_ASSERT(!mThread);
5066 MOZ_ASSERT(mStatus == Pending);
5067
5068 mThread = aThread;
5069 mThread->SetWorker(WorkerThreadFriendKey{}, this);
5070
5071 if (!mPreStartRunnables.IsEmpty()) {
5072 for (uint32_t index = 0; index < mPreStartRunnables.Length(); index++) {
5073 MOZ_ALWAYS_SUCCEEDS(mThread->DispatchAnyThread(
5074 WorkerThreadFriendKey{}, mPreStartRunnables[index].forget()));
5075 }
5076 mPreStartRunnables.Clear();
5077 }
5078 }
5079
ResetWorkerPrivateInWorkerThread()5080 void WorkerPrivate::ResetWorkerPrivateInWorkerThread() {
5081 RefPtr<WorkerThread> doomedThread;
5082
5083 // Release the mutex before doomedThread.
5084 MutexAutoLock lock(mMutex);
5085
5086 MOZ_ASSERT(mThread);
5087
5088 mThread->SetWorker(WorkerThreadFriendKey{}, nullptr);
5089 mThread.swap(doomedThread);
5090 }
5091
BeginCTypesCall()5092 void WorkerPrivate::BeginCTypesCall() {
5093 AssertIsOnWorkerThread();
5094 auto data = mWorkerThreadAccessible.Access();
5095
5096 // Don't try to GC while we're blocked in a ctypes call.
5097 SetGCTimerMode(NoTimer);
5098
5099 data->mYieldJSThreadExecution.EmplaceBack();
5100 }
5101
EndCTypesCall()5102 void WorkerPrivate::EndCTypesCall() {
5103 AssertIsOnWorkerThread();
5104 auto data = mWorkerThreadAccessible.Access();
5105
5106 data->mYieldJSThreadExecution.RemoveLastElement();
5107
5108 // Make sure the periodic timer is running before we start running JS again.
5109 SetGCTimerMode(PeriodicTimer);
5110 }
5111
BeginCTypesCallback()5112 void WorkerPrivate::BeginCTypesCallback() {
5113 AssertIsOnWorkerThread();
5114
5115 // Make sure the periodic timer is running before we start running JS again.
5116 SetGCTimerMode(PeriodicTimer);
5117
5118 // Re-requesting execution is not needed since the JSRuntime code calling
5119 // this will do an AutoEntryScript.
5120 }
5121
EndCTypesCallback()5122 void WorkerPrivate::EndCTypesCallback() {
5123 AssertIsOnWorkerThread();
5124
5125 // Don't try to GC while we're blocked in a ctypes call.
5126 SetGCTimerMode(NoTimer);
5127 }
5128
ConnectMessagePort(JSContext * aCx,UniqueMessagePortId & aIdentifier)5129 bool WorkerPrivate::ConnectMessagePort(JSContext* aCx,
5130 UniqueMessagePortId& aIdentifier) {
5131 AssertIsOnWorkerThread();
5132
5133 WorkerGlobalScope* globalScope = GlobalScope();
5134
5135 JS::Rooted<JSObject*> jsGlobal(aCx, globalScope->GetWrapper());
5136 MOZ_ASSERT(jsGlobal);
5137
5138 // This UniqueMessagePortId is used to create a new port, still connected
5139 // with the other one, but in the worker thread.
5140 ErrorResult rv;
5141 RefPtr<MessagePort> port = MessagePort::Create(globalScope, aIdentifier, rv);
5142 if (NS_WARN_IF(rv.Failed())) {
5143 rv.SuppressException();
5144 return false;
5145 }
5146
5147 GlobalObject globalObject(aCx, jsGlobal);
5148 if (globalObject.Failed()) {
5149 return false;
5150 }
5151
5152 RootedDictionary<MessageEventInit> init(aCx);
5153 init.mData = JS_GetEmptyStringValue(aCx);
5154 init.mBubbles = false;
5155 init.mCancelable = false;
5156 init.mSource.SetValue().SetAsMessagePort() = port;
5157 if (!init.mPorts.AppendElement(port.forget(), fallible)) {
5158 return false;
5159 }
5160
5161 RefPtr<MessageEvent> event =
5162 MessageEvent::Constructor(globalObject, u"connect"_ns, init);
5163
5164 event->SetTrusted(true);
5165
5166 globalScope->DispatchEvent(*event);
5167
5168 return true;
5169 }
5170
GetOrCreateGlobalScope(JSContext * aCx)5171 WorkerGlobalScope* WorkerPrivate::GetOrCreateGlobalScope(JSContext* aCx) {
5172 auto data = mWorkerThreadAccessible.Access();
5173
5174 if (data->mScope) {
5175 return data->mScope;
5176 }
5177
5178 if (IsSharedWorker()) {
5179 data->mScope = new SharedWorkerGlobalScope(
5180 WrapNotNull(this), CreateClientSource(), WorkerName());
5181 } else if (IsServiceWorker()) {
5182 data->mScope =
5183 new ServiceWorkerGlobalScope(WrapNotNull(this), CreateClientSource(),
5184 GetServiceWorkerRegistrationDescriptor());
5185 } else {
5186 data->mScope = new DedicatedWorkerGlobalScope(
5187 WrapNotNull(this), CreateClientSource(), WorkerName());
5188 }
5189
5190 JS::Rooted<JSObject*> global(aCx);
5191 NS_ENSURE_TRUE(data->mScope->WrapGlobalObject(aCx, &global), nullptr);
5192
5193 JSAutoRealm ar(aCx, global);
5194
5195 if (!RegisterBindings(aCx, global)) {
5196 data->mScope = nullptr;
5197 return nullptr;
5198 }
5199
5200 JS_FireOnNewGlobalObject(aCx, global);
5201
5202 return data->mScope;
5203 }
5204
CreateDebuggerGlobalScope(JSContext * aCx)5205 WorkerDebuggerGlobalScope* WorkerPrivate::CreateDebuggerGlobalScope(
5206 JSContext* aCx) {
5207 auto data = mWorkerThreadAccessible.Access();
5208 MOZ_ASSERT(!data->mDebuggerScope);
5209
5210 // The debugger global gets a dummy client, not the "real" client used by the
5211 // debugee worker.
5212 auto clientSource = ClientManager::CreateSource(
5213 GetClientType(), HybridEventTarget(), NullPrincipalInfo());
5214
5215 data->mDebuggerScope =
5216 new WorkerDebuggerGlobalScope(WrapNotNull(this), std::move(clientSource));
5217
5218 JS::Rooted<JSObject*> global(aCx);
5219 NS_ENSURE_TRUE(data->mDebuggerScope->WrapGlobalObject(aCx, &global), nullptr);
5220
5221 JSAutoRealm ar(aCx, global);
5222
5223 if (!RegisterDebuggerBindings(aCx, global)) {
5224 data->mDebuggerScope = nullptr;
5225 return nullptr;
5226 }
5227
5228 JS_FireOnNewGlobalObject(aCx, global);
5229
5230 return data->mDebuggerScope;
5231 }
5232
IsOnWorkerThread() const5233 bool WorkerPrivate::IsOnWorkerThread() const {
5234 // We can't use mThread because it must be protected by mMutex and sometimes
5235 // this method is called when mMutex is already locked. This method should
5236 // always work.
5237 MOZ_ASSERT(mPRThread,
5238 "AssertIsOnWorkerThread() called before a thread was assigned!");
5239
5240 return mPRThread == PR_GetCurrentThread();
5241 }
5242
5243 #ifdef DEBUG
AssertIsOnWorkerThread() const5244 void WorkerPrivate::AssertIsOnWorkerThread() const {
5245 MOZ_ASSERT(IsOnWorkerThread());
5246 }
5247 #endif // DEBUG
5248
DumpCrashInformation(nsACString & aString)5249 void WorkerPrivate::DumpCrashInformation(nsACString& aString) {
5250 auto data = mWorkerThreadAccessible.Access();
5251
5252 aString.Append("IsChromeWorker(");
5253 if (IsChromeWorker()) {
5254 aString.Append(NS_ConvertUTF16toUTF8(ScriptURL()));
5255 } else {
5256 aString.Append("false");
5257 }
5258 aString.Append(")");
5259 for (const auto* workerRef : data->mWorkerRefs.NonObservingRange()) {
5260 if (workerRef->IsPreventingShutdown()) {
5261 aString.Append("|");
5262 aString.Append(workerRef->Name());
5263 }
5264 }
5265 }
5266
GetPerformanceStorage()5267 PerformanceStorage* WorkerPrivate::GetPerformanceStorage() {
5268 AssertIsOnMainThread();
5269 MOZ_ASSERT(mPerformanceStorage);
5270 return mPerformanceStorage;
5271 }
5272
SetRemoteWorkerController(RemoteWorkerChild * aController)5273 void WorkerPrivate::SetRemoteWorkerController(RemoteWorkerChild* aController) {
5274 AssertIsOnMainThread();
5275 MOZ_ASSERT(aController);
5276 MOZ_ASSERT(!mRemoteWorkerController);
5277
5278 mRemoteWorkerController = aController;
5279 }
5280
GetRemoteWorkerController()5281 RemoteWorkerChild* WorkerPrivate::GetRemoteWorkerController() {
5282 AssertIsOnMainThread();
5283 MOZ_ASSERT(mRemoteWorkerController);
5284 return mRemoteWorkerController;
5285 }
5286
SetRemoteWorkerControllerWeakRef(ThreadSafeWeakPtr<RemoteWorkerChild> aWeakRef)5287 void WorkerPrivate::SetRemoteWorkerControllerWeakRef(
5288 ThreadSafeWeakPtr<RemoteWorkerChild> aWeakRef) {
5289 MOZ_ASSERT(!aWeakRef.IsNull());
5290 MOZ_ASSERT(mRemoteWorkerControllerWeakRef.IsNull());
5291 MOZ_ASSERT(IsServiceWorker());
5292
5293 mRemoteWorkerControllerWeakRef = std::move(aWeakRef);
5294 }
5295
5296 ThreadSafeWeakPtr<RemoteWorkerChild>
GetRemoteWorkerControllerWeakRef()5297 WorkerPrivate::GetRemoteWorkerControllerWeakRef() {
5298 MOZ_ASSERT(IsServiceWorker());
5299 return mRemoteWorkerControllerWeakRef;
5300 }
5301
SetServiceWorkerSkipWaitingFlag()5302 RefPtr<GenericPromise> WorkerPrivate::SetServiceWorkerSkipWaitingFlag() {
5303 AssertIsOnWorkerThread();
5304 MOZ_ASSERT(IsServiceWorker());
5305
5306 RefPtr<RemoteWorkerChild> rwc(mRemoteWorkerControllerWeakRef);
5307
5308 if (!rwc) {
5309 return GenericPromise::CreateAndReject(NS_ERROR_DOM_ABORT_ERR, __func__);
5310 }
5311
5312 RefPtr<GenericPromise> promise =
5313 rwc->MaybeSendSetServiceWorkerSkipWaitingFlag();
5314
5315 NS_ProxyRelease("WorkerPrivate::mRemoteWorkerControllerWeakRef",
5316 RemoteWorkerService::Thread(), rwc.forget());
5317
5318 return promise;
5319 }
5320
Id()5321 const nsAString& WorkerPrivate::Id() {
5322 AssertIsOnMainThread();
5323
5324 if (mId.IsEmpty()) {
5325 mId = ComputeWorkerPrivateId();
5326 }
5327
5328 MOZ_ASSERT(!mId.IsEmpty());
5329
5330 return mId;
5331 }
5332
IsSharedMemoryAllowed() const5333 bool WorkerPrivate::IsSharedMemoryAllowed() const {
5334 if (StaticPrefs::
5335 dom_postMessage_sharedArrayBuffer_bypassCOOP_COEP_insecure_enabled()) {
5336 return true;
5337 }
5338
5339 if (mIsPrivilegedAddonGlobal) {
5340 return true;
5341 }
5342
5343 return CrossOriginIsolated();
5344 }
5345
CrossOriginIsolated() const5346 bool WorkerPrivate::CrossOriginIsolated() const {
5347 if (!StaticPrefs::
5348 dom_postMessage_sharedArrayBuffer_withCOOP_COEP_AtStartup()) {
5349 return false;
5350 }
5351
5352 return mAgentClusterOpenerPolicy ==
5353 nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP;
5354 }
5355
GetEmbedderPolicy() const5356 nsILoadInfo::CrossOriginEmbedderPolicy WorkerPrivate::GetEmbedderPolicy()
5357 const {
5358 if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
5359 return nsILoadInfo::EMBEDDER_POLICY_NULL;
5360 }
5361
5362 return mEmbedderPolicy.valueOr(nsILoadInfo::EMBEDDER_POLICY_NULL);
5363 }
5364
SetEmbedderPolicy(nsILoadInfo::CrossOriginEmbedderPolicy aPolicy)5365 Result<Ok, nsresult> WorkerPrivate::SetEmbedderPolicy(
5366 nsILoadInfo::CrossOriginEmbedderPolicy aPolicy) {
5367 MOZ_ASSERT(NS_IsMainThread());
5368 MOZ_ASSERT(mEmbedderPolicy.isNothing());
5369
5370 if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
5371 return Ok();
5372 }
5373
5374 // If owner's emebedder policy is corp_reqired, aPolicy must also be
5375 // corp_reqired. But if owner's embedder policy is null, aPolicy needs not
5376 // match owner's value.
5377 // https://wicg.github.io/cross-origin-embedder-policy/#cascade-vs-require
5378 EnsureOwnerEmbedderPolicy();
5379 if (mOwnerEmbedderPolicy.valueOr(nsILoadInfo::EMBEDDER_POLICY_NULL) !=
5380 nsILoadInfo::EMBEDDER_POLICY_NULL) {
5381 if (mOwnerEmbedderPolicy.valueOr(aPolicy) != aPolicy) {
5382 return Err(NS_ERROR_BLOCKED_BY_POLICY);
5383 }
5384 }
5385
5386 mEmbedderPolicy.emplace(aPolicy);
5387
5388 return Ok();
5389 }
5390
InheritOwnerEmbedderPolicyOrNull(nsIRequest * aRequest)5391 void WorkerPrivate::InheritOwnerEmbedderPolicyOrNull(nsIRequest* aRequest) {
5392 MOZ_ASSERT(NS_IsMainThread());
5393 MOZ_ASSERT(aRequest);
5394
5395 EnsureOwnerEmbedderPolicy();
5396
5397 if (mOwnerEmbedderPolicy.isSome()) {
5398 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
5399 MOZ_ASSERT(channel);
5400
5401 nsCOMPtr<nsIURI> scriptURI;
5402 MOZ_ALWAYS_SUCCEEDS(channel->GetURI(getter_AddRefs(scriptURI)));
5403
5404 bool isLocalScriptURI = false;
5405 MOZ_ALWAYS_SUCCEEDS(NS_URIChainHasFlags(
5406 scriptURI, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
5407 &isLocalScriptURI));
5408
5409 MOZ_RELEASE_ASSERT(isLocalScriptURI);
5410 }
5411
5412 mEmbedderPolicy.emplace(
5413 mOwnerEmbedderPolicy.valueOr(nsILoadInfo::EMBEDDER_POLICY_NULL));
5414 }
5415
MatchEmbedderPolicy(nsILoadInfo::CrossOriginEmbedderPolicy aPolicy) const5416 bool WorkerPrivate::MatchEmbedderPolicy(
5417 nsILoadInfo::CrossOriginEmbedderPolicy aPolicy) const {
5418 MOZ_ASSERT(NS_IsMainThread());
5419
5420 if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
5421 return true;
5422 }
5423
5424 return mEmbedderPolicy.value() == aPolicy;
5425 }
5426
GetOwnerEmbedderPolicy() const5427 nsILoadInfo::CrossOriginEmbedderPolicy WorkerPrivate::GetOwnerEmbedderPolicy()
5428 const {
5429 if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
5430 return nsILoadInfo::EMBEDDER_POLICY_NULL;
5431 }
5432
5433 return mOwnerEmbedderPolicy.valueOr(nsILoadInfo::EMBEDDER_POLICY_NULL);
5434 }
5435
EnsureOwnerEmbedderPolicy()5436 void WorkerPrivate::EnsureOwnerEmbedderPolicy() {
5437 MOZ_ASSERT(NS_IsMainThread());
5438 MOZ_ASSERT(mOwnerEmbedderPolicy.isNothing());
5439
5440 if (GetParent()) {
5441 mOwnerEmbedderPolicy.emplace(GetParent()->GetEmbedderPolicy());
5442 } else if (GetWindow() && GetWindow()->GetWindowContext()) {
5443 mOwnerEmbedderPolicy.emplace(
5444 GetWindow()->GetWindowContext()->GetEmbedderPolicy());
5445 }
5446 }
5447
5448 const mozilla::ipc::PrincipalInfo&
GetEffectiveStoragePrincipalInfo() const5449 WorkerPrivate::GetEffectiveStoragePrincipalInfo() const {
5450 AssertIsOnWorkerThread();
5451
5452 if (mLoadInfo.mUseRegularPrincipal) {
5453 return *mLoadInfo.mPrincipalInfo;
5454 }
5455
5456 return *mLoadInfo.mPartitionedPrincipalInfo;
5457 }
5458
EffectiveStoragePrincipalOrigin() const5459 const nsACString& WorkerPrivate::EffectiveStoragePrincipalOrigin() const {
5460 AssertIsOnWorkerThread();
5461
5462 if (mLoadInfo.mUseRegularPrincipal) {
5463 return mLoadInfo.mOrigin;
5464 }
5465
5466 return mLoadInfo.mPartitionedOrigin;
5467 }
5468
5469 NS_IMPL_ADDREF(WorkerPrivate::EventTarget)
5470 NS_IMPL_RELEASE(WorkerPrivate::EventTarget)
5471
5472 NS_INTERFACE_MAP_BEGIN(WorkerPrivate::EventTarget)
5473 NS_INTERFACE_MAP_ENTRY(nsISerialEventTarget)
5474 NS_INTERFACE_MAP_ENTRY(nsIEventTarget)
5475 NS_INTERFACE_MAP_ENTRY(nsISupports)
5476 #ifdef DEBUG
5477 // kDEBUGWorkerEventTargetIID is special in that it does not AddRef its
5478 // result.
5479 if (aIID.Equals(kDEBUGWorkerEventTargetIID)) {
5480 *aInstancePtr = this;
5481 return NS_OK;
5482 } else
5483 #endif
5484 NS_INTERFACE_MAP_END
5485
5486 NS_IMETHODIMP
DispatchFromScript(nsIRunnable * aRunnable,uint32_t aFlags)5487 WorkerPrivate::EventTarget::DispatchFromScript(nsIRunnable* aRunnable,
5488 uint32_t aFlags) {
5489 nsCOMPtr<nsIRunnable> event(aRunnable);
5490 return Dispatch(event.forget(), aFlags);
5491 }
5492
5493 NS_IMETHODIMP
Dispatch(already_AddRefed<nsIRunnable> aRunnable,uint32_t aFlags)5494 WorkerPrivate::EventTarget::Dispatch(already_AddRefed<nsIRunnable> aRunnable,
5495 uint32_t aFlags) {
5496 // May be called on any thread!
5497 nsCOMPtr<nsIRunnable> event(aRunnable);
5498
5499 // Workers only support asynchronous dispatch for now.
5500 if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL)) {
5501 return NS_ERROR_UNEXPECTED;
5502 }
5503
5504 RefPtr<WorkerRunnable> workerRunnable;
5505
5506 MutexAutoLock lock(mMutex);
5507
5508 if (!mWorkerPrivate) {
5509 NS_WARNING(
5510 "A runnable was posted to a worker that is already shutting "
5511 "down!");
5512 return NS_ERROR_UNEXPECTED;
5513 }
5514
5515 if (event) {
5516 workerRunnable = mWorkerPrivate->MaybeWrapAsWorkerRunnable(event.forget());
5517 }
5518
5519 nsresult rv =
5520 mWorkerPrivate->Dispatch(workerRunnable.forget(), mNestedEventTarget);
5521 if (NS_WARN_IF(NS_FAILED(rv))) {
5522 return rv;
5523 }
5524
5525 return NS_OK;
5526 }
5527
5528 NS_IMETHODIMP
DelayedDispatch(already_AddRefed<nsIRunnable>,uint32_t)5529 WorkerPrivate::EventTarget::DelayedDispatch(already_AddRefed<nsIRunnable>,
5530 uint32_t)
5531
5532 {
5533 return NS_ERROR_NOT_IMPLEMENTED;
5534 }
5535
5536 NS_IMETHODIMP
IsOnCurrentThread(bool * aIsOnCurrentThread)5537 WorkerPrivate::EventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread) {
5538 // May be called on any thread!
5539
5540 MOZ_ASSERT(aIsOnCurrentThread);
5541
5542 MutexAutoLock lock(mMutex);
5543
5544 if (!mWorkerPrivate) {
5545 NS_WARNING("A worker's event target was used after the worker has !");
5546 return NS_ERROR_UNEXPECTED;
5547 }
5548
5549 *aIsOnCurrentThread = mWorkerPrivate->IsOnCurrentThread();
5550 return NS_OK;
5551 }
5552
NS_IMETHODIMP_(bool)5553 NS_IMETHODIMP_(bool)
5554 WorkerPrivate::EventTarget::IsOnCurrentThreadInfallible() {
5555 // May be called on any thread!
5556
5557 MutexAutoLock lock(mMutex);
5558
5559 if (!mWorkerPrivate) {
5560 NS_WARNING("A worker's event target was used after the worker has !");
5561 return false;
5562 }
5563
5564 return mWorkerPrivate->IsOnCurrentThread();
5565 }
5566
AutoPushEventLoopGlobal(WorkerPrivate * aWorkerPrivate,JSContext * aCx)5567 WorkerPrivate::AutoPushEventLoopGlobal::AutoPushEventLoopGlobal(
5568 WorkerPrivate* aWorkerPrivate, JSContext* aCx)
5569 : mWorkerPrivate(aWorkerPrivate) {
5570 auto data = mWorkerPrivate->mWorkerThreadAccessible.Access();
5571 mOldEventLoopGlobal = std::move(data->mCurrentEventLoopGlobal);
5572 if (JSObject* global = JS::CurrentGlobalOrNull(aCx)) {
5573 data->mCurrentEventLoopGlobal = xpc::NativeGlobal(global);
5574 }
5575 }
5576
~AutoPushEventLoopGlobal()5577 WorkerPrivate::AutoPushEventLoopGlobal::~AutoPushEventLoopGlobal() {
5578 auto data = mWorkerPrivate->mWorkerThreadAccessible.Access();
5579 data->mCurrentEventLoopGlobal = std::move(mOldEventLoopGlobal);
5580 }
5581
5582 } // namespace dom
5583 } // namespace mozilla
5584