Backed out 12 changesets (bug 1843308, bug 1848406, bug 1888500, bug 1888504, bug...
[gecko.git] / dom / workers / WorkerPrivate.cpp
blobdf248acda417ae2f304a7f5fa5d067912e402431
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/. */
7 #include "WorkerPrivate.h"
9 #include <utility>
11 #include "js/CallAndConstruct.h" // JS_CallFunctionValue
12 #include "js/CompilationAndEvaluation.h"
13 #include "js/ContextOptions.h"
14 #include "js/Exception.h"
15 #include "js/friend/ErrorMessages.h" // JSMSG_OUT_OF_MEMORY
16 #include "js/LocaleSensitive.h"
17 #include "js/MemoryMetrics.h"
18 #include "js/SourceText.h"
19 #include "MessageEventRunnable.h"
20 #include "mozilla/AntiTrackingUtils.h"
21 #include "mozilla/BasePrincipal.h"
22 #include "mozilla/CycleCollectedJSContext.h"
23 #include "mozilla/ExtensionPolicyService.h"
24 #include "mozilla/Mutex.h"
25 #include "mozilla/ProfilerLabels.h"
26 #include "mozilla/Result.h"
27 #include "mozilla/ScopeExit.h"
28 #include "mozilla/StaticPrefs_browser.h"
29 #include "mozilla/StaticPrefs_dom.h"
30 #include "mozilla/dom/BrowsingContextGroup.h"
31 #include "mozilla/dom/CallbackDebuggerNotification.h"
32 #include "mozilla/dom/ClientManager.h"
33 #include "mozilla/dom/ClientState.h"
34 #include "mozilla/dom/Console.h"
35 #include "mozilla/dom/DocGroup.h"
36 #include "mozilla/dom/Document.h"
37 #include "mozilla/dom/DOMTypes.h"
38 #include "mozilla/dom/Event.h"
39 #include "mozilla/dom/Exceptions.h"
40 #include "mozilla/dom/FunctionBinding.h"
41 #include "mozilla/dom/IndexedDatabaseManager.h"
42 #include "mozilla/dom/MessageEvent.h"
43 #include "mozilla/dom/MessageEventBinding.h"
44 #include "mozilla/dom/MessagePort.h"
45 #include "mozilla/dom/MessagePortBinding.h"
46 #include "mozilla/dom/nsCSPContext.h"
47 #include "mozilla/dom/nsCSPUtils.h"
48 #include "mozilla/dom/Performance.h"
49 #include "mozilla/dom/PerformanceStorageWorker.h"
50 #include "mozilla/dom/PromiseDebugging.h"
51 #include "mozilla/dom/ReferrerInfo.h"
52 #include "mozilla/dom/RemoteWorkerChild.h"
53 #include "mozilla/dom/RemoteWorkerService.h"
54 #include "mozilla/dom/RootedDictionary.h"
55 #include "mozilla/dom/TimeoutHandler.h"
56 #include "mozilla/dom/UseCounterMetrics.h"
57 #include "mozilla/dom/WorkerBinding.h"
58 #include "mozilla/dom/WorkerScope.h"
59 #include "mozilla/dom/WorkerStatus.h"
60 #include "mozilla/dom/WebTaskScheduler.h"
61 #include "mozilla/dom/JSExecutionManager.h"
62 #include "mozilla/dom/WindowContext.h"
63 #include "mozilla/extensions/ExtensionBrowser.h" // extensions::Create{AndDispatchInitWorkerContext,WorkerLoaded,WorkerDestroyed}Runnable
64 #include "mozilla/extensions/WebExtensionPolicy.h"
65 #include "mozilla/glean/GleanMetrics.h"
66 #include "mozilla/ipc/BackgroundChild.h"
67 #include "mozilla/ipc/PBackgroundChild.h"
68 #include "mozilla/StorageAccess.h"
69 #include "mozilla/StoragePrincipalHelper.h"
70 #include "mozilla/Telemetry.h"
71 #include "mozilla/ThreadEventQueue.h"
72 #include "mozilla/ThreadSafety.h"
73 #include "mozilla/ThrottledEventQueue.h"
74 #include "nsCycleCollector.h"
75 #include "nsGlobalWindowInner.h"
76 #include "nsIDUtils.h"
77 #include "nsNetUtil.h"
78 #include "nsIFile.h"
79 #include "nsIMemoryReporter.h"
80 #include "nsIPermissionManager.h"
81 #include "nsIProtocolHandler.h"
82 #include "nsIScriptError.h"
83 #include "nsIURI.h"
84 #include "nsIURL.h"
85 #include "nsIUUIDGenerator.h"
86 #include "nsPrintfCString.h"
87 #include "nsProxyRelease.h"
88 #include "nsQueryObject.h"
89 #include "nsRFPService.h"
90 #include "nsSandboxFlags.h"
91 #include "nsThreadUtils.h"
92 #include "nsUTF8Utils.h"
94 #include "RuntimeService.h"
95 #include "ScriptLoader.h"
96 #include "mozilla/dom/ServiceWorkerEvents.h"
97 #include "mozilla/dom/ServiceWorkerManager.h"
98 #include "mozilla/net/CookieJarSettings.h"
99 #include "WorkerCSPEventListener.h"
100 #include "WorkerDebugger.h"
101 #include "WorkerDebuggerManager.h"
102 #include "WorkerError.h"
103 #include "WorkerEventTarget.h"
104 #include "WorkerNavigator.h"
105 #include "WorkerRef.h"
106 #include "WorkerRunnable.h"
107 #include "WorkerThread.h"
108 #include "nsContentSecurityManager.h"
110 #include "nsThreadManager.h"
112 #ifdef XP_WIN
113 # undef PostMessage
114 #endif
116 // JS_MaybeGC will run once every second during normal execution.
117 #define PERIODIC_GC_TIMER_DELAY_SEC 1
119 // A shrinking GC will run five seconds after the last event is processed.
120 #define IDLE_GC_TIMER_DELAY_SEC 5
122 static mozilla::LazyLogModule sWorkerPrivateLog("WorkerPrivate");
123 static mozilla::LazyLogModule sWorkerTimeoutsLog("WorkerTimeouts");
125 mozilla::LogModule* WorkerLog() { return sWorkerPrivateLog; }
127 mozilla::LogModule* TimeoutsLog() { return sWorkerTimeoutsLog; }
129 #ifdef LOG
130 # undef LOG
131 #endif
132 #ifdef LOGV
133 # undef LOGV
134 #endif
135 #define LOG(log, _args) MOZ_LOG(log, LogLevel::Debug, _args);
136 #define LOGV(args) MOZ_LOG(sWorkerPrivateLog, LogLevel::Verbose, args);
138 namespace mozilla {
140 using namespace ipc;
142 namespace dom {
144 using namespace workerinternals;
146 MOZ_DEFINE_MALLOC_SIZE_OF(JsWorkerMallocSizeOf)
148 namespace {
150 #ifdef DEBUG
152 const nsIID kDEBUGWorkerEventTargetIID = {
153 0xccaba3fa,
154 0x5be2,
155 0x4de2,
156 {0xba, 0x87, 0x3b, 0x3b, 0x5b, 0x1d, 0x5, 0xfb}};
158 #endif
160 template <class T>
161 class UniquePtrComparator {
162 using A = UniquePtr<T>;
163 using B = T*;
165 public:
166 bool Equals(const A& a, const A& b) const {
167 return (a && b) ? (*a == *b) : (!a && !b);
169 bool LessThan(const A& a, const A& b) const {
170 return (a && b) ? (*a < *b) : !!b;
174 template <class T>
175 inline UniquePtrComparator<T> GetUniquePtrComparator(
176 const nsTArray<UniquePtr<T>>&) {
177 return UniquePtrComparator<T>();
180 // This class is used to wrap any runnables that the worker receives via the
181 // nsIEventTarget::Dispatch() method (either from NS_DispatchToCurrentThread or
182 // from the worker's EventTarget).
183 class ExternalRunnableWrapper final : public WorkerRunnable {
184 nsCOMPtr<nsIRunnable> mWrappedRunnable;
186 public:
187 ExternalRunnableWrapper(WorkerPrivate* aWorkerPrivate,
188 nsIRunnable* aWrappedRunnable)
189 : WorkerRunnable(aWorkerPrivate, "ExternalRunnableWrapper", WorkerThread),
190 mWrappedRunnable(aWrappedRunnable) {
191 MOZ_ASSERT(aWorkerPrivate);
192 MOZ_ASSERT(aWrappedRunnable);
195 NS_INLINE_DECL_REFCOUNTING_INHERITED(ExternalRunnableWrapper, WorkerRunnable)
197 private:
198 ~ExternalRunnableWrapper() = default;
200 virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
201 // Silence bad assertions.
202 return true;
205 virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
206 bool aDispatchResult) override {
207 // Silence bad assertions.
210 virtual bool WorkerRun(JSContext* aCx,
211 WorkerPrivate* aWorkerPrivate) override {
212 nsresult rv = mWrappedRunnable->Run();
213 mWrappedRunnable = nullptr;
214 if (NS_FAILED(rv)) {
215 if (!JS_IsExceptionPending(aCx)) {
216 Throw(aCx, rv);
218 return false;
220 return true;
223 nsresult Cancel() override {
224 nsCOMPtr<nsIDiscardableRunnable> doomed =
225 do_QueryInterface(mWrappedRunnable);
226 if (doomed) {
227 doomed->OnDiscard();
229 mWrappedRunnable = nullptr;
230 return NS_OK;
234 struct WindowAction {
235 nsPIDOMWindowInner* mWindow;
236 bool mDefaultAction;
238 MOZ_IMPLICIT WindowAction(nsPIDOMWindowInner* aWindow)
239 : mWindow(aWindow), mDefaultAction(true) {}
241 bool operator==(const WindowAction& aOther) const {
242 return mWindow == aOther.mWindow;
246 class WorkerFinishedRunnable final : public WorkerControlRunnable {
247 WorkerPrivate* mFinishedWorker;
249 public:
250 WorkerFinishedRunnable(WorkerPrivate* aWorkerPrivate,
251 WorkerPrivate* aFinishedWorker)
252 : WorkerControlRunnable(aWorkerPrivate, "WorkerFinishedRunnable",
253 WorkerThread),
254 mFinishedWorker(aFinishedWorker) {
255 aFinishedWorker->IncreaseWorkerFinishedRunnableCount();
258 private:
259 virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
260 // Silence bad assertions.
261 return true;
264 virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
265 bool aDispatchResult) override {
266 // Silence bad assertions.
269 virtual bool WorkerRun(JSContext* aCx,
270 WorkerPrivate* aWorkerPrivate) override {
271 // This may block on the main thread.
272 AutoYieldJSThreadExecution yield;
274 mFinishedWorker->DecreaseWorkerFinishedRunnableCount();
276 if (!mFinishedWorker->ProxyReleaseMainThreadObjects()) {
277 NS_WARNING("Failed to dispatch, going to leak!");
280 RuntimeService* runtime = RuntimeService::GetService();
281 NS_ASSERTION(runtime, "This should never be null!");
283 mFinishedWorker->DisableDebugger();
285 runtime->UnregisterWorker(*mFinishedWorker);
287 mFinishedWorker->ClearSelfAndParentEventTargetRef();
288 return true;
292 class TopLevelWorkerFinishedRunnable final : public Runnable {
293 WorkerPrivate* mFinishedWorker;
295 public:
296 explicit TopLevelWorkerFinishedRunnable(WorkerPrivate* aFinishedWorker)
297 : mozilla::Runnable("TopLevelWorkerFinishedRunnable"),
298 mFinishedWorker(aFinishedWorker) {
299 aFinishedWorker->AssertIsOnWorkerThread();
300 aFinishedWorker->IncreaseTopLevelWorkerFinishedRunnableCount();
303 NS_INLINE_DECL_REFCOUNTING_INHERITED(TopLevelWorkerFinishedRunnable, Runnable)
305 private:
306 ~TopLevelWorkerFinishedRunnable() = default;
308 NS_IMETHOD
309 Run() override {
310 AssertIsOnMainThread();
312 mFinishedWorker->DecreaseTopLevelWorkerFinishedRunnableCount();
314 RuntimeService* runtime = RuntimeService::GetService();
315 MOZ_ASSERT(runtime);
317 mFinishedWorker->DisableDebugger();
319 runtime->UnregisterWorker(*mFinishedWorker);
321 if (!mFinishedWorker->ProxyReleaseMainThreadObjects()) {
322 NS_WARNING("Failed to dispatch, going to leak!");
325 mFinishedWorker->ClearSelfAndParentEventTargetRef();
326 return NS_OK;
330 class CompileScriptRunnable final : public WorkerDebuggeeRunnable {
331 nsString mScriptURL;
332 const mozilla::Encoding* mDocumentEncoding;
333 UniquePtr<SerializedStackHolder> mOriginStack;
335 public:
336 explicit CompileScriptRunnable(WorkerPrivate* aWorkerPrivate,
337 UniquePtr<SerializedStackHolder> aOriginStack,
338 const nsAString& aScriptURL,
339 const mozilla::Encoding* aDocumentEncoding)
340 : WorkerDebuggeeRunnable(aWorkerPrivate, "CompileScriptRunnable",
341 WorkerThread),
342 mScriptURL(aScriptURL),
343 mDocumentEncoding(aDocumentEncoding),
344 mOriginStack(aOriginStack.release()) {}
346 private:
347 // We can't implement PreRun effectively, because at the point when that would
348 // run we have not yet done our load so don't know things like our final
349 // principal and whatnot.
351 virtual bool WorkerRun(JSContext* aCx,
352 WorkerPrivate* aWorkerPrivate) override {
353 aWorkerPrivate->AssertIsOnWorkerThread();
355 WorkerGlobalScope* globalScope =
356 aWorkerPrivate->GetOrCreateGlobalScope(aCx);
357 if (NS_WARN_IF(!globalScope)) {
358 return false;
361 if (NS_WARN_IF(!aWorkerPrivate->EnsureCSPEventListener())) {
362 return false;
365 ErrorResult rv;
366 workerinternals::LoadMainScript(aWorkerPrivate, std::move(mOriginStack),
367 mScriptURL, WorkerScript, rv,
368 mDocumentEncoding);
370 if (aWorkerPrivate->ExtensionAPIAllowed()) {
371 MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
372 RefPtr<Runnable> extWorkerRunnable =
373 extensions::CreateWorkerLoadedRunnable(
374 aWorkerPrivate->ServiceWorkerID(), aWorkerPrivate->GetBaseURI());
375 // Dispatch as a low priority runnable.
376 if (NS_FAILED(aWorkerPrivate->DispatchToMainThreadForMessaging(
377 extWorkerRunnable.forget()))) {
378 NS_WARNING(
379 "Failed to dispatch runnable to notify extensions worker loaded");
383 rv.WouldReportJSException();
384 // Explicitly ignore NS_BINDING_ABORTED on rv. Or more precisely, still
385 // return false and don't SetWorkerScriptExecutedSuccessfully() in that
386 // case, but don't throw anything on aCx. The idea is to not dispatch error
387 // events if our load is canceled with that error code.
388 if (rv.ErrorCodeIs(NS_BINDING_ABORTED)) {
389 rv.SuppressException();
390 return false;
393 // Make sure to propagate exceptions from rv onto aCx, so that they will get
394 // reported after we return. We want to propagate just JS exceptions,
395 // because all the other errors are handled when the script is loaded.
396 // See: https://dom.spec.whatwg.org/#concept-event-fire
397 if (rv.Failed() && !rv.IsJSException()) {
398 WorkerErrorReport::CreateAndDispatchGenericErrorRunnableToParent(
399 aWorkerPrivate);
400 rv.SuppressException();
401 return false;
404 // This is a little dumb, but aCx is in the null realm here because we
405 // set it up that way in our Run(), since we had not created the global at
406 // that point yet. So we need to enter the realm of our global,
407 // because setting a pending exception on aCx involves wrapping into its
408 // current compartment. Luckily we have a global now.
409 JSAutoRealm ar(aCx, globalScope->GetGlobalJSObject());
410 if (rv.MaybeSetPendingException(aCx)) {
411 // In the event of an uncaught exception, the worker should still keep
412 // running (return true) but should not be marked as having executed
413 // successfully (which will cause ServiceWorker installation to fail).
414 // In previous error handling cases in this method, we return false (to
415 // trigger CloseInternal) because the global is not in an operable
416 // state at all.
418 // For ServiceWorkers, this would correspond to the "Run Service Worker"
419 // algorithm returning an "abrupt completion" and _not_ failure.
421 // For DedicatedWorkers and SharedWorkers, this would correspond to the
422 // "run a worker" algorithm disregarding the return value of "run the
423 // classic script"/"run the module script" in step 24:
425 // "If script is a classic script, then run the classic script script.
426 // Otherwise, it is a module script; run the module script script."
427 return true;
430 aWorkerPrivate->SetWorkerScriptExecutedSuccessfully();
431 return true;
434 void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
435 bool aRunResult) override {
436 if (!aRunResult) {
437 aWorkerPrivate->CloseInternal();
439 WorkerRunnable::PostRun(aCx, aWorkerPrivate, aRunResult);
443 class NotifyRunnable final : public WorkerControlRunnable {
444 WorkerStatus mStatus;
446 public:
447 NotifyRunnable(WorkerPrivate* aWorkerPrivate, WorkerStatus aStatus)
448 : WorkerControlRunnable(aWorkerPrivate, "NotifyRunnable", WorkerThread),
449 mStatus(aStatus) {
450 MOZ_ASSERT(aStatus == Closing || aStatus == Canceling ||
451 aStatus == Killing);
454 private:
455 virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
456 aWorkerPrivate->AssertIsOnParentThread();
457 return true;
460 virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
461 bool aDispatchResult) override {
462 aWorkerPrivate->AssertIsOnParentThread();
465 virtual bool WorkerRun(JSContext* aCx,
466 WorkerPrivate* aWorkerPrivate) override {
467 return aWorkerPrivate->NotifyInternal(mStatus);
470 virtual void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
471 bool aRunResult) override {}
474 class FreezeRunnable final : public WorkerControlRunnable {
475 public:
476 explicit FreezeRunnable(WorkerPrivate* aWorkerPrivate)
477 : WorkerControlRunnable(aWorkerPrivate, "FreezeRunnable", WorkerThread) {}
479 private:
480 virtual bool WorkerRun(JSContext* aCx,
481 WorkerPrivate* aWorkerPrivate) override {
482 return aWorkerPrivate->FreezeInternal();
486 class ThawRunnable final : public WorkerControlRunnable {
487 public:
488 explicit ThawRunnable(WorkerPrivate* aWorkerPrivate)
489 : WorkerControlRunnable(aWorkerPrivate, "ThawRunnable", WorkerThread) {}
491 private:
492 virtual bool WorkerRun(JSContext* aCx,
493 WorkerPrivate* aWorkerPrivate) override {
494 return aWorkerPrivate->ThawInternal();
498 class PropagateStorageAccessPermissionGrantedRunnable final
499 : public WorkerControlRunnable {
500 public:
501 explicit PropagateStorageAccessPermissionGrantedRunnable(
502 WorkerPrivate* aWorkerPrivate)
503 : WorkerControlRunnable(aWorkerPrivate,
504 "PropagateStorageAccessPermissionGrantedRunnable",
505 WorkerThread) {}
507 private:
508 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
509 aWorkerPrivate->PropagateStorageAccessPermissionGrantedInternal();
510 return true;
514 class ReportErrorToConsoleRunnable final : public WorkerRunnable {
515 const char* mMessage;
516 const nsTArray<nsString> mParams;
518 public:
519 // aWorkerPrivate is the worker thread we're on (or the main thread, if null)
520 static void Report(WorkerPrivate* aWorkerPrivate, const char* aMessage,
521 const nsTArray<nsString>& aParams) {
522 if (aWorkerPrivate) {
523 aWorkerPrivate->AssertIsOnWorkerThread();
524 } else {
525 AssertIsOnMainThread();
528 // Now fire a runnable to do the same on the parent's thread if we can.
529 if (aWorkerPrivate) {
530 RefPtr<ReportErrorToConsoleRunnable> runnable =
531 new ReportErrorToConsoleRunnable(aWorkerPrivate, aMessage, aParams);
532 runnable->Dispatch();
533 return;
536 // Log a warning to the console.
537 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
538 nullptr, nsContentUtils::eDOM_PROPERTIES,
539 aMessage, aParams);
542 private:
543 ReportErrorToConsoleRunnable(WorkerPrivate* aWorkerPrivate,
544 const char* aMessage,
545 const nsTArray<nsString>& aParams)
546 : WorkerRunnable(aWorkerPrivate, "ReportErrorToConsoleRunnable",
547 ParentThread),
548 mMessage(aMessage),
549 mParams(aParams.Clone()) {}
551 virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
552 bool aDispatchResult) override {
553 aWorkerPrivate->AssertIsOnWorkerThread();
555 // Dispatch may fail if the worker was canceled, no need to report that as
556 // an error, so don't call base class PostDispatch.
559 virtual bool WorkerRun(JSContext* aCx,
560 WorkerPrivate* aWorkerPrivate) override {
561 WorkerPrivate* parent = aWorkerPrivate->GetParent();
562 MOZ_ASSERT_IF(!parent, NS_IsMainThread());
563 Report(parent, mMessage, mParams);
564 return true;
568 class RunExpiredTimoutsRunnable final : public WorkerRunnable,
569 public nsITimerCallback {
570 public:
571 NS_DECL_ISUPPORTS_INHERITED
573 explicit RunExpiredTimoutsRunnable(WorkerPrivate* aWorkerPrivate)
574 : WorkerRunnable(aWorkerPrivate, "RunExpiredTimoutsRunnable",
575 WorkerThread) {}
577 private:
578 ~RunExpiredTimoutsRunnable() = default;
580 virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
581 // Silence bad assertions.
582 return true;
585 virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
586 bool aDispatchResult) override {
587 // Silence bad assertions.
590 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until worker runnables are generally
591 // MOZ_CAN_RUN_SCRIPT.
592 MOZ_CAN_RUN_SCRIPT_BOUNDARY
593 virtual bool WorkerRun(JSContext* aCx,
594 WorkerPrivate* aWorkerPrivate) override {
595 return aWorkerPrivate->RunExpiredTimeouts(aCx);
598 NS_IMETHOD
599 Notify(nsITimer* aTimer) override { return Run(); }
602 NS_IMPL_ISUPPORTS_INHERITED(RunExpiredTimoutsRunnable, WorkerRunnable,
603 nsITimerCallback)
605 class DebuggerImmediateRunnable final : public WorkerRunnable {
606 RefPtr<dom::Function> mHandler;
608 public:
609 explicit DebuggerImmediateRunnable(WorkerPrivate* aWorkerPrivate,
610 dom::Function& aHandler)
611 : WorkerRunnable(aWorkerPrivate, "DebuggerImmediateRunnable",
612 WorkerThread),
613 mHandler(&aHandler) {}
615 private:
616 virtual bool IsDebuggerRunnable() const override { return true; }
618 virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
619 // Silence bad assertions.
620 return true;
623 virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
624 bool aDispatchResult) override {
625 // Silence bad assertions.
628 virtual bool WorkerRun(JSContext* aCx,
629 WorkerPrivate* aWorkerPrivate) override {
630 JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
631 JS::Rooted<JS::Value> callable(
632 aCx, JS::ObjectOrNullValue(mHandler->CallableOrNull()));
633 JS::HandleValueArray args = JS::HandleValueArray::empty();
634 JS::Rooted<JS::Value> rval(aCx);
636 // WorkerRunnable::Run will report the exception if it happens.
637 return JS_CallFunctionValue(aCx, global, callable, args, &rval);
641 // GetJSContext() is safe on the worker thread
642 void PeriodicGCTimerCallback(nsITimer* aTimer,
643 void* aClosure) MOZ_NO_THREAD_SAFETY_ANALYSIS {
644 auto* workerPrivate = static_cast<WorkerPrivate*>(aClosure);
645 MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
646 workerPrivate->AssertIsOnWorkerThread();
647 workerPrivate->GarbageCollectInternal(workerPrivate->GetJSContext(),
648 false /* shrinking */,
649 false /* collect children */);
650 LOG(WorkerLog(), ("Worker %p run periodic GC\n", workerPrivate));
653 void IdleGCTimerCallback(nsITimer* aTimer,
654 void* aClosure) MOZ_NO_THREAD_SAFETY_ANALYSIS {
655 auto* workerPrivate = static_cast<WorkerPrivate*>(aClosure);
656 MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
657 workerPrivate->AssertIsOnWorkerThread();
658 workerPrivate->GarbageCollectInternal(workerPrivate->GetJSContext(),
659 true /* shrinking */,
660 false /* collect children */);
661 LOG(WorkerLog(), ("Worker %p run idle GC\n", workerPrivate));
663 // After running idle GC we can cancel the current timers.
664 workerPrivate->CancelGCTimers();
667 class UpdateContextOptionsRunnable final : public WorkerControlRunnable {
668 JS::ContextOptions mContextOptions;
670 public:
671 UpdateContextOptionsRunnable(WorkerPrivate* aWorkerPrivate,
672 const JS::ContextOptions& aContextOptions)
673 : WorkerControlRunnable(aWorkerPrivate, "UpdateContextOptionsRunnable",
674 WorkerThread),
675 mContextOptions(aContextOptions) {}
677 private:
678 virtual bool WorkerRun(JSContext* aCx,
679 WorkerPrivate* aWorkerPrivate) override {
680 aWorkerPrivate->UpdateContextOptionsInternal(aCx, mContextOptions);
681 return true;
685 class UpdateLanguagesRunnable final : public WorkerRunnable {
686 nsTArray<nsString> mLanguages;
688 public:
689 UpdateLanguagesRunnable(WorkerPrivate* aWorkerPrivate,
690 const nsTArray<nsString>& aLanguages)
691 : WorkerRunnable(aWorkerPrivate, "UpdateLanguagesRunnable"),
692 mLanguages(aLanguages.Clone()) {}
694 virtual bool WorkerRun(JSContext* aCx,
695 WorkerPrivate* aWorkerPrivate) override {
696 aWorkerPrivate->UpdateLanguagesInternal(mLanguages);
697 return true;
701 class UpdateJSWorkerMemoryParameterRunnable final
702 : public WorkerControlRunnable {
703 Maybe<uint32_t> mValue;
704 JSGCParamKey mKey;
706 public:
707 UpdateJSWorkerMemoryParameterRunnable(WorkerPrivate* aWorkerPrivate,
708 JSGCParamKey aKey,
709 Maybe<uint32_t> aValue)
710 : WorkerControlRunnable(aWorkerPrivate,
711 "UpdateJSWorkerMemoryParameterRunnable",
712 WorkerThread),
713 mValue(aValue),
714 mKey(aKey) {}
716 private:
717 virtual bool WorkerRun(JSContext* aCx,
718 WorkerPrivate* aWorkerPrivate) override {
719 aWorkerPrivate->UpdateJSWorkerMemoryParameterInternal(aCx, mKey, mValue);
720 return true;
724 #ifdef JS_GC_ZEAL
725 class UpdateGCZealRunnable final : public WorkerControlRunnable {
726 uint8_t mGCZeal;
727 uint32_t mFrequency;
729 public:
730 UpdateGCZealRunnable(WorkerPrivate* aWorkerPrivate, uint8_t aGCZeal,
731 uint32_t aFrequency)
732 : WorkerControlRunnable(aWorkerPrivate, "UpdateGCZealRunnable",
733 WorkerThread),
734 mGCZeal(aGCZeal),
735 mFrequency(aFrequency) {}
737 private:
738 virtual bool WorkerRun(JSContext* aCx,
739 WorkerPrivate* aWorkerPrivate) override {
740 aWorkerPrivate->UpdateGCZealInternal(aCx, mGCZeal, mFrequency);
741 return true;
744 #endif
746 class SetLowMemoryStateRunnable final : public WorkerControlRunnable {
747 bool mState;
749 public:
750 SetLowMemoryStateRunnable(WorkerPrivate* aWorkerPrivate, bool aState)
751 : WorkerControlRunnable(aWorkerPrivate, "SetLowMemoryStateRunnable",
752 WorkerThread),
753 mState(aState) {}
755 private:
756 virtual bool WorkerRun(JSContext* aCx,
757 WorkerPrivate* aWorkerPrivate) override {
758 aWorkerPrivate->SetLowMemoryStateInternal(aCx, mState);
759 return true;
763 class GarbageCollectRunnable final : public WorkerControlRunnable {
764 bool mShrinking;
765 bool mCollectChildren;
767 public:
768 GarbageCollectRunnable(WorkerPrivate* aWorkerPrivate, bool aShrinking,
769 bool aCollectChildren)
770 : WorkerControlRunnable(aWorkerPrivate, "GarbageCollectRunnable",
771 WorkerThread),
772 mShrinking(aShrinking),
773 mCollectChildren(aCollectChildren) {}
775 private:
776 virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
777 // Silence bad assertions, this can be dispatched from either the main
778 // thread or the timer thread..
779 return true;
782 virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
783 bool aDispatchResult) override {
784 // Silence bad assertions, this can be dispatched from either the main
785 // thread or the timer thread..
788 virtual bool WorkerRun(JSContext* aCx,
789 WorkerPrivate* aWorkerPrivate) override {
790 aWorkerPrivate->GarbageCollectInternal(aCx, mShrinking, mCollectChildren);
791 if (mShrinking) {
792 // Either we've run the idle GC or explicit GC call from the parent,
793 // we can cancel the current timers.
794 aWorkerPrivate->CancelGCTimers();
796 return true;
800 class CycleCollectRunnable final : public WorkerControlRunnable {
801 bool mCollectChildren;
803 public:
804 CycleCollectRunnable(WorkerPrivate* aWorkerPrivate, bool aCollectChildren)
805 : WorkerControlRunnable(aWorkerPrivate, "CycleCollectRunnable",
806 WorkerThread),
807 mCollectChildren(aCollectChildren) {}
809 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
810 aWorkerPrivate->CycleCollectInternal(mCollectChildren);
811 return true;
815 class OfflineStatusChangeRunnable final : public WorkerRunnable {
816 public:
817 OfflineStatusChangeRunnable(WorkerPrivate* aWorkerPrivate, bool aIsOffline)
818 : WorkerRunnable(aWorkerPrivate, "OfflineStatusChangeRunnable"),
819 mIsOffline(aIsOffline) {}
821 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
822 aWorkerPrivate->OfflineStatusChangeEventInternal(mIsOffline);
823 return true;
826 private:
827 bool mIsOffline;
830 class MemoryPressureRunnable final : public WorkerControlRunnable {
831 public:
832 explicit MemoryPressureRunnable(WorkerPrivate* aWorkerPrivate)
833 : WorkerControlRunnable(aWorkerPrivate, "MemoryPressureRunnable",
834 WorkerThread) {}
836 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
837 aWorkerPrivate->MemoryPressureInternal();
838 return true;
842 #ifdef DEBUG
843 static bool StartsWithExplicit(nsACString& s) {
844 return StringBeginsWith(s, "explicit/"_ns);
846 #endif
848 PRThread* PRThreadFromThread(nsIThread* aThread) {
849 MOZ_ASSERT(aThread);
851 PRThread* result;
852 MOZ_ALWAYS_SUCCEEDS(aThread->GetPRThread(&result));
853 MOZ_ASSERT(result);
855 return result;
858 // A runnable to cancel the worker from the parent thread when self.close() is
859 // called. This runnable is executed on the parent process in order to cancel
860 // the current runnable. It uses a normal WorkerDebuggeeRunnable in order to be
861 // sure that all the pending WorkerDebuggeeRunnables are executed before this.
862 class CancelingOnParentRunnable final : public WorkerDebuggeeRunnable {
863 public:
864 explicit CancelingOnParentRunnable(WorkerPrivate* aWorkerPrivate)
865 : WorkerDebuggeeRunnable(aWorkerPrivate, "CancelingOnParentRunnable",
866 ParentThread) {}
868 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
869 aWorkerPrivate->Cancel();
870 return true;
874 // A runnable to cancel the worker from the parent process.
875 class CancelingWithTimeoutOnParentRunnable final
876 : public WorkerControlRunnable {
877 public:
878 explicit CancelingWithTimeoutOnParentRunnable(WorkerPrivate* aWorkerPrivate)
879 : WorkerControlRunnable(aWorkerPrivate,
880 "CancelingWithTimeoutOnParentRunnable",
881 ParentThread) {}
883 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
884 aWorkerPrivate->AssertIsOnParentThread();
885 aWorkerPrivate->StartCancelingTimer();
886 return true;
890 class CancelingTimerCallback final : public nsITimerCallback {
891 public:
892 NS_DECL_ISUPPORTS
894 explicit CancelingTimerCallback(WorkerPrivate* aWorkerPrivate)
895 : mWorkerPrivate(aWorkerPrivate) {}
897 NS_IMETHOD
898 Notify(nsITimer* aTimer) override {
899 mWorkerPrivate->AssertIsOnParentThread();
900 mWorkerPrivate->Cancel();
901 return NS_OK;
904 private:
905 ~CancelingTimerCallback() = default;
907 // Raw pointer here is OK because the timer is canceled during the shutdown
908 // steps.
909 WorkerPrivate* mWorkerPrivate;
912 NS_IMPL_ISUPPORTS(CancelingTimerCallback, nsITimerCallback)
914 // This runnable starts the canceling of a worker after a self.close().
915 class CancelingRunnable final : public Runnable {
916 public:
917 CancelingRunnable() : Runnable("CancelingRunnable") {}
919 NS_IMETHOD
920 Run() override {
921 LOG(WorkerLog(), ("CancelingRunnable::Run [%p]", this));
922 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
923 MOZ_ASSERT(workerPrivate);
924 workerPrivate->AssertIsOnWorkerThread();
926 // Now we can cancel the this worker from the parent process.
927 RefPtr<CancelingOnParentRunnable> r =
928 new CancelingOnParentRunnable(workerPrivate);
929 r->Dispatch();
931 return NS_OK;
935 } /* anonymous namespace */
937 nsString ComputeWorkerPrivateId() {
938 nsID uuid = nsID::GenerateUUID();
939 return NSID_TrimBracketsUTF16(uuid);
942 class WorkerPrivate::EventTarget final : public nsISerialEventTarget {
943 // This mutex protects mWorkerPrivate and must be acquired *before* the
944 // WorkerPrivate's mutex whenever they must both be held.
945 mozilla::Mutex mMutex;
946 WorkerPrivate* mWorkerPrivate MOZ_GUARDED_BY(mMutex);
947 nsCOMPtr<nsIEventTarget> mNestedEventTarget MOZ_GUARDED_BY(mMutex);
948 bool mDisabled MOZ_GUARDED_BY(mMutex);
949 bool mShutdown MOZ_GUARDED_BY(mMutex);
951 public:
952 EventTarget(WorkerPrivate* aWorkerPrivate, nsIEventTarget* aNestedEventTarget)
953 : mMutex("WorkerPrivate::EventTarget::mMutex"),
954 mWorkerPrivate(aWorkerPrivate),
955 mNestedEventTarget(aNestedEventTarget),
956 mDisabled(false),
957 mShutdown(false) {
958 MOZ_ASSERT(aWorkerPrivate);
959 MOZ_ASSERT(aNestedEventTarget);
962 void Disable() {
964 MutexAutoLock lock(mMutex);
966 // Note, Disable() can be called more than once safely.
967 mDisabled = true;
971 void Shutdown() {
972 nsCOMPtr<nsIEventTarget> nestedEventTarget;
974 MutexAutoLock lock(mMutex);
976 mWorkerPrivate = nullptr;
977 mNestedEventTarget.swap(nestedEventTarget);
978 MOZ_ASSERT(mDisabled);
979 mShutdown = true;
983 RefPtr<nsIEventTarget> GetNestedEventTarget() {
984 RefPtr<nsIEventTarget> nestedEventTarget = nullptr;
986 MutexAutoLock lock(mMutex);
987 if (mWorkerPrivate) {
988 mWorkerPrivate->AssertIsOnWorkerThread();
989 nestedEventTarget = mNestedEventTarget.get();
992 return nestedEventTarget;
995 NS_DECL_THREADSAFE_ISUPPORTS
996 NS_DECL_NSIEVENTTARGET_FULL
998 private:
999 ~EventTarget() = default;
1002 struct WorkerPrivate::TimeoutInfo {
1003 TimeoutInfo()
1004 : mId(0),
1005 mNestingLevel(0),
1006 mReason(Timeout::Reason::eTimeoutOrInterval),
1007 mIsInterval(false),
1008 mCanceled(false),
1009 mOnChromeWorker(false) {
1010 MOZ_COUNT_CTOR(mozilla::dom::WorkerPrivate::TimeoutInfo);
1013 ~TimeoutInfo() { MOZ_COUNT_DTOR(mozilla::dom::WorkerPrivate::TimeoutInfo); }
1015 bool operator==(const TimeoutInfo& aOther) const {
1016 return mTargetTime == aOther.mTargetTime;
1019 bool operator<(const TimeoutInfo& aOther) const {
1020 return mTargetTime < aOther.mTargetTime;
1023 void AccumulateNestingLevel(const uint32_t& aBaseLevel) {
1024 if (aBaseLevel < StaticPrefs::dom_clamp_timeout_nesting_level_AtStartup()) {
1025 mNestingLevel = aBaseLevel + 1;
1026 return;
1028 mNestingLevel = StaticPrefs::dom_clamp_timeout_nesting_level_AtStartup();
1031 void CalculateTargetTime() {
1032 auto target = mInterval;
1033 // Don't clamp timeout for chrome workers
1034 if (mNestingLevel >=
1035 StaticPrefs::dom_clamp_timeout_nesting_level_AtStartup() &&
1036 !mOnChromeWorker) {
1037 target = TimeDuration::Max(
1038 mInterval,
1039 TimeDuration::FromMilliseconds(StaticPrefs::dom_min_timeout_value()));
1041 mTargetTime = TimeStamp::Now() + target;
1044 RefPtr<TimeoutHandler> mHandler;
1045 mozilla::TimeStamp mTargetTime;
1046 mozilla::TimeDuration mInterval;
1047 int32_t mId;
1048 uint32_t mNestingLevel;
1049 Timeout::Reason mReason;
1050 bool mIsInterval;
1051 bool mCanceled;
1052 bool mOnChromeWorker;
1055 class WorkerJSContextStats final : public JS::RuntimeStats {
1056 const nsCString mRtPath;
1058 public:
1059 explicit WorkerJSContextStats(const nsACString& aRtPath)
1060 : JS::RuntimeStats(JsWorkerMallocSizeOf), mRtPath(aRtPath) {}
1062 ~WorkerJSContextStats() {
1063 for (JS::ZoneStats& stats : zoneStatsVector) {
1064 delete static_cast<xpc::ZoneStatsExtras*>(stats.extra);
1067 for (JS::RealmStats& stats : realmStatsVector) {
1068 delete static_cast<xpc::RealmStatsExtras*>(stats.extra);
1072 const nsCString& Path() const { return mRtPath; }
1074 virtual void initExtraZoneStats(JS::Zone* aZone, JS::ZoneStats* aZoneStats,
1075 const JS::AutoRequireNoGC& nogc) override {
1076 MOZ_ASSERT(!aZoneStats->extra);
1078 // ReportJSRuntimeExplicitTreeStats expects that
1079 // aZoneStats->extra is a xpc::ZoneStatsExtras pointer.
1080 xpc::ZoneStatsExtras* extras = new xpc::ZoneStatsExtras;
1081 extras->pathPrefix = mRtPath;
1082 extras->pathPrefix += nsPrintfCString("zone(0x%p)/", (void*)aZone);
1084 MOZ_ASSERT(StartsWithExplicit(extras->pathPrefix));
1086 aZoneStats->extra = extras;
1089 virtual void initExtraRealmStats(JS::Realm* aRealm,
1090 JS::RealmStats* aRealmStats,
1091 const JS::AutoRequireNoGC& nogc) override {
1092 MOZ_ASSERT(!aRealmStats->extra);
1094 // ReportJSRuntimeExplicitTreeStats expects that
1095 // aRealmStats->extra is a xpc::RealmStatsExtras pointer.
1096 xpc::RealmStatsExtras* extras = new xpc::RealmStatsExtras;
1098 // This is the |jsPathPrefix|. Each worker has exactly one realm.
1099 extras->jsPathPrefix.Assign(mRtPath);
1100 extras->jsPathPrefix +=
1101 nsPrintfCString("zone(0x%p)/", (void*)js::GetRealmZone(aRealm));
1102 extras->jsPathPrefix += "realm(web-worker)/"_ns;
1104 // This should never be used when reporting with workers (hence the "?!").
1105 extras->domPathPrefix.AssignLiteral("explicit/workers/?!/");
1107 MOZ_ASSERT(StartsWithExplicit(extras->jsPathPrefix));
1108 MOZ_ASSERT(StartsWithExplicit(extras->domPathPrefix));
1110 extras->location = nullptr;
1112 aRealmStats->extra = extras;
1116 class WorkerPrivate::MemoryReporter final : public nsIMemoryReporter {
1117 NS_DECL_THREADSAFE_ISUPPORTS
1119 friend class WorkerPrivate;
1121 SharedMutex mMutex;
1122 WorkerPrivate* mWorkerPrivate;
1124 public:
1125 explicit MemoryReporter(WorkerPrivate* aWorkerPrivate)
1126 : mMutex(aWorkerPrivate->mMutex), mWorkerPrivate(aWorkerPrivate) {
1127 aWorkerPrivate->AssertIsOnWorkerThread();
1130 NS_IMETHOD
1131 CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData,
1132 bool aAnonymize) override;
1134 private:
1135 class FinishCollectRunnable;
1137 class CollectReportsRunnable final : public MainThreadWorkerControlRunnable {
1138 RefPtr<FinishCollectRunnable> mFinishCollectRunnable;
1139 const bool mAnonymize;
1141 public:
1142 CollectReportsRunnable(WorkerPrivate* aWorkerPrivate,
1143 nsIHandleReportCallback* aHandleReport,
1144 nsISupports* aHandlerData, bool aAnonymize,
1145 const nsACString& aPath);
1147 private:
1148 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override;
1150 ~CollectReportsRunnable() {
1151 if (NS_IsMainThread()) {
1152 mFinishCollectRunnable->Run();
1153 return;
1156 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
1157 MOZ_ASSERT(workerPrivate);
1158 MOZ_ALWAYS_SUCCEEDS(workerPrivate->DispatchToMainThreadForMessaging(
1159 mFinishCollectRunnable.forget()));
1163 class FinishCollectRunnable final : public Runnable {
1164 nsCOMPtr<nsIHandleReportCallback> mHandleReport;
1165 nsCOMPtr<nsISupports> mHandlerData;
1166 size_t mPerformanceUserEntries;
1167 size_t mPerformanceResourceEntries;
1168 const bool mAnonymize;
1169 bool mSuccess;
1171 public:
1172 WorkerJSContextStats mCxStats;
1174 explicit FinishCollectRunnable(nsIHandleReportCallback* aHandleReport,
1175 nsISupports* aHandlerData, bool aAnonymize,
1176 const nsACString& aPath);
1178 NS_IMETHOD Run() override;
1180 void SetPerformanceSizes(size_t userEntries, size_t resourceEntries) {
1181 mPerformanceUserEntries = userEntries;
1182 mPerformanceResourceEntries = resourceEntries;
1185 void SetSuccess(bool success) { mSuccess = success; }
1187 FinishCollectRunnable(const FinishCollectRunnable&) = delete;
1188 FinishCollectRunnable& operator=(const FinishCollectRunnable&) = delete;
1189 FinishCollectRunnable& operator=(const FinishCollectRunnable&&) = delete;
1191 private:
1192 ~FinishCollectRunnable() {
1193 // mHandleReport and mHandlerData are released on the main thread.
1194 AssertIsOnMainThread();
1198 ~MemoryReporter() = default;
1200 void Disable() {
1201 // Called from WorkerPrivate::DisableMemoryReporter.
1202 mMutex.AssertCurrentThreadOwns();
1204 NS_ASSERTION(mWorkerPrivate, "Disabled more than once!");
1205 mWorkerPrivate = nullptr;
1209 NS_IMPL_ISUPPORTS(WorkerPrivate::MemoryReporter, nsIMemoryReporter)
1211 NS_IMETHODIMP
1212 WorkerPrivate::MemoryReporter::CollectReports(
1213 nsIHandleReportCallback* aHandleReport, nsISupports* aData,
1214 bool aAnonymize) {
1215 AssertIsOnMainThread();
1217 RefPtr<CollectReportsRunnable> runnable;
1220 MutexAutoLock lock(mMutex);
1222 if (!mWorkerPrivate) {
1223 // This will effectively report 0 memory.
1224 nsCOMPtr<nsIMemoryReporterManager> manager =
1225 do_GetService("@mozilla.org/memory-reporter-manager;1");
1226 if (manager) {
1227 manager->EndReport();
1229 return NS_OK;
1232 nsAutoCString path;
1233 path.AppendLiteral("explicit/workers/workers(");
1234 if (aAnonymize && !mWorkerPrivate->Domain().IsEmpty()) {
1235 path.AppendLiteral("<anonymized-domain>)/worker(<anonymized-url>");
1236 } else {
1237 nsAutoCString escapedDomain(mWorkerPrivate->Domain());
1238 if (escapedDomain.IsEmpty()) {
1239 escapedDomain += "chrome";
1240 } else {
1241 escapedDomain.ReplaceChar('/', '\\');
1243 path.Append(escapedDomain);
1244 path.AppendLiteral(")/worker(");
1245 NS_ConvertUTF16toUTF8 escapedURL(mWorkerPrivate->ScriptURL());
1246 escapedURL.ReplaceChar('/', '\\');
1247 path.Append(escapedURL);
1249 path.AppendPrintf(", 0x%p)/", static_cast<void*>(mWorkerPrivate));
1251 runnable = new CollectReportsRunnable(mWorkerPrivate, aHandleReport, aData,
1252 aAnonymize, path);
1255 if (!runnable->Dispatch()) {
1256 return NS_ERROR_UNEXPECTED;
1259 return NS_OK;
1262 WorkerPrivate::MemoryReporter::CollectReportsRunnable::CollectReportsRunnable(
1263 WorkerPrivate* aWorkerPrivate, nsIHandleReportCallback* aHandleReport,
1264 nsISupports* aHandlerData, bool aAnonymize, const nsACString& aPath)
1265 : MainThreadWorkerControlRunnable(aWorkerPrivate),
1266 mFinishCollectRunnable(new FinishCollectRunnable(
1267 aHandleReport, aHandlerData, aAnonymize, aPath)),
1268 mAnonymize(aAnonymize) {}
1270 bool WorkerPrivate::MemoryReporter::CollectReportsRunnable::WorkerRun(
1271 JSContext* aCx, WorkerPrivate* aWorkerPrivate) {
1272 aWorkerPrivate->AssertIsOnWorkerThread();
1274 RefPtr<WorkerGlobalScope> scope = aWorkerPrivate->GlobalScope();
1275 RefPtr<Performance> performance =
1276 scope ? scope->GetPerformanceIfExists() : nullptr;
1277 if (performance) {
1278 size_t userEntries = performance->SizeOfUserEntries(JsWorkerMallocSizeOf);
1279 size_t resourceEntries =
1280 performance->SizeOfResourceEntries(JsWorkerMallocSizeOf);
1281 mFinishCollectRunnable->SetPerformanceSizes(userEntries, resourceEntries);
1284 mFinishCollectRunnable->SetSuccess(aWorkerPrivate->CollectRuntimeStats(
1285 &mFinishCollectRunnable->mCxStats, mAnonymize));
1287 return true;
1290 WorkerPrivate::MemoryReporter::FinishCollectRunnable::FinishCollectRunnable(
1291 nsIHandleReportCallback* aHandleReport, nsISupports* aHandlerData,
1292 bool aAnonymize, const nsACString& aPath)
1293 : mozilla::Runnable(
1294 "dom::WorkerPrivate::MemoryReporter::FinishCollectRunnable"),
1295 mHandleReport(aHandleReport),
1296 mHandlerData(aHandlerData),
1297 mPerformanceUserEntries(0),
1298 mPerformanceResourceEntries(0),
1299 mAnonymize(aAnonymize),
1300 mSuccess(false),
1301 mCxStats(aPath) {}
1303 NS_IMETHODIMP
1304 WorkerPrivate::MemoryReporter::FinishCollectRunnable::Run() {
1305 AssertIsOnMainThread();
1307 nsCOMPtr<nsIMemoryReporterManager> manager =
1308 do_GetService("@mozilla.org/memory-reporter-manager;1");
1310 if (!manager) return NS_OK;
1312 if (mSuccess) {
1313 xpc::ReportJSRuntimeExplicitTreeStats(
1314 mCxStats, mCxStats.Path(), mHandleReport, mHandlerData, mAnonymize);
1316 if (mPerformanceUserEntries) {
1317 nsCString path = mCxStats.Path();
1318 path.AppendLiteral("dom/performance/user-entries");
1319 mHandleReport->Callback(""_ns, path, nsIMemoryReporter::KIND_HEAP,
1320 nsIMemoryReporter::UNITS_BYTES,
1321 static_cast<int64_t>(mPerformanceUserEntries),
1322 "Memory used for performance user entries."_ns,
1323 mHandlerData);
1326 if (mPerformanceResourceEntries) {
1327 nsCString path = mCxStats.Path();
1328 path.AppendLiteral("dom/performance/resource-entries");
1329 mHandleReport->Callback(
1330 ""_ns, path, nsIMemoryReporter::KIND_HEAP,
1331 nsIMemoryReporter::UNITS_BYTES,
1332 static_cast<int64_t>(mPerformanceResourceEntries),
1333 "Memory used for performance resource entries."_ns, mHandlerData);
1337 manager->EndReport();
1339 return NS_OK;
1342 WorkerPrivate::SyncLoopInfo::SyncLoopInfo(EventTarget* aEventTarget)
1343 : mEventTarget(aEventTarget),
1344 mResult(NS_ERROR_FAILURE),
1345 mCompleted(false)
1346 #ifdef DEBUG
1348 mHasRun(false)
1349 #endif
1353 Document* WorkerPrivate::GetDocument() const {
1354 AssertIsOnMainThread();
1355 if (nsPIDOMWindowInner* window = GetAncestorWindow()) {
1356 return window->GetExtantDoc();
1358 // couldn't query a document, give up and return nullptr
1359 return nullptr;
1362 nsPIDOMWindowInner* WorkerPrivate::GetAncestorWindow() const {
1363 AssertIsOnMainThread();
1364 if (mLoadInfo.mWindow) {
1365 return mLoadInfo.mWindow;
1367 // if we don't have a document, we should query the document
1368 // from the parent in case of a nested worker
1369 WorkerPrivate* parent = mParent;
1370 while (parent) {
1371 if (parent->mLoadInfo.mWindow) {
1372 return parent->mLoadInfo.mWindow;
1374 parent = parent->GetParent();
1376 // couldn't query a window, give up and return nullptr
1377 return nullptr;
1380 class EvictFromBFCacheRunnable final : public WorkerProxyToMainThreadRunnable {
1381 public:
1382 void RunOnMainThread(WorkerPrivate* aWorkerPrivate) override {
1383 MOZ_ASSERT(aWorkerPrivate);
1384 AssertIsOnMainThread();
1385 if (nsCOMPtr<nsPIDOMWindowInner> win =
1386 aWorkerPrivate->GetAncestorWindow()) {
1387 win->RemoveFromBFCacheSync();
1391 void RunBackOnWorkerThreadForCleanup(WorkerPrivate* aWorkerPrivate) override {
1392 MOZ_ASSERT(aWorkerPrivate);
1393 aWorkerPrivate->AssertIsOnWorkerThread();
1397 void WorkerPrivate::EvictFromBFCache() {
1398 AssertIsOnWorkerThread();
1399 RefPtr<EvictFromBFCacheRunnable> runnable = new EvictFromBFCacheRunnable();
1400 runnable->Dispatch(this);
1403 void WorkerPrivate::SetCsp(nsIContentSecurityPolicy* aCSP) {
1404 AssertIsOnMainThread();
1405 if (!aCSP) {
1406 return;
1408 aCSP->EnsureEventTarget(mMainThreadEventTarget);
1410 mLoadInfo.mCSP = aCSP;
1411 mLoadInfo.mCSPInfo = MakeUnique<CSPInfo>();
1412 nsresult rv = CSPToCSPInfo(mLoadInfo.mCSP, mLoadInfo.mCSPInfo.get());
1413 if (NS_WARN_IF(NS_FAILED(rv))) {
1414 return;
1418 nsresult WorkerPrivate::SetCSPFromHeaderValues(
1419 const nsACString& aCSPHeaderValue,
1420 const nsACString& aCSPReportOnlyHeaderValue) {
1421 AssertIsOnMainThread();
1422 MOZ_DIAGNOSTIC_ASSERT(!mLoadInfo.mCSP);
1424 NS_ConvertASCIItoUTF16 cspHeaderValue(aCSPHeaderValue);
1425 NS_ConvertASCIItoUTF16 cspROHeaderValue(aCSPReportOnlyHeaderValue);
1427 nsresult rv;
1428 nsCOMPtr<nsIContentSecurityPolicy> csp = new nsCSPContext();
1430 // First, we try to query the URI from the Principal, but
1431 // in case selfURI remains empty (e.g in case the Principal
1432 // is a SystemPrincipal) then we fall back and use the
1433 // base URI as selfURI for CSP.
1434 nsCOMPtr<nsIURI> selfURI;
1435 // Its not recommended to use the BasePrincipal to get the URI
1436 // but in this case we need to make an exception
1437 auto* basePrin = BasePrincipal::Cast(mLoadInfo.mPrincipal);
1438 if (basePrin) {
1439 basePrin->GetURI(getter_AddRefs(selfURI));
1441 if (!selfURI) {
1442 selfURI = mLoadInfo.mBaseURI;
1444 MOZ_ASSERT(selfURI, "need a self URI for CSP");
1446 rv = csp->SetRequestContextWithPrincipal(mLoadInfo.mPrincipal, selfURI, ""_ns,
1448 NS_ENSURE_SUCCESS(rv, rv);
1450 csp->EnsureEventTarget(mMainThreadEventTarget);
1452 // If there's a CSP header, apply it.
1453 if (!cspHeaderValue.IsEmpty()) {
1454 rv = CSP_AppendCSPFromHeader(csp, cspHeaderValue, false);
1455 NS_ENSURE_SUCCESS(rv, rv);
1457 // If there's a report-only CSP header, apply it.
1458 if (!cspROHeaderValue.IsEmpty()) {
1459 rv = CSP_AppendCSPFromHeader(csp, cspROHeaderValue, true);
1460 NS_ENSURE_SUCCESS(rv, rv);
1463 RefPtr<extensions::WebExtensionPolicy> addonPolicy;
1465 if (basePrin) {
1466 addonPolicy = basePrin->AddonPolicy();
1469 // For extension workers there aren't any csp header values,
1470 // instead it will inherit the Extension CSP.
1471 if (addonPolicy) {
1472 csp->AppendPolicy(addonPolicy->BaseCSP(), false, false);
1473 csp->AppendPolicy(addonPolicy->ExtensionPageCSP(), false, false);
1476 mLoadInfo.mCSP = csp;
1478 // Set evalAllowed, default value is set in GetAllowsEval
1479 bool evalAllowed = false;
1480 bool reportEvalViolations = false;
1481 rv = csp->GetAllowsEval(&reportEvalViolations, &evalAllowed);
1482 NS_ENSURE_SUCCESS(rv, rv);
1484 mLoadInfo.mEvalAllowed = evalAllowed;
1485 mLoadInfo.mReportEvalCSPViolations = reportEvalViolations;
1487 // Set wasmEvalAllowed
1488 bool wasmEvalAllowed = false;
1489 bool reportWasmEvalViolations = false;
1490 rv = csp->GetAllowsWasmEval(&reportWasmEvalViolations, &wasmEvalAllowed);
1491 NS_ENSURE_SUCCESS(rv, rv);
1493 // As for nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction,
1494 // for MV2 extensions we have to allow wasm by default and report violations
1495 // for historical reasons.
1496 // TODO bug 1770909: remove this exception.
1497 if (!wasmEvalAllowed && addonPolicy && addonPolicy->ManifestVersion() == 2) {
1498 wasmEvalAllowed = true;
1499 reportWasmEvalViolations = true;
1502 mLoadInfo.mWasmEvalAllowed = wasmEvalAllowed;
1503 mLoadInfo.mReportWasmEvalCSPViolations = reportWasmEvalViolations;
1505 mLoadInfo.mCSPInfo = MakeUnique<CSPInfo>();
1506 rv = CSPToCSPInfo(csp, mLoadInfo.mCSPInfo.get());
1507 if (NS_WARN_IF(NS_FAILED(rv))) {
1508 return rv;
1510 return NS_OK;
1513 void WorkerPrivate::StoreCSPOnClient() {
1514 auto data = mWorkerThreadAccessible.Access();
1515 MOZ_ASSERT(data->mScope);
1516 if (mLoadInfo.mCSPInfo) {
1517 data->mScope->MutableClientSourceRef().SetCspInfo(*mLoadInfo.mCSPInfo);
1521 void WorkerPrivate::UpdateReferrerInfoFromHeader(
1522 const nsACString& aReferrerPolicyHeaderValue) {
1523 NS_ConvertUTF8toUTF16 headerValue(aReferrerPolicyHeaderValue);
1525 if (headerValue.IsEmpty()) {
1526 return;
1529 ReferrerPolicy policy =
1530 ReferrerInfo::ReferrerPolicyFromHeaderString(headerValue);
1531 if (policy == ReferrerPolicy::_empty) {
1532 return;
1535 nsCOMPtr<nsIReferrerInfo> referrerInfo =
1536 static_cast<ReferrerInfo*>(GetReferrerInfo())->CloneWithNewPolicy(policy);
1537 SetReferrerInfo(referrerInfo);
1540 void WorkerPrivate::Traverse(nsCycleCollectionTraversalCallback& aCb) {
1541 AssertIsOnParentThread();
1543 // The WorkerPrivate::mParentEventTargetRef has a reference to the exposed
1544 // Worker object, which is really held by the worker thread. We traverse this
1545 // reference if and only if all main thread event queues are empty, no
1546 // shutdown tasks, no StrongWorkerRefs, no child workers, no timeouts, no
1547 // blocking background actors, and we have not released the main thread
1548 // reference. We do not unlink it. This allows the CC to break cycles
1549 // involving the Worker and begin shutting it down (which does happen in
1550 // unlink) but ensures that the WorkerPrivate won't be deleted before we're
1551 // done shutting down the thread.
1552 if (IsEligibleForCC() && !mMainThreadObjectsForgotten) {
1553 nsCycleCollectionTraversalCallback& cb = aCb;
1554 WorkerPrivate* tmp = this;
1555 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParentEventTargetRef);
1559 nsresult WorkerPrivate::Dispatch(already_AddRefed<WorkerRunnable> aRunnable,
1560 nsIEventTarget* aSyncLoopTarget) {
1561 // May be called on any thread!
1562 MutexAutoLock lock(mMutex);
1563 return DispatchLockHeld(std::move(aRunnable), aSyncLoopTarget, lock);
1566 nsresult WorkerPrivate::DispatchLockHeld(
1567 already_AddRefed<WorkerRunnable> aRunnable, nsIEventTarget* aSyncLoopTarget,
1568 const MutexAutoLock& aProofOfLock) {
1569 // May be called on any thread!
1570 RefPtr<WorkerRunnable> runnable(aRunnable);
1571 LOGV(("WorkerPrivate::DispatchLockHeld [%p] runnable: %p", this,
1572 runnable.get()));
1574 MOZ_ASSERT_IF(aSyncLoopTarget, mThread);
1576 if (mStatus == Dead || (!aSyncLoopTarget && ParentStatus() > Canceling)) {
1577 NS_WARNING(
1578 "A runnable was posted to a worker that is already shutting "
1579 "down!");
1580 return NS_ERROR_UNEXPECTED;
1583 if (runnable->IsDebuggeeRunnable() && !mDebuggerReady) {
1584 MOZ_RELEASE_ASSERT(!aSyncLoopTarget);
1585 mDelayedDebuggeeRunnables.AppendElement(runnable);
1586 return NS_OK;
1589 if (!mThread) {
1590 if (ParentStatus() == Pending || mStatus == Pending) {
1591 LOGV(
1592 ("WorkerPrivate::DispatchLockHeld [%p] runnable %p is queued in "
1593 "mPreStartRunnables",
1594 this, runnable.get()));
1595 mPreStartRunnables.AppendElement(runnable);
1596 return NS_OK;
1599 NS_WARNING(
1600 "Using a worker event target after the thread has already"
1601 "been released!");
1602 return NS_ERROR_UNEXPECTED;
1605 nsresult rv;
1606 if (aSyncLoopTarget) {
1607 LOGV(
1608 ("WorkerPrivate::DispatchLockHeld [%p] runnable %p dispatch to a "
1609 "SyncLoop(%p)",
1610 this, runnable.get(), aSyncLoopTarget));
1611 rv = aSyncLoopTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
1612 } else {
1613 // WorkerDebuggeeRunnables don't need any special treatment here. True,
1614 // they should not be delivered to a frozen worker. But frozen workers
1615 // aren't drawing from the thread's main event queue anyway, only from
1616 // mControlQueue.
1617 LOGV(
1618 ("WorkerPrivate::DispatchLockHeld [%p] runnable %p dispatch to the "
1619 "main event queue",
1620 this, runnable.get()));
1621 rv = mThread->DispatchAnyThread(WorkerThreadFriendKey(), runnable.forget());
1624 if (NS_WARN_IF(NS_FAILED(rv))) {
1625 LOGV(("WorkerPrivate::Dispatch Failed [%p]", this));
1626 return rv;
1629 mCondVar.Notify();
1630 return NS_OK;
1633 void WorkerPrivate::EnableDebugger() {
1634 AssertIsOnParentThread();
1636 if (NS_FAILED(RegisterWorkerDebugger(this))) {
1637 NS_WARNING("Failed to register worker debugger!");
1638 return;
1642 void WorkerPrivate::DisableDebugger() {
1643 AssertIsOnParentThread();
1645 // RegisterDebuggerMainThreadRunnable might be dispatched but not executed.
1646 // Wait for its execution before unregistraion.
1647 if (!NS_IsMainThread()) {
1648 WaitForIsDebuggerRegistered(true);
1651 if (NS_FAILED(UnregisterWorkerDebugger(this))) {
1652 NS_WARNING("Failed to unregister worker debugger!");
1656 nsresult WorkerPrivate::DispatchControlRunnable(
1657 already_AddRefed<WorkerControlRunnable> aWorkerControlRunnable) {
1658 // May be called on any thread!
1659 RefPtr<WorkerControlRunnable> runnable(aWorkerControlRunnable);
1660 MOZ_ASSERT(runnable);
1662 LOG(WorkerLog(), ("WorkerPrivate::DispatchControlRunnable [%p] runnable %p",
1663 this, runnable.get()));
1666 MutexAutoLock lock(mMutex);
1668 if (mStatus == Dead) {
1669 return NS_ERROR_UNEXPECTED;
1672 // Transfer ownership to the control queue.
1673 mControlQueue.Push(runnable.forget().take());
1675 if (JSContext* cx = mJSContext) {
1676 MOZ_ASSERT(mThread);
1677 JS_RequestInterruptCallback(cx);
1680 mCondVar.Notify();
1683 return NS_OK;
1686 nsresult WorkerPrivate::DispatchDebuggerRunnable(
1687 already_AddRefed<WorkerRunnable> aDebuggerRunnable) {
1688 // May be called on any thread!
1690 RefPtr<WorkerRunnable> runnable(aDebuggerRunnable);
1692 MOZ_ASSERT(runnable);
1695 MutexAutoLock lock(mMutex);
1697 if (mStatus == Dead) {
1698 NS_WARNING(
1699 "A debugger runnable was posted to a worker that is already "
1700 "shutting down!");
1701 return NS_ERROR_UNEXPECTED;
1704 // Transfer ownership to the debugger queue.
1705 mDebuggerQueue.Push(runnable.forget().take());
1707 mCondVar.Notify();
1710 return NS_OK;
1713 already_AddRefed<WorkerRunnable> WorkerPrivate::MaybeWrapAsWorkerRunnable(
1714 already_AddRefed<nsIRunnable> aRunnable) {
1715 // May be called on any thread!
1717 nsCOMPtr<nsIRunnable> runnable(aRunnable);
1718 MOZ_ASSERT(runnable);
1720 LOGV(("WorkerPrivate::MaybeWrapAsWorkerRunnable [%p] runnable: %p", this,
1721 runnable.get()));
1723 RefPtr<WorkerRunnable> workerRunnable =
1724 WorkerRunnable::FromRunnable(runnable);
1725 if (workerRunnable) {
1726 return workerRunnable.forget();
1729 workerRunnable = new ExternalRunnableWrapper(this, runnable);
1730 return workerRunnable.forget();
1733 bool WorkerPrivate::Start() {
1734 // May be called on any thread!
1735 LOG(WorkerLog(), ("WorkerPrivate::Start [%p]", this));
1737 MutexAutoLock lock(mMutex);
1738 NS_ASSERTION(mParentStatus != Running, "How can this be?!");
1740 if (mParentStatus == Pending) {
1741 mParentStatus = Running;
1742 return true;
1746 return false;
1749 // aCx is null when called from the finalizer
1750 bool WorkerPrivate::Notify(WorkerStatus aStatus) {
1751 AssertIsOnParentThread();
1752 // This method is only called for Canceling or later.
1753 MOZ_DIAGNOSTIC_ASSERT(aStatus >= Canceling);
1755 bool pending;
1757 MutexAutoLock lock(mMutex);
1759 if (mParentStatus >= aStatus) {
1760 return true;
1763 pending = mParentStatus == Pending;
1764 mParentStatus = aStatus;
1767 if (mCancellationCallback) {
1768 mCancellationCallback(!pending);
1769 mCancellationCallback = nullptr;
1772 if (pending) {
1773 #ifdef DEBUG
1775 // Fake a thread here just so that our assertions don't go off for no
1776 // reason.
1777 nsIThread* currentThread = NS_GetCurrentThread();
1778 MOZ_ASSERT(currentThread);
1780 MOZ_ASSERT(!mPRThread);
1781 mPRThread = PRThreadFromThread(currentThread);
1782 MOZ_ASSERT(mPRThread);
1784 #endif
1786 // Worker never got a chance to run, go ahead and delete it.
1787 ScheduleDeletion(WorkerPrivate::WorkerNeverRan);
1788 return true;
1791 // No Canceling timeout is needed.
1792 if (mCancelingTimer) {
1793 mCancelingTimer->Cancel();
1794 mCancelingTimer = nullptr;
1797 // The NotifyRunnable kicks off a series of events that need the
1798 // CancelingOnParentRunnable to be executed always.
1799 // Note that we already advanced mParentStatus above and we check that
1800 // status in all other (asynchronous) call sites of SetIsPaused.
1801 if (!mParent) {
1802 MOZ_ALWAYS_SUCCEEDS(mMainThreadDebuggeeEventTarget->SetIsPaused(false));
1805 RefPtr<NotifyRunnable> runnable = new NotifyRunnable(this, aStatus);
1806 return runnable->Dispatch();
1809 bool WorkerPrivate::Freeze(const nsPIDOMWindowInner* aWindow) {
1810 AssertIsOnParentThread();
1812 mParentFrozen = true;
1814 bool isCanceling = false;
1816 MutexAutoLock lock(mMutex);
1818 isCanceling = mParentStatus >= Canceling;
1821 // WorkerDebuggeeRunnables sent from a worker to content must not be
1822 // delivered while the worker is frozen.
1824 // Since a top-level worker and all its children share the same
1825 // mMainThreadDebuggeeEventTarget, it's sufficient to do this only in the
1826 // top-level worker.
1827 if (aWindow) {
1828 // This is called from WorkerPrivate construction, and We may not have
1829 // allocated mMainThreadDebuggeeEventTarget yet.
1830 if (mMainThreadDebuggeeEventTarget) {
1831 // Pausing a ThrottledEventQueue is infallible.
1832 MOZ_ALWAYS_SUCCEEDS(
1833 mMainThreadDebuggeeEventTarget->SetIsPaused(!isCanceling));
1837 if (isCanceling) {
1838 return true;
1841 DisableDebugger();
1843 RefPtr<FreezeRunnable> runnable = new FreezeRunnable(this);
1844 return runnable->Dispatch();
1847 bool WorkerPrivate::Thaw(const nsPIDOMWindowInner* aWindow) {
1848 AssertIsOnParentThread();
1849 MOZ_ASSERT(mParentFrozen);
1851 mParentFrozen = false;
1854 bool isCanceling = false;
1857 MutexAutoLock lock(mMutex);
1859 isCanceling = mParentStatus >= Canceling;
1862 // Delivery of WorkerDebuggeeRunnables to the window may resume.
1864 // Since a top-level worker and all its children share the same
1865 // mMainThreadDebuggeeEventTarget, it's sufficient to do this only in the
1866 // top-level worker.
1867 if (aWindow) {
1868 // Since the worker is no longer frozen, only a paused parent window
1869 // should require the queue to remain paused.
1871 // This can only fail if the ThrottledEventQueue cannot dispatch its
1872 // executor to the main thread, in which case the main thread was never
1873 // going to draw runnables from it anyway, so the failure doesn't matter.
1874 Unused << mMainThreadDebuggeeEventTarget->SetIsPaused(
1875 IsParentWindowPaused() && !isCanceling);
1878 if (isCanceling) {
1879 return true;
1883 EnableDebugger();
1885 RefPtr<ThawRunnable> runnable = new ThawRunnable(this);
1886 return runnable->Dispatch();
1889 void WorkerPrivate::ParentWindowPaused() {
1890 AssertIsOnMainThread();
1891 MOZ_ASSERT(!mParentWindowPaused);
1892 mParentWindowPaused = true;
1894 // This is called from WorkerPrivate construction, and we may not have
1895 // allocated mMainThreadDebuggeeEventTarget yet.
1896 if (mMainThreadDebuggeeEventTarget) {
1897 bool isCanceling = false;
1900 MutexAutoLock lock(mMutex);
1902 isCanceling = mParentStatus >= Canceling;
1905 // If we are already canceling we might wait for CancelingOnParentRunnable
1906 // to be executed, so do not pause.
1907 MOZ_ALWAYS_SUCCEEDS(
1908 mMainThreadDebuggeeEventTarget->SetIsPaused(!isCanceling));
1912 void WorkerPrivate::ParentWindowResumed() {
1913 AssertIsOnMainThread();
1915 MOZ_ASSERT(mParentWindowPaused);
1916 mParentWindowPaused = false;
1918 bool isCanceling = false;
1920 MutexAutoLock lock(mMutex);
1922 isCanceling = mParentStatus >= Canceling;
1925 // Since the window is no longer paused, the queue should only remain paused
1926 // if the worker is frozen.
1928 // This can only fail if the ThrottledEventQueue cannot dispatch its executor
1929 // to the main thread, in which case the main thread was never going to draw
1930 // runnables from it anyway, so the failure doesn't matter.
1931 Unused << mMainThreadDebuggeeEventTarget->SetIsPaused(IsFrozen() &&
1932 !isCanceling);
1935 void WorkerPrivate::PropagateStorageAccessPermissionGranted() {
1936 AssertIsOnParentThread();
1939 MutexAutoLock lock(mMutex);
1941 if (mParentStatus >= Canceling) {
1942 return;
1946 RefPtr<PropagateStorageAccessPermissionGrantedRunnable> runnable =
1947 new PropagateStorageAccessPermissionGrantedRunnable(this);
1948 Unused << NS_WARN_IF(!runnable->Dispatch());
1951 bool WorkerPrivate::Close() {
1952 mMutex.AssertCurrentThreadOwns();
1953 if (mParentStatus < Closing) {
1954 mParentStatus = Closing;
1957 return true;
1960 bool WorkerPrivate::ProxyReleaseMainThreadObjects() {
1961 AssertIsOnParentThread();
1962 MOZ_ASSERT(!mMainThreadObjectsForgotten);
1964 nsCOMPtr<nsILoadGroup> loadGroupToCancel;
1965 // If we're not overriden, then do nothing here. Let the load group get
1966 // handled in ForgetMainThreadObjects().
1967 if (mLoadInfo.mInterfaceRequestor) {
1968 mLoadInfo.mLoadGroup.swap(loadGroupToCancel);
1971 bool result = mLoadInfo.ProxyReleaseMainThreadObjects(
1972 this, std::move(loadGroupToCancel));
1974 mMainThreadObjectsForgotten = true;
1976 return result;
1979 void WorkerPrivate::UpdateContextOptions(
1980 const JS::ContextOptions& aContextOptions) {
1981 AssertIsOnParentThread();
1984 MutexAutoLock lock(mMutex);
1985 mJSSettings.contextOptions = aContextOptions;
1988 RefPtr<UpdateContextOptionsRunnable> runnable =
1989 new UpdateContextOptionsRunnable(this, aContextOptions);
1990 if (!runnable->Dispatch()) {
1991 NS_WARNING("Failed to update worker context options!");
1995 void WorkerPrivate::UpdateLanguages(const nsTArray<nsString>& aLanguages) {
1996 AssertIsOnParentThread();
1998 RefPtr<UpdateLanguagesRunnable> runnable =
1999 new UpdateLanguagesRunnable(this, aLanguages);
2000 if (!runnable->Dispatch()) {
2001 NS_WARNING("Failed to update worker languages!");
2005 void WorkerPrivate::UpdateJSWorkerMemoryParameter(JSGCParamKey aKey,
2006 Maybe<uint32_t> aValue) {
2007 AssertIsOnParentThread();
2009 bool changed = false;
2012 MutexAutoLock lock(mMutex);
2013 changed = mJSSettings.ApplyGCSetting(aKey, aValue);
2016 if (changed) {
2017 RefPtr<UpdateJSWorkerMemoryParameterRunnable> runnable =
2018 new UpdateJSWorkerMemoryParameterRunnable(this, aKey, aValue);
2019 if (!runnable->Dispatch()) {
2020 NS_WARNING("Failed to update memory parameter!");
2025 #ifdef JS_GC_ZEAL
2026 void WorkerPrivate::UpdateGCZeal(uint8_t aGCZeal, uint32_t aFrequency) {
2027 AssertIsOnParentThread();
2030 MutexAutoLock lock(mMutex);
2031 mJSSettings.gcZeal = aGCZeal;
2032 mJSSettings.gcZealFrequency = aFrequency;
2035 RefPtr<UpdateGCZealRunnable> runnable =
2036 new UpdateGCZealRunnable(this, aGCZeal, aFrequency);
2037 if (!runnable->Dispatch()) {
2038 NS_WARNING("Failed to update worker gczeal!");
2041 #endif
2043 void WorkerPrivate::SetLowMemoryState(bool aState) {
2044 AssertIsOnParentThread();
2046 RefPtr<SetLowMemoryStateRunnable> runnable =
2047 new SetLowMemoryStateRunnable(this, aState);
2048 if (!runnable->Dispatch()) {
2049 NS_WARNING("Failed to set low memory state!");
2053 void WorkerPrivate::GarbageCollect(bool aShrinking) {
2054 AssertIsOnParentThread();
2056 RefPtr<GarbageCollectRunnable> runnable = new GarbageCollectRunnable(
2057 this, aShrinking, /* aCollectChildren = */ true);
2058 if (!runnable->Dispatch()) {
2059 NS_WARNING("Failed to GC worker!");
2063 void WorkerPrivate::CycleCollect() {
2064 AssertIsOnParentThread();
2066 RefPtr<CycleCollectRunnable> runnable =
2067 new CycleCollectRunnable(this, /* aCollectChildren = */ true);
2068 if (!runnable->Dispatch()) {
2069 NS_WARNING("Failed to CC worker!");
2073 void WorkerPrivate::OfflineStatusChangeEvent(bool aIsOffline) {
2074 AssertIsOnParentThread();
2076 RefPtr<OfflineStatusChangeRunnable> runnable =
2077 new OfflineStatusChangeRunnable(this, aIsOffline);
2078 if (!runnable->Dispatch()) {
2079 NS_WARNING("Failed to dispatch offline status change event!");
2083 void WorkerPrivate::OfflineStatusChangeEventInternal(bool aIsOffline) {
2084 auto data = mWorkerThreadAccessible.Access();
2086 // The worker is already in this state. No need to dispatch an event.
2087 if (data->mOnLine == !aIsOffline) {
2088 return;
2091 for (uint32_t index = 0; index < data->mChildWorkers.Length(); ++index) {
2092 data->mChildWorkers[index]->OfflineStatusChangeEvent(aIsOffline);
2095 data->mOnLine = !aIsOffline;
2096 WorkerGlobalScope* globalScope = GlobalScope();
2097 RefPtr<WorkerNavigator> nav = globalScope->GetExistingNavigator();
2098 if (nav) {
2099 nav->SetOnLine(data->mOnLine);
2102 nsString eventType;
2103 if (aIsOffline) {
2104 eventType.AssignLiteral("offline");
2105 } else {
2106 eventType.AssignLiteral("online");
2109 RefPtr<Event> event = NS_NewDOMEvent(globalScope, nullptr, nullptr);
2111 event->InitEvent(eventType, false, false);
2112 event->SetTrusted(true);
2114 globalScope->DispatchEvent(*event);
2117 void WorkerPrivate::MemoryPressure() {
2118 AssertIsOnParentThread();
2120 RefPtr<MemoryPressureRunnable> runnable = new MemoryPressureRunnable(this);
2121 Unused << NS_WARN_IF(!runnable->Dispatch());
2124 RefPtr<WorkerPrivate::JSMemoryUsagePromise> WorkerPrivate::GetJSMemoryUsage() {
2125 AssertIsOnMainThread();
2128 MutexAutoLock lock(mMutex);
2129 // If we have started shutting down the worker, do not dispatch a runnable
2130 // to measure its memory.
2131 if (ParentStatus() > Running) {
2132 return nullptr;
2136 return InvokeAsync(ControlEventTarget(), __func__, []() {
2137 WorkerPrivate* wp = GetCurrentThreadWorkerPrivate();
2138 MOZ_ASSERT(wp);
2139 wp->AssertIsOnWorkerThread();
2140 MutexAutoLock lock(wp->mMutex);
2141 return JSMemoryUsagePromise::CreateAndResolve(
2142 js::GetGCHeapUsage(wp->mJSContext), __func__);
2146 void WorkerPrivate::WorkerScriptLoaded() {
2147 AssertIsOnMainThread();
2149 if (IsSharedWorker() || IsServiceWorker()) {
2150 // No longer need to hold references to the window or document we came from.
2151 mLoadInfo.mWindow = nullptr;
2152 mLoadInfo.mScriptContext = nullptr;
2156 void WorkerPrivate::SetBaseURI(nsIURI* aBaseURI) {
2157 AssertIsOnMainThread();
2159 if (!mLoadInfo.mBaseURI) {
2160 NS_ASSERTION(GetParent(), "Shouldn't happen without a parent!");
2161 mLoadInfo.mResolvedScriptURI = aBaseURI;
2164 mLoadInfo.mBaseURI = aBaseURI;
2166 if (NS_FAILED(aBaseURI->GetSpec(mLocationInfo.mHref))) {
2167 mLocationInfo.mHref.Truncate();
2170 mLocationInfo.mHostname.Truncate();
2171 nsContentUtils::GetHostOrIPv6WithBrackets(aBaseURI, mLocationInfo.mHostname);
2173 nsCOMPtr<nsIURL> url(do_QueryInterface(aBaseURI));
2174 if (!url || NS_FAILED(url->GetFilePath(mLocationInfo.mPathname))) {
2175 mLocationInfo.mPathname.Truncate();
2178 nsCString temp;
2180 if (url && NS_SUCCEEDED(url->GetQuery(temp)) && !temp.IsEmpty()) {
2181 mLocationInfo.mSearch.Assign('?');
2182 mLocationInfo.mSearch.Append(temp);
2185 if (NS_SUCCEEDED(aBaseURI->GetRef(temp)) && !temp.IsEmpty()) {
2186 if (mLocationInfo.mHash.IsEmpty()) {
2187 mLocationInfo.mHash.Assign('#');
2188 mLocationInfo.mHash.Append(temp);
2192 if (NS_SUCCEEDED(aBaseURI->GetScheme(mLocationInfo.mProtocol))) {
2193 mLocationInfo.mProtocol.Append(':');
2194 } else {
2195 mLocationInfo.mProtocol.Truncate();
2198 int32_t port;
2199 if (NS_SUCCEEDED(aBaseURI->GetPort(&port)) && port != -1) {
2200 mLocationInfo.mPort.AppendInt(port);
2202 nsAutoCString host(mLocationInfo.mHostname);
2203 host.Append(':');
2204 host.Append(mLocationInfo.mPort);
2206 mLocationInfo.mHost.Assign(host);
2207 } else {
2208 mLocationInfo.mHost.Assign(mLocationInfo.mHostname);
2211 nsContentUtils::GetWebExposedOriginSerialization(aBaseURI,
2212 mLocationInfo.mOrigin);
2215 nsresult WorkerPrivate::SetPrincipalsAndCSPOnMainThread(
2216 nsIPrincipal* aPrincipal, nsIPrincipal* aPartitionedPrincipal,
2217 nsILoadGroup* aLoadGroup, nsIContentSecurityPolicy* aCsp) {
2218 return mLoadInfo.SetPrincipalsAndCSPOnMainThread(
2219 aPrincipal, aPartitionedPrincipal, aLoadGroup, aCsp);
2222 nsresult WorkerPrivate::SetPrincipalsAndCSPFromChannel(nsIChannel* aChannel) {
2223 return mLoadInfo.SetPrincipalsAndCSPFromChannel(aChannel);
2226 bool WorkerPrivate::FinalChannelPrincipalIsValid(nsIChannel* aChannel) {
2227 return mLoadInfo.FinalChannelPrincipalIsValid(aChannel);
2230 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
2231 bool WorkerPrivate::PrincipalURIMatchesScriptURL() {
2232 return mLoadInfo.PrincipalURIMatchesScriptURL();
2234 #endif
2236 void WorkerPrivate::UpdateOverridenLoadGroup(nsILoadGroup* aBaseLoadGroup) {
2237 AssertIsOnMainThread();
2239 // The load group should have been overriden at init time.
2240 mLoadInfo.mInterfaceRequestor->MaybeAddBrowserChild(aBaseLoadGroup);
2243 bool WorkerPrivate::IsOnParentThread() const {
2244 if (GetParent()) {
2245 return GetParent()->IsOnWorkerThread();
2247 return NS_IsMainThread();
2250 #ifdef DEBUG
2252 void WorkerPrivate::AssertIsOnParentThread() const {
2253 if (GetParent()) {
2254 GetParent()->AssertIsOnWorkerThread();
2255 } else {
2256 AssertIsOnMainThread();
2260 void WorkerPrivate::AssertInnerWindowIsCorrect() const {
2261 AssertIsOnParentThread();
2263 // Only care about top level workers from windows.
2264 if (mParent || !mLoadInfo.mWindow) {
2265 return;
2268 AssertIsOnMainThread();
2270 nsPIDOMWindowOuter* outer = mLoadInfo.mWindow->GetOuterWindow();
2271 NS_ASSERTION(outer && outer->GetCurrentInnerWindow() == mLoadInfo.mWindow,
2272 "Inner window no longer correct!");
2275 #endif
2277 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
2278 bool WorkerPrivate::PrincipalIsValid() const {
2279 return mLoadInfo.PrincipalIsValid();
2281 #endif
2283 WorkerPrivate::WorkerThreadAccessible::WorkerThreadAccessible(
2284 WorkerPrivate* const aParent)
2285 : mNumWorkerRefsPreventingShutdownStart(0),
2286 mDebuggerEventLoopLevel(0),
2287 mNonblockingCCBackgroundActorCount(0),
2288 mErrorHandlerRecursionCount(0),
2289 mNextTimeoutId(1),
2290 mCurrentTimerNestingLevel(0),
2291 mFrozen(false),
2292 mTimerRunning(false),
2293 mRunningExpiredTimeouts(false),
2294 mPeriodicGCTimerRunning(false),
2295 mIdleGCTimerRunning(false),
2296 mOnLine(aParent ? aParent->OnLine() : !NS_IsOffline()),
2297 mJSThreadExecutionGranted(false),
2298 mCCCollectedAnything(false) {}
2300 namespace {
2302 bool IsNewWorkerSecureContext(const WorkerPrivate* const aParent,
2303 const WorkerKind aWorkerKind,
2304 const WorkerLoadInfo& aLoadInfo) {
2305 if (aParent) {
2306 return aParent->IsSecureContext();
2309 // Our secure context state depends on the kind of worker we have.
2311 if (aLoadInfo.mPrincipal && aLoadInfo.mPrincipal->IsSystemPrincipal()) {
2312 return true;
2315 if (aWorkerKind == WorkerKindService) {
2316 return true;
2319 if (aLoadInfo.mSecureContext != WorkerLoadInfo::eNotSet) {
2320 return aLoadInfo.mSecureContext == WorkerLoadInfo::eSecureContext;
2323 MOZ_ASSERT_UNREACHABLE(
2324 "non-chrome worker that is not a service worker "
2325 "that has no parent and no associated window");
2327 return false;
2330 } // namespace
2332 WorkerPrivate::WorkerPrivate(
2333 WorkerPrivate* aParent, const nsAString& aScriptURL, bool aIsChromeWorker,
2334 WorkerKind aWorkerKind, RequestCredentials aRequestCredentials,
2335 enum WorkerType aWorkerType, const nsAString& aWorkerName,
2336 const nsACString& aServiceWorkerScope, WorkerLoadInfo& aLoadInfo,
2337 nsString&& aId, const nsID& aAgentClusterId,
2338 const nsILoadInfo::CrossOriginOpenerPolicy aAgentClusterOpenerPolicy,
2339 CancellationCallback&& aCancellationCallback,
2340 TerminationCallback&& aTerminationCallback)
2341 : mMutex("WorkerPrivate Mutex"),
2342 mCondVar(mMutex, "WorkerPrivate CondVar"),
2343 mParent(aParent),
2344 mScriptURL(aScriptURL),
2345 mWorkerName(aWorkerName),
2346 mCredentialsMode(aRequestCredentials),
2347 mWorkerType(aWorkerType), // If the worker runs as a script or a module
2348 mWorkerKind(aWorkerKind),
2349 mCancellationCallback(std::move(aCancellationCallback)),
2350 mTerminationCallback(std::move(aTerminationCallback)),
2351 mLoadInfo(std::move(aLoadInfo)),
2352 mDebugger(nullptr),
2353 mJSContext(nullptr),
2354 mPRThread(nullptr),
2355 mWorkerControlEventTarget(new WorkerEventTarget(
2356 this, WorkerEventTarget::Behavior::ControlOnly)),
2357 mWorkerHybridEventTarget(
2358 new WorkerEventTarget(this, WorkerEventTarget::Behavior::Hybrid)),
2359 mParentStatus(Pending),
2360 mStatus(Pending),
2361 mCreationTimeStamp(TimeStamp::Now()),
2362 mCreationTimeHighRes((double)PR_Now() / PR_USEC_PER_MSEC),
2363 mReportedUseCounters(false),
2364 mAgentClusterId(aAgentClusterId),
2365 mWorkerThreadAccessible(aParent),
2366 mPostSyncLoopOperations(0),
2367 mParentWindowPaused(false),
2368 mWorkerScriptExecutedSuccessfully(false),
2369 mFetchHandlerWasAdded(false),
2370 mMainThreadObjectsForgotten(false),
2371 mIsChromeWorker(aIsChromeWorker),
2372 mParentFrozen(false),
2373 mIsSecureContext(
2374 IsNewWorkerSecureContext(mParent, mWorkerKind, mLoadInfo)),
2375 mDebuggerRegistered(false),
2376 mDebuggerReady(true),
2377 mExtensionAPIAllowed(false),
2378 mIsInAutomation(false),
2379 mId(std::move(aId)),
2380 mAgentClusterOpenerPolicy(aAgentClusterOpenerPolicy),
2381 mIsPrivilegedAddonGlobal(false),
2382 mTopLevelWorkerFinishedRunnableCount(0),
2383 mWorkerFinishedRunnableCount(0) {
2384 LOG(WorkerLog(), ("WorkerPrivate::WorkerPrivate [%p]", this));
2385 MOZ_ASSERT_IF(!IsDedicatedWorker(), NS_IsMainThread());
2387 if (aParent) {
2388 aParent->AssertIsOnWorkerThread();
2390 // Note that this copies our parent's secure context state into mJSSettings.
2391 aParent->CopyJSSettings(mJSSettings);
2393 MOZ_ASSERT_IF(mIsChromeWorker, mIsSecureContext);
2395 mIsInAutomation = aParent->IsInAutomation();
2397 MOZ_ASSERT(IsDedicatedWorker());
2399 if (aParent->mParentFrozen) {
2400 Freeze(nullptr);
2403 mIsPrivilegedAddonGlobal = aParent->mIsPrivilegedAddonGlobal;
2404 } else {
2405 AssertIsOnMainThread();
2407 RuntimeService::GetDefaultJSSettings(mJSSettings);
2410 JS::RealmOptions& chromeRealmOptions = mJSSettings.chromeRealmOptions;
2411 JS::RealmOptions& contentRealmOptions = mJSSettings.contentRealmOptions;
2413 xpc::InitGlobalObjectOptions(
2414 chromeRealmOptions, UsesSystemPrincipal(), mIsSecureContext,
2415 ShouldResistFingerprinting(RFPTarget::JSDateTimeUTC),
2416 ShouldResistFingerprinting(RFPTarget::JSMathFdlibm),
2417 ShouldResistFingerprinting(RFPTarget::JSLocale));
2418 xpc::InitGlobalObjectOptions(
2419 contentRealmOptions, UsesSystemPrincipal(), mIsSecureContext,
2420 ShouldResistFingerprinting(RFPTarget::JSDateTimeUTC),
2421 ShouldResistFingerprinting(RFPTarget::JSMathFdlibm),
2422 ShouldResistFingerprinting(RFPTarget::JSLocale));
2424 // Check if it's a privileged addon executing in order to allow access
2425 // to SharedArrayBuffer
2426 if (mLoadInfo.mPrincipal) {
2427 if (auto* policy =
2428 BasePrincipal::Cast(mLoadInfo.mPrincipal)->AddonPolicy()) {
2429 if (policy->IsPrivileged() &&
2430 ExtensionPolicyService::GetSingleton().IsExtensionProcess()) {
2431 // Privileged extensions are allowed to use SharedArrayBuffer in
2432 // their extension process, but never in content scripts in
2433 // content processes.
2434 mIsPrivilegedAddonGlobal = true;
2437 if (StaticPrefs::
2438 extensions_backgroundServiceWorker_enabled_AtStartup() &&
2439 mWorkerKind == WorkerKindService &&
2440 policy->IsManifestBackgroundWorker(mScriptURL)) {
2441 // Only allows ExtensionAPI for extension service workers
2442 // that are declared in the extension manifest json as
2443 // the background service worker.
2444 mExtensionAPIAllowed = true;
2449 // The SharedArrayBuffer global constructor property should not be present
2450 // in a fresh global object when shared memory objects aren't allowed
2451 // (because COOP/COEP support isn't enabled, or because COOP/COEP don't
2452 // act to isolate this worker to a separate process).
2453 const bool defineSharedArrayBufferConstructor = IsSharedMemoryAllowed();
2454 chromeRealmOptions.creationOptions()
2455 .setDefineSharedArrayBufferConstructor(
2456 defineSharedArrayBufferConstructor);
2457 contentRealmOptions.creationOptions()
2458 .setDefineSharedArrayBufferConstructor(
2459 defineSharedArrayBufferConstructor);
2462 mIsInAutomation = xpc::IsInAutomation();
2464 // Our parent can get suspended after it initiates the async creation
2465 // of a new worker thread. In this case suspend the new worker as well.
2466 if (mLoadInfo.mWindow && mLoadInfo.mWindow->IsSuspended()) {
2467 ParentWindowPaused();
2470 if (mLoadInfo.mWindow && mLoadInfo.mWindow->IsFrozen()) {
2471 Freeze(mLoadInfo.mWindow);
2475 nsCOMPtr<nsISerialEventTarget> target;
2477 // A child worker just inherits the parent workers ThrottledEventQueue
2478 // and main thread target for now. This is mainly due to the restriction
2479 // that ThrottledEventQueue can only be created on the main thread at the
2480 // moment.
2481 if (aParent) {
2482 mMainThreadEventTargetForMessaging =
2483 aParent->mMainThreadEventTargetForMessaging;
2484 mMainThreadEventTarget = aParent->mMainThreadEventTarget;
2485 mMainThreadDebuggeeEventTarget = aParent->mMainThreadDebuggeeEventTarget;
2486 return;
2489 MOZ_ASSERT(NS_IsMainThread());
2490 target = GetWindow()
2491 ? GetWindow()->GetBrowsingContextGroup()->GetWorkerEventQueue()
2492 : nullptr;
2494 if (!target) {
2495 target = GetMainThreadSerialEventTarget();
2496 MOZ_DIAGNOSTIC_ASSERT(target);
2499 // Throttle events to the main thread using a ThrottledEventQueue specific to
2500 // this tree of worker threads.
2501 mMainThreadEventTargetForMessaging =
2502 ThrottledEventQueue::Create(target, "Worker queue for messaging");
2503 if (StaticPrefs::dom_worker_use_medium_high_event_queue()) {
2504 mMainThreadEventTarget = ThrottledEventQueue::Create(
2505 GetMainThreadSerialEventTarget(), "Worker queue",
2506 nsIRunnablePriority::PRIORITY_MEDIUMHIGH);
2507 } else {
2508 mMainThreadEventTarget = mMainThreadEventTargetForMessaging;
2510 mMainThreadDebuggeeEventTarget =
2511 ThrottledEventQueue::Create(target, "Worker debuggee queue");
2512 if (IsParentWindowPaused() || IsFrozen()) {
2513 MOZ_ALWAYS_SUCCEEDS(mMainThreadDebuggeeEventTarget->SetIsPaused(true));
2517 WorkerPrivate::~WorkerPrivate() {
2518 MOZ_DIAGNOSTIC_ASSERT(mTopLevelWorkerFinishedRunnableCount == 0);
2519 MOZ_DIAGNOSTIC_ASSERT(mWorkerFinishedRunnableCount == 0);
2521 mWorkerControlEventTarget->ForgetWorkerPrivate(this);
2523 // We force the hybrid event target to forget the thread when we
2524 // enter the Killing state, but we do it again here to be safe.
2525 // Its possible that we may be created and destroyed without progressing
2526 // to Killing via some obscure code path.
2527 mWorkerHybridEventTarget->ForgetWorkerPrivate(this);
2530 WorkerPrivate::AgentClusterIdAndCoop
2531 WorkerPrivate::ComputeAgentClusterIdAndCoop(WorkerPrivate* aParent,
2532 WorkerKind aWorkerKind,
2533 WorkerLoadInfo* aLoadInfo) {
2534 nsILoadInfo::CrossOriginOpenerPolicy agentClusterCoop =
2535 nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
2537 if (aParent) {
2538 MOZ_ASSERT(aWorkerKind == WorkerKind::WorkerKindDedicated);
2540 return {aParent->AgentClusterId(), aParent->mAgentClusterOpenerPolicy};
2543 AssertIsOnMainThread();
2545 if (aWorkerKind == WorkerKind::WorkerKindService ||
2546 aWorkerKind == WorkerKind::WorkerKindShared) {
2547 return {aLoadInfo->mAgentClusterId, agentClusterCoop};
2550 if (aLoadInfo->mWindow) {
2551 Document* doc = aLoadInfo->mWindow->GetExtantDoc();
2552 MOZ_DIAGNOSTIC_ASSERT(doc);
2553 RefPtr<DocGroup> docGroup = doc->GetDocGroup();
2555 nsID agentClusterId =
2556 docGroup ? docGroup->AgentClusterId() : nsID::GenerateUUID();
2558 BrowsingContext* bc = aLoadInfo->mWindow->GetBrowsingContext();
2559 MOZ_DIAGNOSTIC_ASSERT(bc);
2560 return {agentClusterId, bc->Top()->GetOpenerPolicy()};
2563 // If the window object was failed to be set into the WorkerLoadInfo, we
2564 // make the worker into another agent cluster group instead of failures.
2565 return {nsID::GenerateUUID(), agentClusterCoop};
2568 // static
2569 already_AddRefed<WorkerPrivate> WorkerPrivate::Constructor(
2570 JSContext* aCx, const nsAString& aScriptURL, bool aIsChromeWorker,
2571 WorkerKind aWorkerKind, RequestCredentials aRequestCredentials,
2572 enum WorkerType aWorkerType, const nsAString& aWorkerName,
2573 const nsACString& aServiceWorkerScope, WorkerLoadInfo* aLoadInfo,
2574 ErrorResult& aRv, nsString aId,
2575 CancellationCallback&& aCancellationCallback,
2576 TerminationCallback&& aTerminationCallback) {
2577 WorkerPrivate* parent =
2578 NS_IsMainThread() ? nullptr : GetCurrentThreadWorkerPrivate();
2580 // If this is a sub-worker, we need to keep the parent worker alive until this
2581 // one is registered.
2582 RefPtr<StrongWorkerRef> workerRef;
2583 if (parent) {
2584 parent->AssertIsOnWorkerThread();
2586 workerRef = StrongWorkerRef::Create(parent, "WorkerPrivate::Constructor");
2587 if (NS_WARN_IF(!workerRef)) {
2588 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
2589 return nullptr;
2591 } else {
2592 AssertIsOnMainThread();
2595 Maybe<WorkerLoadInfo> stackLoadInfo;
2596 if (!aLoadInfo) {
2597 stackLoadInfo.emplace();
2599 nsresult rv = GetLoadInfo(
2600 aCx, nullptr, parent, aScriptURL, aWorkerType, aRequestCredentials,
2601 aIsChromeWorker, InheritLoadGroup, aWorkerKind, stackLoadInfo.ptr());
2602 aRv.MightThrowJSException();
2603 if (NS_FAILED(rv)) {
2604 workerinternals::ReportLoadError(aRv, rv, aScriptURL);
2605 return nullptr;
2608 aLoadInfo = stackLoadInfo.ptr();
2611 // NB: This has to be done before creating the WorkerPrivate, because it will
2612 // attempt to use static variables that are initialized in the RuntimeService
2613 // constructor.
2614 RuntimeService* runtimeService;
2616 if (!parent) {
2617 runtimeService = RuntimeService::GetOrCreateService();
2618 if (!runtimeService) {
2619 aRv.Throw(NS_ERROR_FAILURE);
2620 return nullptr;
2622 } else {
2623 runtimeService = RuntimeService::GetService();
2626 MOZ_ASSERT(runtimeService);
2628 // Don't create a worker with the shutting down RuntimeService.
2629 if (runtimeService->IsShuttingDown()) {
2630 aRv.Throw(NS_ERROR_UNEXPECTED);
2631 return nullptr;
2634 AgentClusterIdAndCoop idAndCoop =
2635 ComputeAgentClusterIdAndCoop(parent, aWorkerKind, aLoadInfo);
2637 RefPtr<WorkerPrivate> worker = new WorkerPrivate(
2638 parent, aScriptURL, aIsChromeWorker, aWorkerKind, aRequestCredentials,
2639 aWorkerType, aWorkerName, aServiceWorkerScope, *aLoadInfo, std::move(aId),
2640 idAndCoop.mId, idAndCoop.mCoop, std::move(aCancellationCallback),
2641 std::move(aTerminationCallback));
2643 // Gecko contexts always have an explicitly-set default locale (set by
2644 // XPJSRuntime::Initialize for the main thread, set by
2645 // WorkerThreadPrimaryRunnable::Run for workers just before running worker
2646 // code), so this is never SpiderMonkey's builtin default locale.
2647 JS::UniqueChars defaultLocale = JS_GetDefaultLocale(aCx);
2648 if (NS_WARN_IF(!defaultLocale)) {
2649 aRv.Throw(NS_ERROR_UNEXPECTED);
2650 return nullptr;
2653 worker->mDefaultLocale = std::move(defaultLocale);
2655 if (!runtimeService->RegisterWorker(*worker)) {
2656 aRv.Throw(NS_ERROR_UNEXPECTED);
2657 return nullptr;
2660 // From this point on (worker thread has been started) we
2661 // must keep ourself alive. We can now only be cleared by
2662 // ClearSelfAndParentEventTargetRef().
2663 worker->mSelfRef = worker;
2665 worker->EnableDebugger();
2667 MOZ_DIAGNOSTIC_ASSERT(worker->PrincipalIsValid());
2669 UniquePtr<SerializedStackHolder> stack;
2670 if (worker->IsWatchedByDevTools()) {
2671 stack = GetCurrentStackForNetMonitor(aCx);
2674 // This should be non-null for dedicated workers and null for Shared and
2675 // Service workers. All Encoding values are static and will live as long
2676 // as the process and the convention is to therefore use raw pointers.
2677 const mozilla::Encoding* aDocumentEncoding =
2678 NS_IsMainThread() && !worker->GetParent() && worker->GetDocument()
2679 ? worker->GetDocument()->GetDocumentCharacterSet().get()
2680 : nullptr;
2682 RefPtr<CompileScriptRunnable> compiler = new CompileScriptRunnable(
2683 worker, std::move(stack), aScriptURL, aDocumentEncoding);
2684 if (!compiler->Dispatch()) {
2685 aRv.Throw(NS_ERROR_UNEXPECTED);
2686 return nullptr;
2689 return worker.forget();
2692 nsresult WorkerPrivate::SetIsDebuggerReady(bool aReady) {
2693 AssertIsOnMainThread();
2694 MutexAutoLock lock(mMutex);
2696 if (mDebuggerReady == aReady) {
2697 return NS_OK;
2700 if (!aReady && mDebuggerRegistered) {
2701 // The debugger can only be marked as not ready during registration.
2702 return NS_ERROR_FAILURE;
2705 mDebuggerReady = aReady;
2707 if (aReady && mDebuggerRegistered) {
2708 // Dispatch all the delayed runnables without releasing the lock, to ensure
2709 // that the order in which debuggee runnables execute is the same as the
2710 // order in which they were originally dispatched.
2711 auto pending = std::move(mDelayedDebuggeeRunnables);
2712 for (uint32_t i = 0; i < pending.Length(); i++) {
2713 RefPtr<WorkerRunnable> runnable = std::move(pending[i]);
2714 nsresult rv = DispatchLockHeld(runnable.forget(), nullptr, lock);
2715 NS_ENSURE_SUCCESS(rv, rv);
2717 MOZ_RELEASE_ASSERT(mDelayedDebuggeeRunnables.IsEmpty());
2720 return NS_OK;
2723 // static
2724 nsresult WorkerPrivate::GetLoadInfo(
2725 JSContext* aCx, nsPIDOMWindowInner* aWindow, WorkerPrivate* aParent,
2726 const nsAString& aScriptURL, const enum WorkerType& aWorkerType,
2727 const RequestCredentials& aCredentials, bool aIsChromeWorker,
2728 LoadGroupBehavior aLoadGroupBehavior, WorkerKind aWorkerKind,
2729 WorkerLoadInfo* aLoadInfo) {
2730 using namespace mozilla::dom::workerinternals;
2732 MOZ_ASSERT(aCx);
2733 MOZ_ASSERT_IF(NS_IsMainThread(),
2734 aCx == nsContentUtils::GetCurrentJSContext());
2736 if (aWindow) {
2737 AssertIsOnMainThread();
2740 WorkerLoadInfo loadInfo;
2741 nsresult rv;
2743 if (aParent) {
2744 aParent->AssertIsOnWorkerThread();
2746 // If the parent is going away give up now.
2747 WorkerStatus parentStatus;
2749 MutexAutoLock lock(aParent->mMutex);
2750 parentStatus = aParent->mStatus;
2753 if (parentStatus > Running) {
2754 return NS_ERROR_FAILURE;
2757 // Passing a pointer to our stack loadInfo is safe here because this
2758 // method uses a sync runnable to get the channel from the main thread.
2759 rv = ChannelFromScriptURLWorkerThread(aCx, aParent, aScriptURL, aWorkerType,
2760 aCredentials, loadInfo);
2761 if (NS_FAILED(rv)) {
2762 MOZ_ALWAYS_TRUE(loadInfo.ProxyReleaseMainThreadObjects(aParent));
2763 return rv;
2766 // Now that we've spun the loop there's no guarantee that our parent is
2767 // still alive. We may have received control messages initiating shutdown.
2769 MutexAutoLock lock(aParent->mMutex);
2770 parentStatus = aParent->mStatus;
2773 if (parentStatus > Running) {
2774 MOZ_ALWAYS_TRUE(loadInfo.ProxyReleaseMainThreadObjects(aParent));
2775 return NS_ERROR_FAILURE;
2778 loadInfo.mTrials = aParent->Trials();
2779 loadInfo.mDomain = aParent->Domain();
2780 loadInfo.mFromWindow = aParent->IsFromWindow();
2781 loadInfo.mWindowID = aParent->WindowID();
2782 loadInfo.mAssociatedBrowsingContextID =
2783 aParent->AssociatedBrowsingContextID();
2784 loadInfo.mStorageAccess = aParent->StorageAccess();
2785 loadInfo.mUseRegularPrincipal = aParent->UseRegularPrincipal();
2786 loadInfo.mUsingStorageAccess = aParent->UsingStorageAccess();
2787 loadInfo.mCookieJarSettings = aParent->CookieJarSettings();
2788 if (loadInfo.mCookieJarSettings) {
2789 loadInfo.mCookieJarSettingsArgs = aParent->CookieJarSettingsArgs();
2791 loadInfo.mOriginAttributes = aParent->GetOriginAttributes();
2792 loadInfo.mServiceWorkersTestingInWindow =
2793 aParent->ServiceWorkersTestingInWindow();
2794 loadInfo.mIsThirdPartyContext = aParent->IsThirdPartyContext();
2795 loadInfo.mShouldResistFingerprinting = aParent->ShouldResistFingerprinting(
2796 RFPTarget::IsAlwaysEnabledForPrecompute);
2797 loadInfo.mOverriddenFingerprintingSettings =
2798 aParent->GetOverriddenFingerprintingSettings();
2799 loadInfo.mParentController = aParent->GlobalScope()->GetController();
2800 loadInfo.mWatchedByDevTools = aParent->IsWatchedByDevTools();
2801 } else {
2802 AssertIsOnMainThread();
2804 // Make sure that the IndexedDatabaseManager is set up
2805 IndexedDatabaseManager* idm = IndexedDatabaseManager::GetOrCreate();
2806 if (idm) {
2807 Unused << NS_WARN_IF(NS_FAILED(idm->EnsureLocale()));
2808 } else {
2809 NS_WARNING("Failed to get IndexedDatabaseManager!");
2812 nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
2813 MOZ_ASSERT(ssm);
2815 bool isChrome = nsContentUtils::IsSystemCaller(aCx);
2817 // First check to make sure the caller has permission to make a privileged
2818 // worker if they called the ChromeWorker/ChromeSharedWorker constructor.
2819 if (aIsChromeWorker && !isChrome) {
2820 return NS_ERROR_DOM_SECURITY_ERR;
2823 // Chrome callers (whether creating a ChromeWorker or Worker) always get the
2824 // system principal here as they're allowed to load anything. The script
2825 // loader will refuse to run any script that does not also have the system
2826 // principal.
2827 if (isChrome) {
2828 rv = ssm->GetSystemPrincipal(getter_AddRefs(loadInfo.mLoadingPrincipal));
2829 NS_ENSURE_SUCCESS(rv, rv);
2832 // See if we're being called from a window.
2833 nsCOMPtr<nsPIDOMWindowInner> globalWindow = aWindow;
2834 if (!globalWindow) {
2835 globalWindow = xpc::CurrentWindowOrNull(aCx);
2838 nsCOMPtr<Document> document;
2839 Maybe<ClientInfo> clientInfo;
2841 if (globalWindow) {
2842 // Only use the current inner window, and only use it if the caller can
2843 // access it.
2844 if (nsPIDOMWindowOuter* outerWindow = globalWindow->GetOuterWindow()) {
2845 loadInfo.mWindow = outerWindow->GetCurrentInnerWindow();
2848 loadInfo.mTrials =
2849 OriginTrials::FromWindow(nsGlobalWindowInner::Cast(loadInfo.mWindow));
2851 BrowsingContext* browsingContext = globalWindow->GetBrowsingContext();
2853 // TODO: fix this for SharedWorkers with multiple documents (bug
2854 // 1177935)
2855 loadInfo.mServiceWorkersTestingInWindow =
2856 browsingContext &&
2857 browsingContext->Top()->ServiceWorkersTestingEnabled();
2859 if (!loadInfo.mWindow ||
2860 (globalWindow != loadInfo.mWindow &&
2861 !nsContentUtils::CanCallerAccess(loadInfo.mWindow))) {
2862 return NS_ERROR_DOM_SECURITY_ERR;
2865 nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(loadInfo.mWindow);
2866 MOZ_ASSERT(sgo);
2868 loadInfo.mScriptContext = sgo->GetContext();
2869 NS_ENSURE_TRUE(loadInfo.mScriptContext, NS_ERROR_FAILURE);
2871 // If we're called from a window then we can dig out the principal and URI
2872 // from the document.
2873 document = loadInfo.mWindow->GetExtantDoc();
2874 NS_ENSURE_TRUE(document, NS_ERROR_FAILURE);
2876 loadInfo.mBaseURI = document->GetDocBaseURI();
2877 loadInfo.mLoadGroup = document->GetDocumentLoadGroup();
2878 NS_ENSURE_TRUE(loadInfo.mLoadGroup, NS_ERROR_FAILURE);
2880 clientInfo = globalWindow->GetClientInfo();
2882 // Use the document's NodePrincipal as loading principal if we're not
2883 // being called from chrome.
2884 if (!loadInfo.mLoadingPrincipal) {
2885 loadInfo.mLoadingPrincipal = document->NodePrincipal();
2886 NS_ENSURE_TRUE(loadInfo.mLoadingPrincipal, NS_ERROR_FAILURE);
2888 // We use the document's base domain to limit the number of workers
2889 // each domain can create. For sandboxed documents, we use the domain
2890 // of their first non-sandboxed document, walking up until we find
2891 // one. If we can't find one, we fall back to using the GUID of the
2892 // null principal as the base domain.
2893 if (document->GetSandboxFlags() & SANDBOXED_ORIGIN) {
2894 nsCOMPtr<Document> tmpDoc = document;
2895 do {
2896 tmpDoc = tmpDoc->GetInProcessParentDocument();
2897 } while (tmpDoc && tmpDoc->GetSandboxFlags() & SANDBOXED_ORIGIN);
2899 if (tmpDoc) {
2900 // There was an unsandboxed ancestor, yay!
2901 nsCOMPtr<nsIPrincipal> tmpPrincipal = tmpDoc->NodePrincipal();
2902 rv = tmpPrincipal->GetBaseDomain(loadInfo.mDomain);
2903 NS_ENSURE_SUCCESS(rv, rv);
2904 } else {
2905 // No unsandboxed ancestor, use our GUID.
2906 rv = loadInfo.mLoadingPrincipal->GetBaseDomain(loadInfo.mDomain);
2907 NS_ENSURE_SUCCESS(rv, rv);
2909 } else {
2910 // Document creating the worker is not sandboxed.
2911 rv = loadInfo.mLoadingPrincipal->GetBaseDomain(loadInfo.mDomain);
2912 NS_ENSURE_SUCCESS(rv, rv);
2916 NS_ENSURE_TRUE(NS_LoadGroupMatchesPrincipal(loadInfo.mLoadGroup,
2917 loadInfo.mLoadingPrincipal),
2918 NS_ERROR_FAILURE);
2920 nsCOMPtr<nsIPermissionManager> permMgr =
2921 do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
2922 NS_ENSURE_SUCCESS(rv, rv);
2924 uint32_t perm;
2925 rv = permMgr->TestPermissionFromPrincipal(loadInfo.mLoadingPrincipal,
2926 "systemXHR"_ns, &perm);
2927 NS_ENSURE_SUCCESS(rv, rv);
2929 loadInfo.mXHRParamsAllowed = perm == nsIPermissionManager::ALLOW_ACTION;
2931 loadInfo.mWatchedByDevTools =
2932 browsingContext && browsingContext->WatchedByDevTools();
2934 loadInfo.mReferrerInfo =
2935 ReferrerInfo::CreateForFetch(loadInfo.mLoadingPrincipal, document);
2936 loadInfo.mFromWindow = true;
2937 loadInfo.mWindowID = globalWindow->WindowID();
2938 loadInfo.mAssociatedBrowsingContextID =
2939 globalWindow->GetBrowsingContext()->Id();
2940 loadInfo.mStorageAccess = StorageAllowedForWindow(globalWindow);
2941 loadInfo.mUseRegularPrincipal = document->UseRegularPrincipal();
2942 loadInfo.mUsingStorageAccess = document->UsingStorageAccess();
2943 loadInfo.mShouldResistFingerprinting =
2944 document->ShouldResistFingerprinting(
2945 RFPTarget::IsAlwaysEnabledForPrecompute);
2946 loadInfo.mOverriddenFingerprintingSettings =
2947 document->GetOverriddenFingerprintingSettings();
2949 // This is an hack to deny the storage-access-permission for workers of
2950 // sub-iframes.
2951 if (loadInfo.mUsingStorageAccess &&
2952 StorageAllowedForDocument(document) != StorageAccess::eAllow) {
2953 loadInfo.mUsingStorageAccess = false;
2955 loadInfo.mIsThirdPartyContext =
2956 AntiTrackingUtils::IsThirdPartyWindow(globalWindow, nullptr);
2957 loadInfo.mCookieJarSettings = document->CookieJarSettings();
2958 if (loadInfo.mCookieJarSettings) {
2959 auto* cookieJarSettings =
2960 net::CookieJarSettings::Cast(loadInfo.mCookieJarSettings);
2961 cookieJarSettings->Serialize(loadInfo.mCookieJarSettingsArgs);
2963 StoragePrincipalHelper::GetRegularPrincipalOriginAttributes(
2964 document, loadInfo.mOriginAttributes);
2965 loadInfo.mParentController = globalWindow->GetController();
2966 loadInfo.mSecureContext = loadInfo.mWindow->IsSecureContext()
2967 ? WorkerLoadInfo::eSecureContext
2968 : WorkerLoadInfo::eInsecureContext;
2969 } else {
2970 // Not a window
2971 MOZ_ASSERT(isChrome);
2973 // We're being created outside of a window. Need to figure out the script
2974 // that is creating us in order for us to use relative URIs later on.
2975 JS::AutoFilename fileName;
2976 if (JS::DescribeScriptedCaller(aCx, &fileName)) {
2977 // In most cases, fileName is URI. In a few other cases
2978 // (e.g. xpcshell), fileName is a file path. Ideally, we would
2979 // prefer testing whether fileName parses as an URI and fallback
2980 // to file path in case of error, but Windows file paths have
2981 // the interesting property that they can be parsed as bogus
2982 // URIs (e.g. C:/Windows/Tmp is interpreted as scheme "C",
2983 // hostname "Windows", path "Tmp"), which defeats this algorithm.
2984 // Therefore, we adopt the opposite convention.
2985 nsCOMPtr<nsIFile> scriptFile =
2986 do_CreateInstance("@mozilla.org/file/local;1", &rv);
2987 if (NS_FAILED(rv)) {
2988 return rv;
2991 rv = scriptFile->InitWithPath(NS_ConvertUTF8toUTF16(fileName.get()));
2992 if (NS_SUCCEEDED(rv)) {
2993 rv = NS_NewFileURI(getter_AddRefs(loadInfo.mBaseURI), scriptFile);
2995 if (NS_FAILED(rv)) {
2996 // As expected, fileName is not a path, so proceed with
2997 // a uri.
2998 rv = NS_NewURI(getter_AddRefs(loadInfo.mBaseURI), fileName.get());
3000 if (NS_FAILED(rv)) {
3001 return rv;
3004 loadInfo.mXHRParamsAllowed = true;
3005 loadInfo.mFromWindow = false;
3006 loadInfo.mWindowID = UINT64_MAX;
3007 loadInfo.mStorageAccess = StorageAccess::eAllow;
3008 loadInfo.mUseRegularPrincipal = true;
3009 loadInfo.mUsingStorageAccess = false;
3010 loadInfo.mCookieJarSettings =
3011 mozilla::net::CookieJarSettings::Create(loadInfo.mLoadingPrincipal);
3012 loadInfo.mShouldResistFingerprinting =
3013 nsContentUtils::ShouldResistFingerprinting_dangerous(
3014 loadInfo.mLoadingPrincipal,
3015 "Unusual situation - we have no document or CookieJarSettings",
3016 RFPTarget::IsAlwaysEnabledForPrecompute);
3017 MOZ_ASSERT(loadInfo.mCookieJarSettings);
3018 auto* cookieJarSettings =
3019 net::CookieJarSettings::Cast(loadInfo.mCookieJarSettings);
3020 cookieJarSettings->Serialize(loadInfo.mCookieJarSettingsArgs);
3022 loadInfo.mOriginAttributes = OriginAttributes();
3023 loadInfo.mIsThirdPartyContext = false;
3026 MOZ_ASSERT(loadInfo.mLoadingPrincipal);
3027 MOZ_ASSERT(isChrome || !loadInfo.mDomain.IsEmpty());
3029 if (!loadInfo.mLoadGroup || aLoadGroupBehavior == OverrideLoadGroup) {
3030 OverrideLoadInfoLoadGroup(loadInfo, loadInfo.mLoadingPrincipal);
3032 MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(loadInfo.mLoadGroup,
3033 loadInfo.mLoadingPrincipal));
3035 // Top level workers' main script use the document charset for the script
3036 // uri encoding.
3037 nsCOMPtr<nsIURI> url;
3038 rv = nsContentUtils::NewURIWithDocumentCharset(
3039 getter_AddRefs(url), aScriptURL, document, loadInfo.mBaseURI);
3040 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
3042 rv = ChannelFromScriptURLMainThread(
3043 loadInfo.mLoadingPrincipal, document, loadInfo.mLoadGroup, url,
3044 aWorkerType, aCredentials, clientInfo, ContentPolicyType(aWorkerKind),
3045 loadInfo.mCookieJarSettings, loadInfo.mReferrerInfo,
3046 getter_AddRefs(loadInfo.mChannel));
3047 NS_ENSURE_SUCCESS(rv, rv);
3049 rv = NS_GetFinalChannelURI(loadInfo.mChannel,
3050 getter_AddRefs(loadInfo.mResolvedScriptURI));
3051 NS_ENSURE_SUCCESS(rv, rv);
3053 // We need the correct hasStoragePermission flag for the channel here since
3054 // we will do a content blocking check later when we set the storage
3055 // principal for the worker. The channel here won't be opened when we do the
3056 // check later, so the hasStoragePermission flag is incorrect. To address
3057 // this, We copy the hasStoragePermission flag from the document if there is
3058 // a window. The worker is created as the same origin of the window. So, the
3059 // worker is supposed to have the same storage permission as the window as
3060 // well as the hasStoragePermission flag.
3061 nsCOMPtr<nsILoadInfo> channelLoadInfo = loadInfo.mChannel->LoadInfo();
3062 rv = channelLoadInfo->SetStoragePermission(
3063 loadInfo.mUsingStorageAccess ? nsILoadInfo::HasStoragePermission
3064 : nsILoadInfo::NoStoragePermission);
3065 NS_ENSURE_SUCCESS(rv, rv);
3067 rv = loadInfo.SetPrincipalsAndCSPFromChannel(loadInfo.mChannel);
3068 NS_ENSURE_SUCCESS(rv, rv);
3071 MOZ_DIAGNOSTIC_ASSERT(loadInfo.mLoadingPrincipal);
3072 MOZ_DIAGNOSTIC_ASSERT(loadInfo.PrincipalIsValid());
3074 *aLoadInfo = std::move(loadInfo);
3075 return NS_OK;
3078 // static
3079 void WorkerPrivate::OverrideLoadInfoLoadGroup(WorkerLoadInfo& aLoadInfo,
3080 nsIPrincipal* aPrincipal) {
3081 MOZ_ASSERT(!aLoadInfo.mInterfaceRequestor);
3082 MOZ_ASSERT(aLoadInfo.mLoadingPrincipal == aPrincipal);
3084 aLoadInfo.mInterfaceRequestor =
3085 new WorkerLoadInfo::InterfaceRequestor(aPrincipal, aLoadInfo.mLoadGroup);
3086 aLoadInfo.mInterfaceRequestor->MaybeAddBrowserChild(aLoadInfo.mLoadGroup);
3088 // NOTE: this defaults the load context to:
3089 // - private browsing = false
3090 // - content = true
3091 // - use remote tabs = false
3092 nsCOMPtr<nsILoadGroup> loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
3094 nsresult rv =
3095 loadGroup->SetNotificationCallbacks(aLoadInfo.mInterfaceRequestor);
3096 MOZ_ALWAYS_SUCCEEDS(rv);
3098 aLoadInfo.mLoadGroup = std::move(loadGroup);
3100 MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(aLoadInfo.mLoadGroup, aPrincipal));
3103 void WorkerPrivate::RunLoopNeverRan() {
3104 LOG(WorkerLog(), ("WorkerPrivate::RunLoopNeverRan [%p]", this));
3105 // RunLoopNeverRan is only called in WorkerThreadPrimaryRunnable::Run() to
3106 // handle cases
3107 // 1. Fail to get BackgroundChild for the worker thread or
3108 // 2. Fail to initialize the worker's JS context
3109 // However, mPreStartRunnables had already dispatched in
3110 // WorkerThread::SetWorkerPrivateInWorkerThread() where beforing above jobs
3111 // start. So we need to clean up these dispatched runnables for the worker
3112 // thread.
3114 auto data = mWorkerThreadAccessible.Access();
3115 RefPtr<WorkerThread> thread;
3117 MutexAutoLock lock(mMutex);
3118 // WorkerPrivate::DoRunLoop() is never called, so CompileScriptRunnable
3119 // should not execute yet. However, the Worker is going to "Dead", flip the
3120 // mCancelBeforeWorkerScopeConstructed to true for the dispatched runnables
3121 // to indicate runnables there is no valid WorkerGlobalScope for executing.
3122 MOZ_ASSERT(!data->mCancelBeforeWorkerScopeConstructed);
3123 data->mCancelBeforeWorkerScopeConstructed.Flip();
3124 // Switch State to Dead
3125 mStatus = Dead;
3126 thread = mThread;
3129 // Clear the dispatched mPreStartRunnables.
3130 if (thread && NS_HasPendingEvents(thread)) {
3131 NS_ProcessPendingEvents(nullptr);
3134 // After mStatus is set to Dead there can be no more
3135 // WorkerControlRunnables so no need to lock here.
3136 if (!mControlQueue.IsEmpty()) {
3137 WorkerControlRunnable* runnable = nullptr;
3138 while (mControlQueue.Pop(runnable)) {
3139 runnable->Cancel();
3140 runnable->Release();
3144 // There should be no StrongWorkerRefs, child Workers, and Timeouts, but
3145 // WeakWorkerRefs could. WorkerThreadPrimaryRunnable could have created a
3146 // PerformanceStorageWorker which holds a WeakWorkerRef.
3147 // Notify WeakWorkerRefs with Dead status.
3148 NotifyWorkerRefs(Dead);
3150 ScheduleDeletion(WorkerPrivate::WorkerRan);
3153 void WorkerPrivate::UnrootGlobalScopes() {
3154 LOG(WorkerLog(), ("WorkerPrivate::UnrootGlobalScopes [%p]", this));
3155 auto data = mWorkerThreadAccessible.Access();
3157 RefPtr<WorkerDebuggerGlobalScope> debugScope = data->mDebuggerScope.forget();
3158 if (debugScope) {
3159 MOZ_ASSERT(debugScope->mWorkerPrivate == this);
3161 RefPtr<WorkerGlobalScope> scope = data->mScope.forget();
3162 if (scope) {
3163 MOZ_ASSERT(scope->mWorkerPrivate == this);
3167 void WorkerPrivate::DoRunLoop(JSContext* aCx) {
3168 LOG(WorkerLog(), ("WorkerPrivate::DoRunLoop [%p]", this));
3169 auto data = mWorkerThreadAccessible.Access();
3170 MOZ_RELEASE_ASSERT(!GetExecutionManager());
3172 RefPtr<WorkerThread> thread;
3174 MutexAutoLock lock(mMutex);
3175 mJSContext = aCx;
3176 // mThread is set before we enter, and is never changed during DoRunLoop.
3177 // copy to local so we don't trigger mutex analysis
3178 MOZ_ASSERT(mThread);
3179 thread = mThread;
3181 MOZ_ASSERT(mStatus == Pending);
3182 mStatus = Running;
3185 // Now that we've done that, we can go ahead and set up our AutoJSAPI. We
3186 // can't before this point, because it can't find the right JSContext before
3187 // then, since it gets it from our mJSContext.
3188 AutoJSAPI jsapi;
3189 jsapi.Init();
3190 MOZ_ASSERT(jsapi.cx() == aCx);
3192 EnableMemoryReporter();
3194 InitializeGCTimers();
3196 bool checkFinalGCCC =
3197 StaticPrefs::dom_workers_GCCC_on_potentially_last_event();
3199 bool debuggerRunnablesPending = false;
3200 bool normalRunnablesPending = false;
3201 auto noRunnablesPendingAndKeepAlive =
3202 [&debuggerRunnablesPending, &normalRunnablesPending, &thread, this]()
3203 MOZ_REQUIRES(mMutex) {
3204 // We want to keep both pending flags always updated while looping.
3205 debuggerRunnablesPending = !mDebuggerQueue.IsEmpty();
3206 normalRunnablesPending = NS_HasPendingEvents(thread);
3208 bool anyRunnablesPending = !mControlQueue.IsEmpty() ||
3209 debuggerRunnablesPending ||
3210 normalRunnablesPending;
3211 bool keepWorkerAlive = mStatus == Running || HasActiveWorkerRefs();
3213 return (!anyRunnablesPending && keepWorkerAlive);
3216 for (;;) {
3217 WorkerStatus currentStatus;
3219 if (checkFinalGCCC) {
3220 // If we get here after the last event ran but someone holds a WorkerRef
3221 // and there is no other logic to release that WorkerRef than lazily
3222 // through GC/CC, we might block forever on the next WaitForWorkerEvents.
3223 // Every object holding a WorkerRef should really have a straight,
3224 // deterministic line from the WorkerRef's callback being invoked to the
3225 // WorkerRef being released which is supported by strong-references that
3226 // can't form a cycle.
3227 bool mayNeedFinalGCCC = false;
3229 MutexAutoLock lock(mMutex);
3231 currentStatus = mStatus;
3232 mayNeedFinalGCCC =
3233 (mStatus >= Canceling && HasActiveWorkerRefs() &&
3234 !debuggerRunnablesPending && !normalRunnablesPending &&
3235 data->mPerformedShutdownAfterLastContentTaskExecuted);
3237 if (mayNeedFinalGCCC) {
3238 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
3239 // WorkerRef::ReleaseWorker will check this flag via
3240 // AssertIsNotPotentiallyLastGCCCRunning
3241 data->mIsPotentiallyLastGCCCRunning = true;
3242 #endif
3243 // GarbageCollectInternal will trigger both GC and CC
3244 GarbageCollectInternal(aCx, true /* aShrinking */,
3245 true /* aCollectChildren */);
3246 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
3247 data->mIsPotentiallyLastGCCCRunning = false;
3248 #endif
3253 MutexAutoLock lock(mMutex);
3254 if (checkFinalGCCC && currentStatus != mStatus) {
3255 // Something moved our status while we were supposed to check for a
3256 // potentially needed GC/CC. Just check again.
3257 continue;
3260 // Wait for a runnable to arrive that we can execute, or for it to be okay
3261 // to shutdown this worker once all holders have been removed.
3262 // Holders may be removed from inside normal runnables, but we don't
3263 // check for that after processing normal runnables, so we need to let
3264 // control flow to the shutdown logic without blocking.
3265 while (noRunnablesPendingAndKeepAlive()) {
3266 // We pop out to this loop when there are no pending events.
3267 // If we don't reset these, we may not re-enter ProcessNextEvent()
3268 // until we have events to process, and it may seem like we have
3269 // an event running for a very long time.
3270 thread->SetRunningEventDelay(TimeDuration(), TimeStamp());
3272 mWorkerLoopIsIdle = true;
3274 WaitForWorkerEvents();
3276 mWorkerLoopIsIdle = false;
3279 auto result = ProcessAllControlRunnablesLocked();
3280 if (result != ProcessAllControlRunnablesResult::Nothing) {
3281 // Update all saved runnable flags for side effect for the
3282 // loop check about transitioning to Killing below.
3283 (void)noRunnablesPendingAndKeepAlive();
3286 currentStatus = mStatus;
3289 // Status transitions to Closing/Canceling and there are no SyncLoops,
3290 // set global start dying, disconnect EventTargetObjects and
3291 // WebTaskScheduler.
3292 // The Worker might switch to the "Killing" immediately then directly exits
3293 // DoRunLoop(). Before exiting the DoRunLoop(), explicitly disconnecting the
3294 // WorkerGlobalScope's EventTargetObject here would help to fail runnable
3295 // dispatching when the Worker is in the status changing.
3296 if (currentStatus >= Closing &&
3297 !data->mPerformedShutdownAfterLastContentTaskExecuted) {
3298 data->mPerformedShutdownAfterLastContentTaskExecuted.Flip();
3299 if (data->mScope) {
3300 data->mScope->NoteTerminating();
3301 data->mScope->DisconnectGlobalTeardownObservers();
3302 if (data->mScope->GetExistingScheduler()) {
3303 data->mScope->GetExistingScheduler()->Disconnect();
3308 // Transition from Canceling to Killing and exit this loop when:
3309 // * All (non-weak) WorkerRefs have been released.
3310 // * There are no runnables pending. This is intended to let same-thread
3311 // dispatches as part of cleanup be able to run to completion, but any
3312 // logic that still wants async things to happen should be holding a
3313 // StrongWorkerRef.
3314 if (currentStatus != Running && !HasActiveWorkerRefs() &&
3315 !normalRunnablesPending && !debuggerRunnablesPending) {
3316 // Now we are ready to kill the worker thread.
3317 if (currentStatus == Canceling) {
3318 NotifyInternal(Killing);
3320 #ifdef DEBUG
3322 MutexAutoLock lock(mMutex);
3323 currentStatus = mStatus;
3325 MOZ_ASSERT(currentStatus == Killing);
3326 #else
3327 currentStatus = Killing;
3328 #endif
3331 // If we're supposed to die then we should exit the loop.
3332 if (currentStatus == Killing) {
3333 // We are about to destroy worker, report all use counters.
3334 ReportUseCounters();
3336 // Flush uncaught rejections immediately, without
3337 // waiting for a next tick.
3338 PromiseDebugging::FlushUncaughtRejections();
3340 ShutdownGCTimers();
3342 DisableMemoryReporter();
3345 MutexAutoLock lock(mMutex);
3347 mStatus = Dead;
3348 mJSContext = nullptr;
3351 // After mStatus is set to Dead there can be no more
3352 // WorkerControlRunnables so no need to lock here.
3353 if (!mControlQueue.IsEmpty()) {
3354 LOG(WorkerLog(),
3355 ("WorkerPrivate::DoRunLoop [%p] dropping control runnables in "
3356 "Dead status",
3357 this));
3358 WorkerControlRunnable* runnable = nullptr;
3359 while (mControlQueue.Pop(runnable)) {
3360 runnable->Cancel();
3361 runnable->Release();
3365 // We do not need the timeouts any more, they have been canceled
3366 // by NotifyInternal(Killing) above if they were active.
3367 UnlinkTimeouts();
3369 return;
3373 if (debuggerRunnablesPending || normalRunnablesPending) {
3374 // Start the periodic GC timer if it is not already running.
3375 SetGCTimerMode(PeriodicTimer);
3378 if (debuggerRunnablesPending) {
3379 WorkerRunnable* runnable = nullptr;
3382 MutexAutoLock lock(mMutex);
3384 mDebuggerQueue.Pop(runnable);
3385 debuggerRunnablesPending = !mDebuggerQueue.IsEmpty();
3389 MOZ_ASSERT(runnable);
3390 AUTO_PROFILE_FOLLOWING_RUNNABLE(runnable);
3391 static_cast<nsIRunnable*>(runnable)->Run();
3393 runnable->Release();
3395 CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
3396 ccjs->PerformDebuggerMicroTaskCheckpoint();
3398 if (debuggerRunnablesPending) {
3399 WorkerDebuggerGlobalScope* globalScope = DebuggerGlobalScope();
3400 // If the worker was canceled before ever creating its content global
3401 // then mCancelBeforeWorkerScopeConstructed could have been flipped and
3402 // all of the WorkerDebuggerRunnables canceled, so the debugger global
3403 // would never have been created.
3404 if (globalScope) {
3405 // Now *might* be a good time to GC. Let the JS engine make the
3406 // decision.
3407 JSAutoRealm ar(aCx, globalScope->GetGlobalJSObject());
3408 JS_MaybeGC(aCx);
3411 } else if (normalRunnablesPending) {
3412 // Process a single runnable from the main queue.
3413 NS_ProcessNextEvent(thread, false);
3415 normalRunnablesPending = NS_HasPendingEvents(thread);
3416 if (normalRunnablesPending && GlobalScope()) {
3417 // Now *might* be a good time to GC. Let the JS engine make the
3418 // decision.
3419 JSAutoRealm ar(aCx, GlobalScope()->GetGlobalJSObject());
3420 JS_MaybeGC(aCx);
3424 // Checking the background actors if needed, any runnable execution could
3425 // release background actors which blocks GC/CC on
3426 // WorkerPrivate::mParentEventTargetRef.
3427 if (currentStatus < Canceling) {
3428 UpdateCCFlag(CCFlag::CheckBackgroundActors);
3431 if (!debuggerRunnablesPending && !normalRunnablesPending) {
3432 // Both the debugger event queue and the normal event queue has been
3433 // exhausted, cancel the periodic GC timer and schedule the idle GC timer.
3434 SetGCTimerMode(IdleTimer);
3437 // If the worker thread is spamming the main thread faster than it can
3438 // process the work, then pause the worker thread until the main thread
3439 // catches up.
3440 size_t queuedEvents = mMainThreadEventTargetForMessaging->Length() +
3441 mMainThreadDebuggeeEventTarget->Length();
3442 if (queuedEvents > 5000) {
3443 // Note, postMessage uses mMainThreadDebuggeeEventTarget!
3444 mMainThreadDebuggeeEventTarget->AwaitIdle();
3448 MOZ_CRASH("Shouldn't get here!");
3451 namespace {
3453 * If there is a current CycleCollectedJSContext, return its recursion depth,
3454 * otherwise return 1.
3456 * In the edge case where a worker is starting up so late that PBackground is
3457 * already shutting down, the cycle collected context will never be created,
3458 * but we will need to drain the event loop in ClearMainEventQueue. This will
3459 * result in a normal NS_ProcessPendingEvents invocation which will call
3460 * WorkerPrivate::OnProcessNextEvent and WorkerPrivate::AfterProcessNextEvent
3461 * which want to handle the need to process control runnables and perform a
3462 * sanity check assertion, respectively.
3464 * We claim a depth of 1 when there's no CCJS because this most corresponds to
3465 * reality, but this doesn't meant that other code might want to drain various
3466 * runnable queues as part of this cleanup.
3468 uint32_t GetEffectiveEventLoopRecursionDepth() {
3469 auto* ccjs = CycleCollectedJSContext::Get();
3470 if (ccjs) {
3471 return ccjs->RecursionDepth();
3474 return 1;
3477 } // namespace
3479 void WorkerPrivate::OnProcessNextEvent() {
3480 AssertIsOnWorkerThread();
3482 uint32_t recursionDepth = GetEffectiveEventLoopRecursionDepth();
3483 MOZ_ASSERT(recursionDepth);
3485 // Normally we process control runnables in DoRunLoop or RunCurrentSyncLoop.
3486 // However, it's possible that non-worker C++ could spin its own nested event
3487 // loop, and in that case we must ensure that we continue to process control
3488 // runnables here.
3489 if (recursionDepth > 1 && mSyncLoopStack.Length() < recursionDepth - 1) {
3490 Unused << ProcessAllControlRunnables();
3491 // There's no running JS, and no state to revalidate, so we can ignore the
3492 // return value.
3496 void WorkerPrivate::AfterProcessNextEvent() {
3497 AssertIsOnWorkerThread();
3498 MOZ_ASSERT(GetEffectiveEventLoopRecursionDepth());
3501 nsISerialEventTarget* WorkerPrivate::MainThreadEventTargetForMessaging() {
3502 return mMainThreadEventTargetForMessaging;
3505 nsresult WorkerPrivate::DispatchToMainThreadForMessaging(nsIRunnable* aRunnable,
3506 uint32_t aFlags) {
3507 nsCOMPtr<nsIRunnable> r = aRunnable;
3508 return DispatchToMainThreadForMessaging(r.forget(), aFlags);
3511 nsresult WorkerPrivate::DispatchToMainThreadForMessaging(
3512 already_AddRefed<nsIRunnable> aRunnable, uint32_t aFlags) {
3513 return mMainThreadEventTargetForMessaging->Dispatch(std::move(aRunnable),
3514 aFlags);
3517 nsISerialEventTarget* WorkerPrivate::MainThreadEventTarget() {
3518 return mMainThreadEventTarget;
3521 nsresult WorkerPrivate::DispatchToMainThread(nsIRunnable* aRunnable,
3522 uint32_t aFlags) {
3523 nsCOMPtr<nsIRunnable> r = aRunnable;
3524 return DispatchToMainThread(r.forget(), aFlags);
3527 nsresult WorkerPrivate::DispatchToMainThread(
3528 already_AddRefed<nsIRunnable> aRunnable, uint32_t aFlags) {
3529 return mMainThreadEventTarget->Dispatch(std::move(aRunnable), aFlags);
3532 nsresult WorkerPrivate::DispatchDebuggeeToMainThread(
3533 already_AddRefed<WorkerDebuggeeRunnable> aRunnable, uint32_t aFlags) {
3534 return mMainThreadDebuggeeEventTarget->Dispatch(std::move(aRunnable), aFlags);
3537 nsISerialEventTarget* WorkerPrivate::ControlEventTarget() {
3538 return mWorkerControlEventTarget;
3541 nsISerialEventTarget* WorkerPrivate::HybridEventTarget() {
3542 return mWorkerHybridEventTarget;
3545 ClientType WorkerPrivate::GetClientType() const {
3546 switch (Kind()) {
3547 case WorkerKindDedicated:
3548 return ClientType::Worker;
3549 case WorkerKindShared:
3550 return ClientType::Sharedworker;
3551 case WorkerKindService:
3552 return ClientType::Serviceworker;
3553 default:
3554 MOZ_CRASH("unknown worker type!");
3558 UniquePtr<ClientSource> WorkerPrivate::CreateClientSource() {
3559 auto data = mWorkerThreadAccessible.Access();
3560 MOZ_ASSERT(!data->mScope, "Client should be created before the global");
3562 auto clientSource = ClientManager::CreateSource(
3563 GetClientType(), mWorkerHybridEventTarget,
3564 StoragePrincipalHelper::ShouldUsePartitionPrincipalForServiceWorker(this)
3565 ? GetPartitionedPrincipalInfo()
3566 : GetPrincipalInfo());
3567 MOZ_DIAGNOSTIC_ASSERT(clientSource);
3569 clientSource->SetAgentClusterId(mAgentClusterId);
3571 if (data->mFrozen) {
3572 clientSource->Freeze();
3575 // Shortly after the client is reserved we will try loading the main script
3576 // for the worker. This may get intercepted by the ServiceWorkerManager
3577 // which will then try to create a ClientHandle. Its actually possible for
3578 // the main thread to create this ClientHandle before our IPC message creating
3579 // the ClientSource completes. To avoid this race we synchronously ping our
3580 // parent Client actor here. This ensure the worker ClientSource is created
3581 // in the parent before the main thread might try reaching it with a
3582 // ClientHandle.
3584 // An alternative solution would have been to handle the out-of-order
3585 // operations on the parent side. We could have created a small window where
3586 // we allow ClientHandle objects to exist without a ClientSource. We would
3587 // then time out these handles if they stayed orphaned for too long. This
3588 // approach would be much more complex, but also avoid this extra bit of
3589 // latency when starting workers.
3591 // Note, we only have to do this for workers that can be controlled by a
3592 // service worker. So avoid the sync overhead here if we are starting a
3593 // service worker or a chrome worker.
3594 if (Kind() != WorkerKindService && !IsChromeWorker()) {
3595 clientSource->WorkerSyncPing(this);
3598 return clientSource;
3601 bool WorkerPrivate::EnsureCSPEventListener() {
3602 if (!mCSPEventListener) {
3603 mCSPEventListener = WorkerCSPEventListener::Create(this);
3604 if (NS_WARN_IF(!mCSPEventListener)) {
3605 return false;
3608 return true;
3611 nsICSPEventListener* WorkerPrivate::CSPEventListener() const {
3612 MOZ_ASSERT(mCSPEventListener);
3613 return mCSPEventListener;
3616 void WorkerPrivate::EnsurePerformanceStorage() {
3617 AssertIsOnWorkerThread();
3619 if (!mPerformanceStorage) {
3620 mPerformanceStorage = PerformanceStorageWorker::Create(this);
3624 bool WorkerPrivate::GetExecutionGranted() const {
3625 auto data = mWorkerThreadAccessible.Access();
3626 return data->mJSThreadExecutionGranted;
3629 void WorkerPrivate::SetExecutionGranted(bool aGranted) {
3630 auto data = mWorkerThreadAccessible.Access();
3631 data->mJSThreadExecutionGranted = aGranted;
3634 void WorkerPrivate::ScheduleTimeSliceExpiration(uint32_t aDelay) {
3635 auto data = mWorkerThreadAccessible.Access();
3637 if (!data->mTSTimer) {
3638 data->mTSTimer = NS_NewTimer();
3639 MOZ_ALWAYS_SUCCEEDS(data->mTSTimer->SetTarget(mWorkerControlEventTarget));
3642 // Whenever an event is scheduled on the WorkerControlEventTarget an
3643 // interrupt is automatically requested which causes us to yield JS execution
3644 // and the next JS execution in the queue to execute.
3645 // This allows for simple code reuse of the existing interrupt callback code
3646 // used for control events.
3647 MOZ_ALWAYS_SUCCEEDS(data->mTSTimer->InitWithNamedFuncCallback(
3648 [](nsITimer* Timer, void* aClosure) { return; }, nullptr, aDelay,
3649 nsITimer::TYPE_ONE_SHOT, "TimeSliceExpirationTimer"));
3652 void WorkerPrivate::CancelTimeSliceExpiration() {
3653 auto data = mWorkerThreadAccessible.Access();
3654 MOZ_ALWAYS_SUCCEEDS(data->mTSTimer->Cancel());
3657 JSExecutionManager* WorkerPrivate::GetExecutionManager() const {
3658 auto data = mWorkerThreadAccessible.Access();
3659 return data->mExecutionManager.get();
3662 void WorkerPrivate::SetExecutionManager(JSExecutionManager* aManager) {
3663 auto data = mWorkerThreadAccessible.Access();
3664 data->mExecutionManager = aManager;
3667 void WorkerPrivate::ExecutionReady() {
3668 auto data = mWorkerThreadAccessible.Access();
3670 MutexAutoLock lock(mMutex);
3671 if (mStatus >= Canceling) {
3672 return;
3676 data->mScope->MutableClientSourceRef().WorkerExecutionReady(this);
3678 if (ExtensionAPIAllowed()) {
3679 extensions::CreateAndDispatchInitWorkerContextRunnable();
3683 void WorkerPrivate::InitializeGCTimers() {
3684 auto data = mWorkerThreadAccessible.Access();
3686 // We need timers for GC. The basic plan is to run a non-shrinking GC
3687 // periodically (PERIODIC_GC_TIMER_DELAY_SEC) while the worker is running.
3688 // Once the worker goes idle we set a short (IDLE_GC_TIMER_DELAY_SEC) timer to
3689 // run a shrinking GC.
3690 data->mPeriodicGCTimer = NS_NewTimer();
3691 data->mIdleGCTimer = NS_NewTimer();
3693 data->mPeriodicGCTimerRunning = false;
3694 data->mIdleGCTimerRunning = false;
3697 void WorkerPrivate::SetGCTimerMode(GCTimerMode aMode) {
3698 auto data = mWorkerThreadAccessible.Access();
3700 if (!data->mPeriodicGCTimer || !data->mIdleGCTimer) {
3701 // GC timers have been cleared already.
3702 return;
3705 if (aMode == NoTimer) {
3706 MOZ_ALWAYS_SUCCEEDS(data->mPeriodicGCTimer->Cancel());
3707 data->mPeriodicGCTimerRunning = false;
3708 MOZ_ALWAYS_SUCCEEDS(data->mIdleGCTimer->Cancel());
3709 data->mIdleGCTimerRunning = false;
3710 return;
3713 WorkerStatus status;
3715 MutexAutoLock lock(mMutex);
3716 status = mStatus;
3719 if (status >= Killing) {
3720 ShutdownGCTimers();
3721 return;
3724 // If the idle timer is running, don't cancel it when the periodic timer
3725 // is scheduled since we do want shrinking GC to be called occasionally.
3726 if (aMode == PeriodicTimer && data->mPeriodicGCTimerRunning) {
3727 return;
3730 if (aMode == IdleTimer) {
3731 if (!data->mPeriodicGCTimerRunning) {
3732 // Since running idle GC cancels both GC timers, after that we want
3733 // first at least periodic GC timer getting activated, since that tells
3734 // us that there have been some non-control tasks to process. Otherwise
3735 // idle GC timer would keep running all the time.
3736 return;
3739 // Cancel the periodic timer now, since the event loop is (in the common
3740 // case) empty now.
3741 MOZ_ALWAYS_SUCCEEDS(data->mPeriodicGCTimer->Cancel());
3742 data->mPeriodicGCTimerRunning = false;
3744 if (data->mIdleGCTimerRunning) {
3745 return;
3749 MOZ_ASSERT(aMode == PeriodicTimer || aMode == IdleTimer);
3751 uint32_t delay = 0;
3752 int16_t type = nsITimer::TYPE_ONE_SHOT;
3753 nsTimerCallbackFunc callback = nullptr;
3754 const char* name = nullptr;
3755 nsITimer* timer = nullptr;
3757 if (aMode == PeriodicTimer) {
3758 delay = PERIODIC_GC_TIMER_DELAY_SEC * 1000;
3759 type = nsITimer::TYPE_REPEATING_SLACK;
3760 callback = PeriodicGCTimerCallback;
3761 name = "dom::PeriodicGCTimerCallback";
3762 timer = data->mPeriodicGCTimer;
3763 data->mPeriodicGCTimerRunning = true;
3764 LOG(WorkerLog(), ("Worker %p scheduled periodic GC timer\n", this));
3765 } else {
3766 delay = IDLE_GC_TIMER_DELAY_SEC * 1000;
3767 type = nsITimer::TYPE_ONE_SHOT;
3768 callback = IdleGCTimerCallback;
3769 name = "dom::IdleGCTimerCallback";
3770 timer = data->mIdleGCTimer;
3771 data->mIdleGCTimerRunning = true;
3772 LOG(WorkerLog(), ("Worker %p scheduled idle GC timer\n", this));
3775 MOZ_ALWAYS_SUCCEEDS(timer->SetTarget(mWorkerControlEventTarget));
3776 MOZ_ALWAYS_SUCCEEDS(
3777 timer->InitWithNamedFuncCallback(callback, this, delay, type, name));
3780 void WorkerPrivate::ShutdownGCTimers() {
3781 auto data = mWorkerThreadAccessible.Access();
3783 MOZ_ASSERT(!data->mPeriodicGCTimer == !data->mIdleGCTimer);
3785 if (!data->mPeriodicGCTimer && !data->mIdleGCTimer) {
3786 return;
3789 // Always make sure the timers are canceled.
3790 MOZ_ALWAYS_SUCCEEDS(data->mPeriodicGCTimer->Cancel());
3791 MOZ_ALWAYS_SUCCEEDS(data->mIdleGCTimer->Cancel());
3793 LOG(WorkerLog(), ("Worker %p killed the GC timers\n", this));
3795 data->mPeriodicGCTimer = nullptr;
3796 data->mIdleGCTimer = nullptr;
3797 data->mPeriodicGCTimerRunning = false;
3798 data->mIdleGCTimerRunning = false;
3801 bool WorkerPrivate::InterruptCallback(JSContext* aCx) {
3802 auto data = mWorkerThreadAccessible.Access();
3804 AutoYieldJSThreadExecution yield;
3806 // If we are here it's because a WorkerControlRunnable has been dispatched.
3807 // The runnable could be processed here or it could have already been
3808 // processed by a sync event loop.
3809 // The most important thing this method must do, is to decide if the JS
3810 // execution should continue or not. If the runnable returns an error or if
3811 // the worker status is >= Canceling, we should stop the JS execution.
3813 MOZ_ASSERT(!JS_IsExceptionPending(aCx));
3815 bool mayContinue = true;
3816 bool scheduledIdleGC = false;
3818 for (;;) {
3819 // Run all control events now.
3820 auto result = ProcessAllControlRunnables();
3821 if (result == ProcessAllControlRunnablesResult::Abort) {
3822 mayContinue = false;
3825 bool mayFreeze = data->mFrozen;
3828 MutexAutoLock lock(mMutex);
3830 if (mayFreeze) {
3831 mayFreeze = mStatus <= Running;
3834 if (mStatus >= Canceling) {
3835 mayContinue = false;
3839 if (!mayContinue || !mayFreeze) {
3840 break;
3843 // Cancel the periodic GC timer here before freezing. The idle GC timer
3844 // will clean everything up once it runs.
3845 if (!scheduledIdleGC) {
3846 SetGCTimerMode(IdleTimer);
3847 scheduledIdleGC = true;
3850 while ((mayContinue = MayContinueRunning())) {
3851 MutexAutoLock lock(mMutex);
3852 if (!mControlQueue.IsEmpty()) {
3853 break;
3856 WaitForWorkerEvents();
3860 if (!mayContinue) {
3861 // We want only uncatchable exceptions here.
3862 NS_ASSERTION(!JS_IsExceptionPending(aCx),
3863 "Should not have an exception set here!");
3864 return false;
3867 // Make sure the periodic timer gets turned back on here.
3868 SetGCTimerMode(PeriodicTimer);
3870 return true;
3873 void WorkerPrivate::CloseInternal() {
3874 AssertIsOnWorkerThread();
3875 NotifyInternal(Closing);
3878 bool WorkerPrivate::IsOnCurrentThread() {
3879 // May be called on any thread!
3881 MOZ_ASSERT(mPRThread);
3882 return PR_GetCurrentThread() == mPRThread;
3885 void WorkerPrivate::ScheduleDeletion(WorkerRanOrNot aRanOrNot) {
3886 AssertIsOnWorkerThread();
3888 // mWorkerThreadAccessible's accessor must be destructed before
3889 // the scheduled Runnable gets to run.
3890 auto data = mWorkerThreadAccessible.Access();
3891 MOZ_ASSERT(data->mChildWorkers.IsEmpty());
3893 MOZ_RELEASE_ASSERT(!data->mDeletionScheduled);
3894 data->mDeletionScheduled.Flip();
3896 MOZ_ASSERT(mSyncLoopStack.IsEmpty());
3897 MOZ_ASSERT(mPostSyncLoopOperations == 0);
3899 // If Worker is never ran, clear the mPreStartRunnables. To let the resource
3900 // hold by the pre-submmited runnables.
3901 if (WorkerNeverRan == aRanOrNot) {
3902 ClearPreStartRunnables();
3905 #ifdef DEBUG
3906 if (WorkerRan == aRanOrNot) {
3907 nsIThread* currentThread = NS_GetCurrentThread();
3908 MOZ_ASSERT(currentThread);
3909 // On the worker thread WorkerRunnable will refuse to run if not nested
3910 // on top of a WorkerThreadPrimaryRunnable.
3911 Unused << NS_WARN_IF(NS_HasPendingEvents(currentThread));
3913 #endif
3915 if (WorkerPrivate* parent = GetParent()) {
3916 RefPtr<WorkerFinishedRunnable> runnable =
3917 new WorkerFinishedRunnable(parent, this);
3918 if (!runnable->Dispatch()) {
3919 NS_WARNING("Failed to dispatch runnable!");
3921 } else {
3922 if (ExtensionAPIAllowed()) {
3923 MOZ_ASSERT(IsServiceWorker());
3924 RefPtr<Runnable> extWorkerRunnable =
3925 extensions::CreateWorkerDestroyedRunnable(ServiceWorkerID(),
3926 GetBaseURI());
3927 // Dispatch as a low priority runnable.
3928 if (NS_FAILED(
3929 DispatchToMainThreadForMessaging(extWorkerRunnable.forget()))) {
3930 NS_WARNING(
3931 "Failed to dispatch runnable to notify extensions worker "
3932 "destroyed");
3936 // Note, this uses the lower priority DispatchToMainThreadForMessaging for
3937 // dispatching TopLevelWorkerFinishedRunnable to the main thread so that
3938 // other relevant runnables are guaranteed to run before it.
3939 RefPtr<TopLevelWorkerFinishedRunnable> runnable =
3940 new TopLevelWorkerFinishedRunnable(this);
3941 if (NS_FAILED(DispatchToMainThreadForMessaging(runnable.forget()))) {
3942 NS_WARNING("Failed to dispatch runnable!");
3945 // NOTE: Calling any WorkerPrivate methods (or accessing member data) after
3946 // this point is unsafe (the TopLevelWorkerFinishedRunnable just dispatched
3947 // may be able to call ClearSelfAndParentEventTargetRef on this
3948 // WorkerPrivate instance and by the time we get here the WorkerPrivate
3949 // instance destructor may have been already called).
3953 bool WorkerPrivate::CollectRuntimeStats(
3954 JS::RuntimeStats* aRtStats, bool aAnonymize) MOZ_NO_THREAD_SAFETY_ANALYSIS {
3955 // We don't have a lock to access mJSContext, but it's safe to access on this
3956 // thread.
3957 AssertIsOnWorkerThread();
3958 NS_ASSERTION(aRtStats, "Null RuntimeStats!");
3959 // We don't really own it, but it's safe to access on this thread
3960 NS_ASSERTION(mJSContext, "This must never be null!");
3962 return JS::CollectRuntimeStats(mJSContext, aRtStats, nullptr, aAnonymize);
3965 void WorkerPrivate::EnableMemoryReporter() {
3966 auto data = mWorkerThreadAccessible.Access();
3967 MOZ_ASSERT(!data->mMemoryReporter);
3969 // No need to lock here since the main thread can't race until we've
3970 // successfully registered the reporter.
3971 data->mMemoryReporter = new MemoryReporter(this);
3973 if (NS_FAILED(RegisterWeakAsyncMemoryReporter(data->mMemoryReporter))) {
3974 NS_WARNING("Failed to register memory reporter!");
3975 // No need to lock here since a failed registration means our memory
3976 // reporter can't start running. Just clean up.
3977 data->mMemoryReporter = nullptr;
3981 void WorkerPrivate::DisableMemoryReporter() {
3982 auto data = mWorkerThreadAccessible.Access();
3984 RefPtr<MemoryReporter> memoryReporter;
3986 // Mutex protectes MemoryReporter::mWorkerPrivate which is cleared by
3987 // MemoryReporter::Disable() below.
3988 MutexAutoLock lock(mMutex);
3990 // There is nothing to do here if the memory reporter was never successfully
3991 // registered.
3992 if (!data->mMemoryReporter) {
3993 return;
3996 // We don't need this set any longer. Swap it out so that we can unregister
3997 // below.
3998 data->mMemoryReporter.swap(memoryReporter);
4000 // Next disable the memory reporter so that the main thread stops trying to
4001 // signal us.
4002 memoryReporter->Disable();
4005 // Finally unregister the memory reporter.
4006 if (NS_FAILED(UnregisterWeakMemoryReporter(memoryReporter))) {
4007 NS_WARNING("Failed to unregister memory reporter!");
4011 void WorkerPrivate::WaitForWorkerEvents() {
4012 AUTO_PROFILER_LABEL("WorkerPrivate::WaitForWorkerEvents", IDLE);
4014 AssertIsOnWorkerThread();
4015 mMutex.AssertCurrentThreadOwns();
4017 // Wait for a worker event.
4018 mCondVar.Wait();
4021 WorkerPrivate::ProcessAllControlRunnablesResult
4022 WorkerPrivate::ProcessAllControlRunnablesLocked() {
4023 AssertIsOnWorkerThread();
4024 mMutex.AssertCurrentThreadOwns();
4026 AutoYieldJSThreadExecution yield;
4028 auto result = ProcessAllControlRunnablesResult::Nothing;
4030 for (;;) {
4031 WorkerControlRunnable* event;
4032 if (!mControlQueue.Pop(event)) {
4033 break;
4036 MutexAutoUnlock unlock(mMutex);
4039 MOZ_ASSERT(event);
4040 AUTO_PROFILE_FOLLOWING_RUNNABLE(event);
4041 if (NS_FAILED(static_cast<nsIRunnable*>(event)->Run())) {
4042 result = ProcessAllControlRunnablesResult::Abort;
4046 if (result == ProcessAllControlRunnablesResult::Nothing) {
4047 // We ran at least one thing.
4048 result = ProcessAllControlRunnablesResult::MayContinue;
4050 event->Release();
4053 return result;
4056 void WorkerPrivate::ShutdownModuleLoader() {
4057 AssertIsOnWorkerThread();
4059 WorkerGlobalScope* globalScope = GlobalScope();
4060 if (globalScope) {
4061 if (globalScope->GetModuleLoader(nullptr)) {
4062 globalScope->GetModuleLoader(nullptr)->Shutdown();
4065 WorkerDebuggerGlobalScope* debugGlobalScope = DebuggerGlobalScope();
4066 if (debugGlobalScope) {
4067 if (debugGlobalScope->GetModuleLoader(nullptr)) {
4068 debugGlobalScope->GetModuleLoader(nullptr)->Shutdown();
4073 void WorkerPrivate::ClearPreStartRunnables() {
4074 nsTArray<RefPtr<WorkerRunnable>> prestart;
4076 MutexAutoLock lock(mMutex);
4077 mPreStartRunnables.SwapElements(prestart);
4079 for (uint32_t count = prestart.Length(), index = 0; index < count; index++) {
4080 LOG(WorkerLog(), ("WorkerPrivate::ClearPreStartRunnable [%p]", this));
4081 RefPtr<WorkerRunnable> runnable = std::move(prestart[index]);
4082 runnable->Cancel();
4086 void WorkerPrivate::ClearDebuggerEventQueue() {
4087 while (!mDebuggerQueue.IsEmpty()) {
4088 WorkerRunnable* runnable = nullptr;
4089 mDebuggerQueue.Pop(runnable);
4090 // It should be ok to simply release the runnable, without running it.
4091 runnable->Release();
4095 bool WorkerPrivate::FreezeInternal() {
4096 auto data = mWorkerThreadAccessible.Access();
4097 NS_ASSERTION(!data->mFrozen, "Already frozen!");
4099 AutoYieldJSThreadExecution yield;
4101 // The worker can freeze even if it failed to run (and doesn't have a global).
4102 if (data->mScope) {
4103 data->mScope->MutableClientSourceRef().Freeze();
4106 data->mFrozen = true;
4108 for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
4109 data->mChildWorkers[index]->Freeze(nullptr);
4112 return true;
4115 bool WorkerPrivate::ThawInternal() {
4116 auto data = mWorkerThreadAccessible.Access();
4117 NS_ASSERTION(data->mFrozen, "Not yet frozen!");
4119 for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
4120 data->mChildWorkers[index]->Thaw(nullptr);
4123 data->mFrozen = false;
4125 // The worker can thaw even if it failed to run (and doesn't have a global).
4126 if (data->mScope) {
4127 data->mScope->MutableClientSourceRef().Thaw();
4130 return true;
4133 void WorkerPrivate::PropagateStorageAccessPermissionGrantedInternal() {
4134 auto data = mWorkerThreadAccessible.Access();
4136 mLoadInfo.mUseRegularPrincipal = true;
4137 mLoadInfo.mUsingStorageAccess = true;
4139 WorkerGlobalScope* globalScope = GlobalScope();
4140 if (globalScope) {
4141 globalScope->StorageAccessPermissionGranted();
4144 for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
4145 data->mChildWorkers[index]->PropagateStorageAccessPermissionGranted();
4149 void WorkerPrivate::TraverseTimeouts(nsCycleCollectionTraversalCallback& cb) {
4150 auto data = mWorkerThreadAccessible.Access();
4151 for (uint32_t i = 0; i < data->mTimeouts.Length(); ++i) {
4152 // TODO(erahm): No idea what's going on here.
4153 TimeoutInfo* tmp = data->mTimeouts[i].get();
4154 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHandler)
4158 void WorkerPrivate::UnlinkTimeouts() {
4159 auto data = mWorkerThreadAccessible.Access();
4160 data->mTimeouts.Clear();
4163 bool WorkerPrivate::AddChildWorker(WorkerPrivate& aChildWorker) {
4164 auto data = mWorkerThreadAccessible.Access();
4166 #ifdef DEBUG
4168 WorkerStatus currentStatus;
4170 MutexAutoLock lock(mMutex);
4171 currentStatus = mStatus;
4174 MOZ_ASSERT(currentStatus == Running);
4176 #endif
4178 NS_ASSERTION(!data->mChildWorkers.Contains(&aChildWorker),
4179 "Already know about this one!");
4180 data->mChildWorkers.AppendElement(&aChildWorker);
4182 if (data->mChildWorkers.Length() == 1) {
4183 UpdateCCFlag(CCFlag::IneligibleForChildWorker);
4186 return true;
4189 void WorkerPrivate::RemoveChildWorker(WorkerPrivate& aChildWorker) {
4190 auto data = mWorkerThreadAccessible.Access();
4192 NS_ASSERTION(data->mChildWorkers.Contains(&aChildWorker),
4193 "Didn't know about this one!");
4194 data->mChildWorkers.RemoveElement(&aChildWorker);
4196 if (data->mChildWorkers.IsEmpty()) {
4197 UpdateCCFlag(CCFlag::EligibleForChildWorker);
4201 bool WorkerPrivate::AddWorkerRef(WorkerRef* aWorkerRef,
4202 WorkerStatus aFailStatus) {
4203 MOZ_ASSERT(aWorkerRef);
4204 auto data = mWorkerThreadAccessible.Access();
4207 MutexAutoLock lock(mMutex);
4209 LOG(WorkerLog(),
4210 ("WorkerPrivate::AddWorkerRef [%p] mStatus: %u, aFailStatus: (%u)",
4211 this, static_cast<uint8_t>(mStatus),
4212 static_cast<uint8_t>(aFailStatus)));
4214 if (mStatus >= aFailStatus) {
4215 return false;
4218 // We shouldn't create strong references to workers before their main loop
4219 // begins running. Strong references must be disposed of on the worker
4220 // thread, so strong references from other threads use a control runnable
4221 // for that purpose. If the worker fails to reach the main loop stage then
4222 // no control runnables get run and it would be impossible to get rid of the
4223 // reference properly.
4224 MOZ_DIAGNOSTIC_ASSERT_IF(aWorkerRef->IsPreventingShutdown(),
4225 mStatus >= WorkerStatus::Running);
4228 MOZ_ASSERT(!data->mWorkerRefs.Contains(aWorkerRef),
4229 "Already know about this one!");
4231 if (aWorkerRef->IsPreventingShutdown()) {
4232 data->mNumWorkerRefsPreventingShutdownStart += 1;
4233 if (data->mNumWorkerRefsPreventingShutdownStart == 1) {
4234 UpdateCCFlag(CCFlag::IneligibleForWorkerRef);
4238 data->mWorkerRefs.AppendElement(aWorkerRef);
4239 return true;
4242 void WorkerPrivate::RemoveWorkerRef(WorkerRef* aWorkerRef) {
4243 MOZ_ASSERT(aWorkerRef);
4244 LOG(WorkerLog(),
4245 ("WorkerPrivate::RemoveWorkerRef [%p] aWorkerRef: %p", this, aWorkerRef));
4246 auto data = mWorkerThreadAccessible.Access();
4248 MOZ_ASSERT(data->mWorkerRefs.Contains(aWorkerRef),
4249 "Didn't know about this one!");
4250 data->mWorkerRefs.RemoveElement(aWorkerRef);
4252 if (aWorkerRef->IsPreventingShutdown()) {
4253 data->mNumWorkerRefsPreventingShutdownStart -= 1;
4254 if (!data->mNumWorkerRefsPreventingShutdownStart) {
4255 UpdateCCFlag(CCFlag::EligibleForWorkerRef);
4260 void WorkerPrivate::NotifyWorkerRefs(WorkerStatus aStatus) {
4261 auto data = mWorkerThreadAccessible.Access();
4263 NS_ASSERTION(aStatus > Closing, "Bad status!");
4265 LOG(WorkerLog(), ("WorkerPrivate::NotifyWorkerRefs [%p] aStatus: %u", this,
4266 static_cast<uint8_t>(aStatus)));
4268 for (auto* workerRef : data->mWorkerRefs.ForwardRange()) {
4269 LOG(WorkerLog(), ("WorkerPrivate::NotifyWorkerRefs [%p] WorkerRefs(%s %p)",
4270 this, workerRef->mName, workerRef));
4271 workerRef->Notify();
4274 AutoTArray<CheckedUnsafePtr<WorkerPrivate>, 10> children;
4275 children.AppendElements(data->mChildWorkers);
4277 for (uint32_t index = 0; index < children.Length(); index++) {
4278 if (!children[index]->Notify(aStatus)) {
4279 NS_WARNING("Failed to notify child worker!");
4284 nsresult WorkerPrivate::RegisterShutdownTask(nsITargetShutdownTask* aTask) {
4285 NS_ENSURE_ARG(aTask);
4287 MutexAutoLock lock(mMutex);
4289 // If we've already started running shutdown tasks, don't allow registering
4290 // new ones.
4291 if (mShutdownTasksRun) {
4292 return NS_ERROR_UNEXPECTED;
4295 MOZ_ASSERT(!mShutdownTasks.Contains(aTask));
4296 mShutdownTasks.AppendElement(aTask);
4297 return NS_OK;
4300 nsresult WorkerPrivate::UnregisterShutdownTask(nsITargetShutdownTask* aTask) {
4301 NS_ENSURE_ARG(aTask);
4303 MutexAutoLock lock(mMutex);
4305 // We've already started running shutdown tasks, so can't unregister them
4306 // anymore.
4307 if (mShutdownTasksRun) {
4308 return NS_ERROR_UNEXPECTED;
4311 return mShutdownTasks.RemoveElement(aTask) ? NS_OK : NS_ERROR_UNEXPECTED;
4314 void WorkerPrivate::RunShutdownTasks() {
4315 nsTArray<nsCOMPtr<nsITargetShutdownTask>> shutdownTasks;
4318 MutexAutoLock lock(mMutex);
4319 shutdownTasks = std::move(mShutdownTasks);
4320 mShutdownTasks.Clear();
4321 mShutdownTasksRun = true;
4324 for (auto& task : shutdownTasks) {
4325 task->TargetShutdown();
4327 mWorkerHybridEventTarget->ForgetWorkerPrivate(this);
4330 void WorkerPrivate::AdjustNonblockingCCBackgroundActorCount(int32_t aCount) {
4331 AssertIsOnWorkerThread();
4332 auto data = mWorkerThreadAccessible.Access();
4333 LOGV(("WorkerPrivate::AdjustNonblockingCCBackgroundActors [%p] (%d/%u)", this,
4334 aCount, data->mNonblockingCCBackgroundActorCount));
4336 #ifdef DEBUG
4337 if (aCount < 0) {
4338 MOZ_ASSERT(data->mNonblockingCCBackgroundActorCount >=
4339 (uint32_t)abs(aCount));
4341 #endif
4343 data->mNonblockingCCBackgroundActorCount += aCount;
4346 void WorkerPrivate::UpdateCCFlag(const CCFlag aFlag) {
4347 AssertIsOnWorkerThread();
4349 auto data = mWorkerThreadAccessible.Access();
4351 #ifdef DEBUG
4352 switch (aFlag) {
4353 case CCFlag::EligibleForWorkerRef: {
4354 MOZ_ASSERT(!data->mNumWorkerRefsPreventingShutdownStart);
4355 break;
4357 case CCFlag::IneligibleForWorkerRef: {
4358 MOZ_ASSERT(data->mNumWorkerRefsPreventingShutdownStart);
4359 break;
4361 case CCFlag::EligibleForChildWorker: {
4362 MOZ_ASSERT(data->mChildWorkers.IsEmpty());
4363 break;
4365 case CCFlag::IneligibleForChildWorker: {
4366 MOZ_ASSERT(!data->mChildWorkers.IsEmpty());
4367 break;
4369 case CCFlag::EligibleForTimeout: {
4370 MOZ_ASSERT(data->mTimeouts.IsEmpty());
4371 break;
4373 case CCFlag::IneligibleForTimeout: {
4374 MOZ_ASSERT(!data->mTimeouts.IsEmpty());
4375 break;
4377 case CCFlag::CheckBackgroundActors: {
4378 break;
4381 #endif
4384 MutexAutoLock lock(mMutex);
4385 if (mStatus > Canceling) {
4386 mCCFlagSaysEligible = true;
4387 return;
4390 auto HasBackgroundActors = [nonblockingActorCount =
4391 data->mNonblockingCCBackgroundActorCount]() {
4392 RefPtr<PBackgroundChild> backgroundChild =
4393 BackgroundChild::GetForCurrentThread();
4394 MOZ_ASSERT(backgroundChild);
4395 auto totalCount = backgroundChild->AllManagedActorsCount();
4396 LOGV(("WorkerPrivate::UpdateCCFlag HasBackgroundActors: %s(%u/%u)",
4397 totalCount > nonblockingActorCount ? "true" : "false", totalCount,
4398 nonblockingActorCount));
4400 return totalCount > nonblockingActorCount;
4403 bool eligibleForCC = data->mChildWorkers.IsEmpty() &&
4404 data->mTimeouts.IsEmpty() &&
4405 !data->mNumWorkerRefsPreventingShutdownStart;
4407 // Only checking BackgroundActors when no strong WorkerRef, ChildWorker, and
4408 // Timeout since the checking is expensive.
4409 if (eligibleForCC) {
4410 eligibleForCC = !HasBackgroundActors();
4414 MutexAutoLock lock(mMutex);
4415 mCCFlagSaysEligible = eligibleForCC;
4419 bool WorkerPrivate::IsEligibleForCC() {
4420 LOGV(("WorkerPrivate::IsEligibleForCC [%p]", this));
4421 MutexAutoLock lock(mMutex);
4422 if (mStatus > Canceling) {
4423 return true;
4426 bool hasShutdownTasks = !mShutdownTasks.IsEmpty();
4427 bool hasPendingEvents = false;
4428 if (mThread) {
4429 hasPendingEvents =
4430 NS_SUCCEEDED(mThread->HasPendingEvents(&hasPendingEvents)) &&
4431 hasPendingEvents;
4434 LOGV(("mMainThreadEventTarget: %s",
4435 mMainThreadEventTarget->IsEmpty() ? "empty" : "non-empty"));
4436 LOGV(("mMainThreadEventTargetForMessaging: %s",
4437 mMainThreadEventTargetForMessaging->IsEmpty() ? "empty" : "non-empty"));
4438 LOGV(("mMainThreadDebuggerEventTarget: %s",
4439 mMainThreadDebuggeeEventTarget->IsEmpty() ? "empty" : "non-empty"));
4440 LOGV(("mCCFlagSaysEligible: %s", mCCFlagSaysEligible ? "true" : "false"));
4441 LOGV(("hasShutdownTasks: %s", hasShutdownTasks ? "true" : "false"));
4442 LOGV(("hasPendingEvents: %s", hasPendingEvents ? "true" : "false"));
4444 return mMainThreadEventTarget->IsEmpty() &&
4445 mMainThreadEventTargetForMessaging->IsEmpty() &&
4446 mMainThreadDebuggeeEventTarget->IsEmpty() && mCCFlagSaysEligible &&
4447 !hasShutdownTasks && !hasPendingEvents && mWorkerLoopIsIdle;
4450 void WorkerPrivate::CancelAllTimeouts() {
4451 auto data = mWorkerThreadAccessible.Access();
4453 LOG(TimeoutsLog(), ("Worker %p CancelAllTimeouts.\n", this));
4455 if (data->mTimerRunning) {
4456 NS_ASSERTION(data->mTimer && data->mTimerRunnable, "Huh?!");
4457 NS_ASSERTION(!data->mTimeouts.IsEmpty(), "Huh?!");
4459 if (NS_FAILED(data->mTimer->Cancel())) {
4460 NS_WARNING("Failed to cancel timer!");
4463 for (uint32_t index = 0; index < data->mTimeouts.Length(); index++) {
4464 data->mTimeouts[index]->mCanceled = true;
4467 // If mRunningExpiredTimeouts, then the fact that they are all canceled now
4468 // means that the currently executing RunExpiredTimeouts will deal with
4469 // them. Otherwise, we need to clean them up ourselves.
4470 if (!data->mRunningExpiredTimeouts) {
4471 data->mTimeouts.Clear();
4472 UpdateCCFlag(CCFlag::EligibleForTimeout);
4475 // Set mTimerRunning false even if mRunningExpiredTimeouts is true, so that
4476 // if we get reentered under this same RunExpiredTimeouts call we don't
4477 // assert above that !mTimeouts().IsEmpty(), because that's clearly false
4478 // now.
4479 data->mTimerRunning = false;
4481 #ifdef DEBUG
4482 else if (!data->mRunningExpiredTimeouts) {
4483 NS_ASSERTION(data->mTimeouts.IsEmpty(), "Huh?!");
4485 #endif
4487 data->mTimer = nullptr;
4488 data->mTimerRunnable = nullptr;
4491 already_AddRefed<nsISerialEventTarget> WorkerPrivate::CreateNewSyncLoop(
4492 WorkerStatus aFailStatus) {
4493 AssertIsOnWorkerThread();
4494 MOZ_ASSERT(
4495 aFailStatus >= Canceling,
4496 "Sync loops can be created when the worker is in Running/Closing state!");
4498 LOG(WorkerLog(), ("WorkerPrivate::CreateNewSyncLoop [%p] failstatus: %u",
4499 this, static_cast<uint8_t>(aFailStatus)));
4501 ThreadEventQueue* queue = nullptr;
4503 MutexAutoLock lock(mMutex);
4505 if (mStatus >= aFailStatus) {
4506 return nullptr;
4508 queue = static_cast<ThreadEventQueue*>(mThread->EventQueue());
4511 nsCOMPtr<nsISerialEventTarget> nestedEventTarget = queue->PushEventQueue();
4512 MOZ_ASSERT(nestedEventTarget);
4514 RefPtr<EventTarget> workerEventTarget =
4515 new EventTarget(this, nestedEventTarget);
4518 // Modifications must be protected by mMutex in DEBUG builds, see comment
4519 // about mSyncLoopStack in WorkerPrivate.h.
4520 #ifdef DEBUG
4521 MutexAutoLock lock(mMutex);
4522 #endif
4524 mSyncLoopStack.AppendElement(new SyncLoopInfo(workerEventTarget));
4527 return workerEventTarget.forget();
4530 nsresult WorkerPrivate::RunCurrentSyncLoop() {
4531 AssertIsOnWorkerThread();
4532 LOG(WorkerLog(), ("WorkerPrivate::RunCurrentSyncLoop [%p]", this));
4533 RefPtr<WorkerThread> thread;
4534 JSContext* cx = GetJSContext();
4535 MOZ_ASSERT(cx);
4536 // mThread is set before we enter, and is never changed during
4537 // RunCurrentSyncLoop.
4539 MutexAutoLock lock(mMutex);
4540 // Copy to local so we don't trigger mutex analysis lower down
4541 // mThread is set before we enter, and is never changed during
4542 // RunCurrentSyncLoop copy to local so we don't trigger mutex analysis
4543 thread = mThread;
4546 AutoPushEventLoopGlobal eventLoopGlobal(this, cx);
4548 // This should not change between now and the time we finish running this sync
4549 // loop.
4550 uint32_t currentLoopIndex = mSyncLoopStack.Length() - 1;
4552 SyncLoopInfo* loopInfo = mSyncLoopStack[currentLoopIndex].get();
4554 AutoYieldJSThreadExecution yield;
4556 MOZ_ASSERT(loopInfo);
4557 MOZ_ASSERT(!loopInfo->mHasRun);
4558 MOZ_ASSERT(!loopInfo->mCompleted);
4560 #ifdef DEBUG
4561 loopInfo->mHasRun = true;
4562 #endif
4565 while (!loopInfo->mCompleted) {
4566 bool normalRunnablesPending = false;
4568 // Don't block with the periodic GC timer running.
4569 if (!NS_HasPendingEvents(thread)) {
4570 SetGCTimerMode(IdleTimer);
4573 // Wait for something to do.
4575 MutexAutoLock lock(mMutex);
4577 for (;;) {
4578 while (mControlQueue.IsEmpty() && !normalRunnablesPending &&
4579 !(normalRunnablesPending = NS_HasPendingEvents(thread))) {
4580 WaitForWorkerEvents();
4583 auto result = ProcessAllControlRunnablesLocked();
4584 if (result != ProcessAllControlRunnablesResult::Nothing) {
4585 // The state of the world may have changed. Recheck it if we need to
4586 // continue.
4587 normalRunnablesPending =
4588 result == ProcessAllControlRunnablesResult::MayContinue &&
4589 NS_HasPendingEvents(thread);
4591 // NB: If we processed a NotifyRunnable, we might have run
4592 // non-control runnables, one of which may have shut down the
4593 // sync loop.
4594 if (loopInfo->mCompleted) {
4595 break;
4599 // If we *didn't* run any control runnables, this should be unchanged.
4600 MOZ_ASSERT(!loopInfo->mCompleted);
4602 if (normalRunnablesPending) {
4603 break;
4608 if (normalRunnablesPending) {
4609 // Make sure the periodic timer is running before we continue.
4610 SetGCTimerMode(PeriodicTimer);
4612 MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(thread, false));
4614 // Now *might* be a good time to GC. Let the JS engine make the
4615 // decision.
4616 if (GetCurrentEventLoopGlobal()) {
4617 // If GetCurrentEventLoopGlobal() is non-null, our JSContext is in a
4618 // Realm, so it's safe to try to GC.
4619 MOZ_ASSERT(JS::CurrentGlobalOrNull(cx));
4620 JS_MaybeGC(cx);
4626 // Make sure that the stack didn't change underneath us.
4627 MOZ_ASSERT(mSyncLoopStack[currentLoopIndex].get() == loopInfo);
4629 return DestroySyncLoop(currentLoopIndex);
4632 nsresult WorkerPrivate::DestroySyncLoop(uint32_t aLoopIndex) {
4633 MOZ_ASSERT(!mSyncLoopStack.IsEmpty());
4634 MOZ_ASSERT(mSyncLoopStack.Length() - 1 == aLoopIndex);
4636 LOG(WorkerLog(),
4637 ("WorkerPrivate::DestroySyncLoop [%p] aLoopIndex: %u", this, aLoopIndex));
4639 AutoYieldJSThreadExecution yield;
4641 // We're about to delete the loop, stash its event target and result.
4642 const auto& loopInfo = mSyncLoopStack[aLoopIndex];
4644 nsresult result = loopInfo->mResult;
4647 RefPtr<nsIEventTarget> nestedEventTarget(
4648 loopInfo->mEventTarget->GetNestedEventTarget());
4649 MOZ_ASSERT(nestedEventTarget);
4651 loopInfo->mEventTarget->Shutdown();
4654 MutexAutoLock lock(mMutex);
4655 static_cast<ThreadEventQueue*>(mThread->EventQueue())
4656 ->PopEventQueue(nestedEventTarget);
4660 // Are we making a 1 -> 0 transition here?
4661 if (mSyncLoopStack.Length() == 1) {
4662 if ((mPostSyncLoopOperations & eDispatchCancelingRunnable)) {
4663 LOG(WorkerLog(),
4664 ("WorkerPrivate::DestroySyncLoop [%p] Dispatching CancelingRunnables",
4665 this));
4666 DispatchCancelingRunnable();
4669 mPostSyncLoopOperations = 0;
4673 // Modifications must be protected by mMutex in DEBUG builds, see comment
4674 // about mSyncLoopStack in WorkerPrivate.h.
4675 #ifdef DEBUG
4676 MutexAutoLock lock(mMutex);
4677 #endif
4679 // This will delete |loopInfo|!
4680 mSyncLoopStack.RemoveElementAt(aLoopIndex);
4683 return result;
4686 void WorkerPrivate::DispatchCancelingRunnable() {
4687 // Here we use a normal runnable to know when the current JS chunk of code
4688 // is finished. We cannot use a WorkerRunnable because they are not
4689 // accepted any more by the worker, and we do not want to use a
4690 // WorkerControlRunnable because they are immediately executed.
4692 LOG(WorkerLog(), ("WorkerPrivate::DispatchCancelingRunnable [%p]", this));
4693 RefPtr<CancelingRunnable> r = new CancelingRunnable();
4695 MutexAutoLock lock(mMutex);
4696 mThread->nsThread::Dispatch(r.forget(), NS_DISPATCH_NORMAL);
4699 // At the same time, we want to be sure that we interrupt infinite loops.
4700 // The following runnable starts a timer that cancel the worker, from the
4701 // parent thread, after CANCELING_TIMEOUT millseconds.
4702 LOG(WorkerLog(), ("WorkerPrivate::DispatchCancelingRunnable [%p] Setup a "
4703 "timeout canceling",
4704 this));
4705 RefPtr<CancelingWithTimeoutOnParentRunnable> rr =
4706 new CancelingWithTimeoutOnParentRunnable(this);
4707 rr->Dispatch();
4710 void WorkerPrivate::ReportUseCounters() {
4711 AssertIsOnWorkerThread();
4713 if (mReportedUseCounters) {
4714 return;
4716 mReportedUseCounters = true;
4718 if (IsChromeWorker()) {
4719 return;
4722 const size_t kind = Kind();
4723 switch (kind) {
4724 case WorkerKindDedicated:
4725 glean::use_counter::dedicated_workers_destroyed.Add();
4726 break;
4727 case WorkerKindShared:
4728 glean::use_counter::shared_workers_destroyed.Add();
4729 break;
4730 case WorkerKindService:
4731 glean::use_counter::service_workers_destroyed.Add();
4732 break;
4733 default:
4734 MOZ_ASSERT(false, "Unknown worker kind");
4735 return;
4738 Maybe<nsCString> workerPathForLogging;
4739 const bool dumpCounters = StaticPrefs::dom_use_counters_dump_worker();
4740 if (dumpCounters) {
4741 nsAutoCString path(Domain());
4742 path.AppendLiteral("(");
4743 NS_ConvertUTF16toUTF8 script(ScriptURL());
4744 path.Append(script);
4745 path.AppendPrintf(", 0x%p)", this);
4746 workerPathForLogging.emplace(std::move(path));
4749 const size_t count = static_cast<size_t>(UseCounterWorker::Count);
4751 const auto workerKind = Kind();
4752 for (size_t c = 0; c < count; ++c) {
4753 if (!GetUseCounter(static_cast<UseCounterWorker>(c))) {
4754 continue;
4756 const char* metricName =
4757 IncrementWorkerUseCounter(static_cast<UseCounterWorker>(c), workerKind);
4758 if (dumpCounters) {
4759 printf_stderr("USE_COUNTER_WORKER: %s - %s\n", metricName,
4760 workerPathForLogging->get());
4765 void WorkerPrivate::StopSyncLoop(nsIEventTarget* aSyncLoopTarget,
4766 nsresult aResult) {
4767 AssertValidSyncLoop(aSyncLoopTarget);
4769 if (!MaybeStopSyncLoop(aSyncLoopTarget, aResult)) {
4770 // TODO: I wonder if we should really ever crash here given the assert.
4771 MOZ_CRASH("Unknown sync loop!");
4775 bool WorkerPrivate::MaybeStopSyncLoop(nsIEventTarget* aSyncLoopTarget,
4776 nsresult aResult) {
4777 AssertIsOnWorkerThread();
4779 for (uint32_t index = mSyncLoopStack.Length(); index > 0; index--) {
4780 const auto& loopInfo = mSyncLoopStack[index - 1];
4781 MOZ_ASSERT(loopInfo);
4782 MOZ_ASSERT(loopInfo->mEventTarget);
4784 if (loopInfo->mEventTarget == aSyncLoopTarget) {
4785 // Can't assert |loop->mHasRun| here because dispatch failures can cause
4786 // us to bail out early.
4787 MOZ_ASSERT(!loopInfo->mCompleted);
4789 loopInfo->mResult = aResult;
4790 loopInfo->mCompleted = true;
4792 loopInfo->mEventTarget->Disable();
4794 return true;
4797 MOZ_ASSERT(!SameCOMIdentity(loopInfo->mEventTarget, aSyncLoopTarget));
4800 return false;
4803 #ifdef DEBUG
4804 void WorkerPrivate::AssertValidSyncLoop(nsIEventTarget* aSyncLoopTarget) {
4805 MOZ_ASSERT(aSyncLoopTarget);
4807 EventTarget* workerTarget;
4808 nsresult rv = aSyncLoopTarget->QueryInterface(
4809 kDEBUGWorkerEventTargetIID, reinterpret_cast<void**>(&workerTarget));
4810 MOZ_ASSERT(NS_SUCCEEDED(rv));
4811 MOZ_ASSERT(workerTarget);
4813 bool valid = false;
4816 MutexAutoLock lock(mMutex);
4818 for (uint32_t index = 0; index < mSyncLoopStack.Length(); index++) {
4819 const auto& loopInfo = mSyncLoopStack[index];
4820 MOZ_ASSERT(loopInfo);
4821 MOZ_ASSERT(loopInfo->mEventTarget);
4823 if (loopInfo->mEventTarget == aSyncLoopTarget) {
4824 valid = true;
4825 break;
4828 MOZ_ASSERT(!SameCOMIdentity(loopInfo->mEventTarget, aSyncLoopTarget));
4832 MOZ_ASSERT(valid);
4834 #endif
4836 void WorkerPrivate::PostMessageToParent(
4837 JSContext* aCx, JS::Handle<JS::Value> aMessage,
4838 const Sequence<JSObject*>& aTransferable, ErrorResult& aRv) {
4839 LOG(WorkerLog(), ("WorkerPrivate::PostMessageToParent [%p]", this));
4840 AssertIsOnWorkerThread();
4841 MOZ_DIAGNOSTIC_ASSERT(IsDedicatedWorker());
4843 JS::Rooted<JS::Value> transferable(aCx, JS::UndefinedValue());
4845 aRv = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, aTransferable,
4846 &transferable);
4847 if (NS_WARN_IF(aRv.Failed())) {
4848 return;
4851 RefPtr<MessageEventRunnable> runnable =
4852 new MessageEventRunnable(this, WorkerRunnable::ParentThread);
4854 JS::CloneDataPolicy clonePolicy;
4856 // Parent and dedicated workers are always part of the same cluster.
4857 clonePolicy.allowIntraClusterClonableSharedObjects();
4859 if (IsSharedMemoryAllowed()) {
4860 clonePolicy.allowSharedMemoryObjects();
4863 runnable->Write(aCx, aMessage, transferable, clonePolicy, aRv);
4865 if (NS_WARN_IF(aRv.Failed())) {
4866 return;
4869 if (!runnable->Dispatch()) {
4870 aRv = NS_ERROR_FAILURE;
4874 void WorkerPrivate::EnterDebuggerEventLoop() {
4875 auto data = mWorkerThreadAccessible.Access();
4877 JSContext* cx = GetJSContext();
4878 MOZ_ASSERT(cx);
4880 AutoPushEventLoopGlobal eventLoopGlobal(this, cx);
4881 AutoYieldJSThreadExecution yield;
4883 CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get();
4885 uint32_t currentEventLoopLevel = ++data->mDebuggerEventLoopLevel;
4887 while (currentEventLoopLevel <= data->mDebuggerEventLoopLevel) {
4888 bool debuggerRunnablesPending = false;
4891 MutexAutoLock lock(mMutex);
4893 debuggerRunnablesPending = !mDebuggerQueue.IsEmpty();
4896 // Don't block with the periodic GC timer running.
4897 if (!debuggerRunnablesPending) {
4898 SetGCTimerMode(IdleTimer);
4901 // Wait for something to do
4903 MutexAutoLock lock(mMutex);
4905 std::deque<RefPtr<MicroTaskRunnable>>& debuggerMtQueue =
4906 ccjscx->GetDebuggerMicroTaskQueue();
4907 while (mControlQueue.IsEmpty() &&
4908 !(debuggerRunnablesPending = !mDebuggerQueue.IsEmpty()) &&
4909 debuggerMtQueue.empty()) {
4910 WaitForWorkerEvents();
4913 ProcessAllControlRunnablesLocked();
4915 // XXXkhuey should we abort JS on the stack here if we got Abort above?
4917 ccjscx->PerformDebuggerMicroTaskCheckpoint();
4918 if (debuggerRunnablesPending) {
4919 // Start the periodic GC timer if it is not already running.
4920 SetGCTimerMode(PeriodicTimer);
4922 WorkerRunnable* runnable = nullptr;
4925 MutexAutoLock lock(mMutex);
4927 mDebuggerQueue.Pop(runnable);
4930 MOZ_ASSERT(runnable);
4931 static_cast<nsIRunnable*>(runnable)->Run();
4932 runnable->Release();
4934 ccjscx->PerformDebuggerMicroTaskCheckpoint();
4936 // Now *might* be a good time to GC. Let the JS engine make the decision.
4937 if (GetCurrentEventLoopGlobal()) {
4938 // If GetCurrentEventLoopGlobal() is non-null, our JSContext is in a
4939 // Realm, so it's safe to try to GC.
4940 MOZ_ASSERT(JS::CurrentGlobalOrNull(cx));
4941 JS_MaybeGC(cx);
4947 void WorkerPrivate::LeaveDebuggerEventLoop() {
4948 auto data = mWorkerThreadAccessible.Access();
4950 // TODO: Why lock the mutex if we're accessing data accessible to one thread
4951 // only?
4952 MutexAutoLock lock(mMutex);
4954 if (data->mDebuggerEventLoopLevel > 0) {
4955 --data->mDebuggerEventLoopLevel;
4959 void WorkerPrivate::PostMessageToDebugger(const nsAString& aMessage) {
4960 mDebugger->PostMessageToDebugger(aMessage);
4963 void WorkerPrivate::SetDebuggerImmediate(dom::Function& aHandler,
4964 ErrorResult& aRv) {
4965 AssertIsOnWorkerThread();
4967 RefPtr<DebuggerImmediateRunnable> runnable =
4968 new DebuggerImmediateRunnable(this, aHandler);
4969 if (!runnable->Dispatch()) {
4970 aRv.Throw(NS_ERROR_FAILURE);
4974 void WorkerPrivate::ReportErrorToDebugger(const nsAString& aFilename,
4975 uint32_t aLineno,
4976 const nsAString& aMessage) {
4977 mDebugger->ReportErrorToDebugger(aFilename, aLineno, aMessage);
4980 bool WorkerPrivate::NotifyInternal(WorkerStatus aStatus) {
4981 auto data = mWorkerThreadAccessible.Access();
4983 // Yield execution while notifying out-of-module WorkerRefs and cancelling
4984 // runnables.
4985 AutoYieldJSThreadExecution yield;
4987 NS_ASSERTION(aStatus > Running && aStatus < Dead, "Bad status!");
4989 RefPtr<EventTarget> eventTarget;
4991 // Save the old status and set the new status.
4993 MutexAutoLock lock(mMutex);
4995 LOG(WorkerLog(),
4996 ("WorkerPrivate::NotifyInternal [%p] mStatus: %u, aStatus: %u", this,
4997 static_cast<uint8_t>(mStatus), static_cast<uint8_t>(aStatus)));
4999 if (mStatus >= aStatus) {
5000 return true;
5003 MOZ_ASSERT_IF(aStatus == Killing,
5004 mStatus == Canceling && mParentStatus == Canceling);
5006 mStatus = aStatus;
5008 // Mark parent status as closing immediately to avoid new events being
5009 // dispatched after we clear the queue below.
5010 if (aStatus == Closing) {
5011 Close();
5014 // Synchronize the mParentStatus with mStatus, such that event dispatching
5015 // will fail in proper after WorkerPrivate gets into Killing status.
5016 if (aStatus >= Killing) {
5017 mParentStatus = aStatus;
5021 // Status transistion to "Canceling"/"Killing", mark the scope as dying when
5022 // "Canceling," or shutdown the StorageManager when "Killing."
5023 if (aStatus >= Canceling) {
5024 if (data->mScope) {
5025 if (aStatus == Canceling) {
5026 data->mScope->NoteTerminating();
5027 } else {
5028 data->mScope->NoteShuttingDown();
5033 if (aStatus >= Closing) {
5034 CancelAllTimeouts();
5037 if (aStatus == Closing && GlobalScope()) {
5038 GlobalScope()->SetIsNotEligibleForMessaging();
5041 // Let all our holders know the new status.
5042 if (aStatus == Canceling) {
5043 NotifyWorkerRefs(aStatus);
5046 // If the worker script never ran, or failed to compile, we don't need to do
5047 // anything else.
5048 WorkerGlobalScope* global = GlobalScope();
5049 if (!global) {
5050 if (aStatus == Canceling) {
5051 MOZ_ASSERT(!data->mCancelBeforeWorkerScopeConstructed);
5052 data->mCancelBeforeWorkerScopeConstructed.Flip();
5054 return true;
5057 // Don't abort the script now, but we dispatch a runnable to do it when the
5058 // current JS frame is executed.
5059 if (aStatus == Closing) {
5060 if (!mSyncLoopStack.IsEmpty()) {
5061 LOG(WorkerLog(), ("WorkerPrivate::NotifyInternal [%p] request to "
5062 "dispatch canceling runnables...",
5063 this));
5064 mPostSyncLoopOperations |= eDispatchCancelingRunnable;
5065 } else {
5066 DispatchCancelingRunnable();
5068 return true;
5071 MOZ_ASSERT(aStatus == Canceling || aStatus == Killing);
5073 LOG(WorkerLog(), ("WorkerPrivate::NotifyInternal [%p] abort script", this));
5075 // Always abort the script.
5076 return false;
5079 void WorkerPrivate::ReportError(JSContext* aCx,
5080 JS::ConstUTF8CharsZ aToStringResult,
5081 JSErrorReport* aReport) {
5082 auto data = mWorkerThreadAccessible.Access();
5084 if (!MayContinueRunning() || data->mErrorHandlerRecursionCount == 2) {
5085 return;
5088 NS_ASSERTION(data->mErrorHandlerRecursionCount == 0 ||
5089 data->mErrorHandlerRecursionCount == 1,
5090 "Bad recursion logic!");
5092 UniquePtr<WorkerErrorReport> report = MakeUnique<WorkerErrorReport>();
5093 if (aReport) {
5094 report->AssignErrorReport(aReport);
5097 JS::ExceptionStack exnStack(aCx);
5098 if (JS_IsExceptionPending(aCx)) {
5099 if (!JS::StealPendingExceptionStack(aCx, &exnStack)) {
5100 JS_ClearPendingException(aCx);
5101 return;
5104 JS::Rooted<JSObject*> stack(aCx), stackGlobal(aCx);
5105 xpc::FindExceptionStackForConsoleReport(
5106 nullptr, exnStack.exception(), exnStack.stack(), &stack, &stackGlobal);
5108 if (stack) {
5109 JSAutoRealm ar(aCx, stackGlobal);
5110 report->SerializeWorkerStack(aCx, this, stack);
5112 } else {
5113 // ReportError is also used for reporting warnings,
5114 // so there won't be a pending exception.
5115 MOZ_ASSERT(aReport && aReport->isWarning());
5118 if (report->mMessage.IsEmpty() && aToStringResult) {
5119 nsDependentCString toStringResult(aToStringResult.c_str());
5120 if (!AppendUTF8toUTF16(toStringResult, report->mMessage,
5121 mozilla::fallible)) {
5122 // Try again, with only a 1 KB string. Do this infallibly this time.
5123 // If the user doesn't have 1 KB to spare we're done anyways.
5124 size_t index = std::min<size_t>(1024, toStringResult.Length());
5126 // Drop the last code point that may be cropped.
5127 index = RewindToPriorUTF8Codepoint(toStringResult.BeginReading(), index);
5129 nsDependentCString truncatedToStringResult(aToStringResult.c_str(),
5130 index);
5131 AppendUTF8toUTF16(truncatedToStringResult, report->mMessage);
5135 data->mErrorHandlerRecursionCount++;
5137 // Don't want to run the scope's error handler if this is a recursive error or
5138 // if we ran out of memory.
5139 bool fireAtScope = data->mErrorHandlerRecursionCount == 1 &&
5140 report->mErrorNumber != JSMSG_OUT_OF_MEMORY &&
5141 JS::CurrentGlobalOrNull(aCx);
5143 WorkerErrorReport::ReportError(aCx, this, fireAtScope, nullptr,
5144 std::move(report), 0, exnStack.exception());
5146 data->mErrorHandlerRecursionCount--;
5149 // static
5150 void WorkerPrivate::ReportErrorToConsole(const char* aMessage) {
5151 nsTArray<nsString> emptyParams;
5152 WorkerPrivate::ReportErrorToConsole(aMessage, emptyParams);
5155 // static
5156 void WorkerPrivate::ReportErrorToConsole(const char* aMessage,
5157 const nsTArray<nsString>& aParams) {
5158 WorkerPrivate* wp = nullptr;
5159 if (!NS_IsMainThread()) {
5160 wp = GetCurrentThreadWorkerPrivate();
5163 ReportErrorToConsoleRunnable::Report(wp, aMessage, aParams);
5166 int32_t WorkerPrivate::SetTimeout(JSContext* aCx, TimeoutHandler* aHandler,
5167 int32_t aTimeout, bool aIsInterval,
5168 Timeout::Reason aReason, ErrorResult& aRv) {
5169 auto data = mWorkerThreadAccessible.Access();
5170 MOZ_ASSERT(aHandler);
5172 // Reasons that doesn't support cancellation will get -1 as their ids.
5173 int32_t timerId = -1;
5174 if (aReason == Timeout::Reason::eTimeoutOrInterval) {
5175 timerId = data->mNextTimeoutId;
5176 data->mNextTimeoutId += 1;
5179 WorkerStatus currentStatus;
5181 MutexAutoLock lock(mMutex);
5182 currentStatus = mStatus;
5185 // If the worker is trying to call setTimeout/setInterval and the parent
5186 // thread has initiated the close process then just silently fail.
5187 if (currentStatus >= Closing) {
5188 return timerId;
5191 auto newInfo = MakeUnique<TimeoutInfo>();
5192 newInfo->mReason = aReason;
5193 newInfo->mOnChromeWorker = mIsChromeWorker;
5194 newInfo->mIsInterval = aIsInterval;
5195 newInfo->mId = timerId;
5196 if (newInfo->mReason == Timeout::Reason::eTimeoutOrInterval ||
5197 newInfo->mReason == Timeout::Reason::eIdleCallbackTimeout) {
5198 newInfo->AccumulateNestingLevel(data->mCurrentTimerNestingLevel);
5201 if (MOZ_UNLIKELY(timerId == INT32_MAX)) {
5202 NS_WARNING("Timeout ids overflowed!");
5203 if (aReason == Timeout::Reason::eTimeoutOrInterval) {
5204 data->mNextTimeoutId = 1;
5208 newInfo->mHandler = aHandler;
5210 // See if any of the optional arguments were passed.
5211 aTimeout = std::max(0, aTimeout);
5212 newInfo->mInterval = TimeDuration::FromMilliseconds(aTimeout);
5213 newInfo->CalculateTargetTime();
5215 const auto& insertedInfo = data->mTimeouts.InsertElementSorted(
5216 std::move(newInfo), GetUniquePtrComparator(data->mTimeouts));
5218 LOG(TimeoutsLog(), ("Worker %p has new timeout: delay=%d interval=%s\n", this,
5219 aTimeout, aIsInterval ? "yes" : "no"));
5221 // If the timeout we just made is set to fire next then we need to update the
5222 // timer, unless we're currently running timeouts.
5223 if (insertedInfo == data->mTimeouts.Elements() &&
5224 !data->mRunningExpiredTimeouts) {
5225 if (!data->mTimer) {
5226 data->mTimer = NS_NewTimer(GlobalScope()->SerialEventTarget());
5227 if (!data->mTimer) {
5228 aRv.Throw(NS_ERROR_UNEXPECTED);
5229 return 0;
5232 data->mTimerRunnable = new RunExpiredTimoutsRunnable(this);
5235 if (!data->mTimerRunning) {
5236 UpdateCCFlag(CCFlag::IneligibleForTimeout);
5237 data->mTimerRunning = true;
5240 if (!RescheduleTimeoutTimer(aCx)) {
5241 aRv.Throw(NS_ERROR_FAILURE);
5242 return 0;
5246 return timerId;
5249 void WorkerPrivate::ClearTimeout(int32_t aId, Timeout::Reason aReason) {
5250 MOZ_ASSERT(aReason == Timeout::Reason::eTimeoutOrInterval,
5251 "This timeout reason doesn't support cancellation.");
5253 auto data = mWorkerThreadAccessible.Access();
5255 if (!data->mTimeouts.IsEmpty()) {
5256 NS_ASSERTION(data->mTimerRunning, "Huh?!");
5258 for (uint32_t index = 0; index < data->mTimeouts.Length(); index++) {
5259 const auto& info = data->mTimeouts[index];
5260 if (info->mId == aId && info->mReason == aReason) {
5261 info->mCanceled = true;
5262 break;
5268 bool WorkerPrivate::RunExpiredTimeouts(JSContext* aCx) {
5269 auto data = mWorkerThreadAccessible.Access();
5271 // We may be called recursively (e.g. close() inside a timeout) or we could
5272 // have been canceled while this event was pending, bail out if there is
5273 // nothing to do.
5274 if (data->mRunningExpiredTimeouts || !data->mTimerRunning) {
5275 return true;
5278 NS_ASSERTION(data->mTimer && data->mTimerRunnable, "Must have a timer!");
5279 NS_ASSERTION(!data->mTimeouts.IsEmpty(), "Should have some work to do!");
5281 bool retval = true;
5283 auto comparator = GetUniquePtrComparator(data->mTimeouts);
5284 JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
5286 // We want to make sure to run *something*, even if the timer fired a little
5287 // early. Fudge the value of now to at least include the first timeout.
5288 const TimeStamp actual_now = TimeStamp::Now();
5289 const TimeStamp now = std::max(actual_now, data->mTimeouts[0]->mTargetTime);
5291 if (now != actual_now) {
5292 LOG(TimeoutsLog(), ("Worker %p fudged timeout by %f ms.\n", this,
5293 (now - actual_now).ToMilliseconds()));
5294 #ifdef DEBUG
5295 double microseconds = (now - actual_now).ToMicroseconds();
5296 uint32_t allowedEarlyFiringMicroseconds;
5297 data->mTimer->GetAllowedEarlyFiringMicroseconds(
5298 &allowedEarlyFiringMicroseconds);
5299 MOZ_ASSERT(microseconds < allowedEarlyFiringMicroseconds);
5300 #endif
5303 AutoTArray<TimeoutInfo*, 10> expiredTimeouts;
5304 for (uint32_t index = 0; index < data->mTimeouts.Length(); index++) {
5305 TimeoutInfo* info = data->mTimeouts[index].get();
5306 if (info->mTargetTime > now) {
5307 break;
5309 expiredTimeouts.AppendElement(info);
5312 // Guard against recursion.
5313 data->mRunningExpiredTimeouts = true;
5315 MOZ_DIAGNOSTIC_ASSERT(data->mCurrentTimerNestingLevel == 0);
5317 // Run expired timeouts.
5318 for (uint32_t index = 0; index < expiredTimeouts.Length(); index++) {
5319 TimeoutInfo*& info = expiredTimeouts[index];
5320 AutoRestore<uint32_t> nestingLevel(data->mCurrentTimerNestingLevel);
5322 if (info->mCanceled) {
5323 continue;
5326 // Set current timer nesting level to current running timer handler's
5327 // nesting level
5328 data->mCurrentTimerNestingLevel = info->mNestingLevel;
5330 LOG(TimeoutsLog(),
5331 ("Worker %p executing timeout with original delay %f ms.\n", this,
5332 info->mInterval.ToMilliseconds()));
5334 // Always check JS_IsExceptionPending if something fails, and if
5335 // JS_IsExceptionPending returns false (i.e. uncatchable exception) then
5336 // break out of the loop.
5338 RefPtr<TimeoutHandler> handler(info->mHandler);
5340 const char* reason;
5341 switch (info->mReason) {
5342 case Timeout::Reason::eTimeoutOrInterval:
5343 if (info->mIsInterval) {
5344 reason = "setInterval handler";
5345 } else {
5346 reason = "setTimeout handler";
5348 break;
5349 case Timeout::Reason::eDelayedWebTaskTimeout:
5350 reason = "delayedWebTask handler";
5351 break;
5352 default:
5353 MOZ_ASSERT(info->mReason == Timeout::Reason::eAbortSignalTimeout);
5354 reason = "AbortSignal Timeout";
5356 if (info->mReason == Timeout::Reason::eTimeoutOrInterval ||
5357 info->mReason == Timeout::Reason::eDelayedWebTaskTimeout) {
5358 RefPtr<WorkerGlobalScope> scope(this->GlobalScope());
5359 CallbackDebuggerNotificationGuard guard(
5360 scope, info->mIsInterval
5361 ? DebuggerNotificationType::SetIntervalCallback
5362 : DebuggerNotificationType::SetTimeoutCallback);
5364 if (!handler->Call(reason)) {
5365 retval = false;
5366 break;
5368 } else {
5369 MOZ_ASSERT(info->mReason == Timeout::Reason::eAbortSignalTimeout);
5370 MOZ_ALWAYS_TRUE(handler->Call(reason));
5373 NS_ASSERTION(data->mRunningExpiredTimeouts, "Someone changed this!");
5376 // No longer possible to be called recursively.
5377 data->mRunningExpiredTimeouts = false;
5379 // Now remove canceled and expired timeouts from the main list.
5380 // NB: The timeouts present in expiredTimeouts must have the same order
5381 // with respect to each other in mTimeouts. That is, mTimeouts is just
5382 // expiredTimeouts with extra elements inserted. There may be unexpired
5383 // timeouts that have been inserted between the expired timeouts if the
5384 // timeout event handler called setTimeout/setInterval.
5385 for (uint32_t index = 0, expiredTimeoutIndex = 0,
5386 expiredTimeoutLength = expiredTimeouts.Length();
5387 index < data->mTimeouts.Length();) {
5388 const auto& info = data->mTimeouts[index];
5389 if ((expiredTimeoutIndex < expiredTimeoutLength &&
5390 info == expiredTimeouts[expiredTimeoutIndex] &&
5391 ++expiredTimeoutIndex) ||
5392 info->mCanceled) {
5393 if (info->mIsInterval && !info->mCanceled) {
5394 // Reschedule intervals.
5395 // Reschedule a timeout, if needed, increase the nesting level.
5396 info->AccumulateNestingLevel(info->mNestingLevel);
5397 info->CalculateTargetTime();
5398 // Don't resort the list here, we'll do that at the end.
5399 ++index;
5400 } else {
5401 data->mTimeouts.RemoveElement(info);
5403 } else {
5404 // If info did not match the current entry in expiredTimeouts, it
5405 // shouldn't be there at all.
5406 NS_ASSERTION(!expiredTimeouts.Contains(info),
5407 "Our timeouts are out of order!");
5408 ++index;
5412 data->mTimeouts.Sort(comparator);
5414 // Either signal the parent that we're no longer using timeouts or reschedule
5415 // the timer.
5416 if (data->mTimeouts.IsEmpty()) {
5417 UpdateCCFlag(CCFlag::EligibleForTimeout);
5418 data->mTimerRunning = false;
5419 } else if (retval && !RescheduleTimeoutTimer(aCx)) {
5420 retval = false;
5423 return retval;
5426 bool WorkerPrivate::RescheduleTimeoutTimer(JSContext* aCx) {
5427 auto data = mWorkerThreadAccessible.Access();
5428 MOZ_ASSERT(!data->mRunningExpiredTimeouts);
5429 NS_ASSERTION(!data->mTimeouts.IsEmpty(), "Should have some timeouts!");
5430 NS_ASSERTION(data->mTimer && data->mTimerRunnable, "Should have a timer!");
5432 // NB: This is important! The timer may have already fired, e.g. if a timeout
5433 // callback itself calls setTimeout for a short duration and then takes longer
5434 // than that to finish executing. If that has happened, it's very important
5435 // that we don't execute the event that is now pending in our event queue, or
5436 // our code in RunExpiredTimeouts to "fudge" the timeout value will unleash an
5437 // early timeout when we execute the event we're about to queue.
5438 data->mTimer->Cancel();
5440 double delta =
5441 (data->mTimeouts[0]->mTargetTime - TimeStamp::Now()).ToMilliseconds();
5442 uint32_t delay = delta > 0 ? static_cast<uint32_t>(std::ceil(
5443 std::min(delta, double(UINT32_MAX))))
5444 : 0;
5446 LOG(TimeoutsLog(),
5447 ("Worker %p scheduled timer for %d ms, %zu pending timeouts\n", this,
5448 delay, data->mTimeouts.Length()));
5450 nsresult rv = data->mTimer->InitWithCallback(data->mTimerRunnable, delay,
5451 nsITimer::TYPE_ONE_SHOT);
5452 if (NS_FAILED(rv)) {
5453 JS_ReportErrorASCII(aCx, "Failed to start timer!");
5454 return false;
5457 return true;
5460 void WorkerPrivate::StartCancelingTimer() {
5461 AssertIsOnParentThread();
5463 // return if mCancelingTimer has already existed.
5464 if (mCancelingTimer) {
5465 return;
5468 auto errorCleanup = MakeScopeExit([&] { mCancelingTimer = nullptr; });
5470 if (WorkerPrivate* parent = GetParent()) {
5471 mCancelingTimer = NS_NewTimer(parent->ControlEventTarget());
5472 } else {
5473 mCancelingTimer = NS_NewTimer();
5476 if (NS_WARN_IF(!mCancelingTimer)) {
5477 return;
5480 // This is not needed if we are already in an advanced shutdown state.
5482 MutexAutoLock lock(mMutex);
5483 if (ParentStatus() >= Canceling) {
5484 return;
5488 uint32_t cancelingTimeoutMillis =
5489 StaticPrefs::dom_worker_canceling_timeoutMilliseconds();
5491 RefPtr<CancelingTimerCallback> callback = new CancelingTimerCallback(this);
5492 nsresult rv = mCancelingTimer->InitWithCallback(
5493 callback, cancelingTimeoutMillis, nsITimer::TYPE_ONE_SHOT);
5494 if (NS_WARN_IF(NS_FAILED(rv))) {
5495 return;
5498 errorCleanup.release();
5501 void WorkerPrivate::UpdateContextOptionsInternal(
5502 JSContext* aCx, const JS::ContextOptions& aContextOptions) {
5503 auto data = mWorkerThreadAccessible.Access();
5505 JS::ContextOptionsRef(aCx) = aContextOptions;
5507 for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
5508 data->mChildWorkers[index]->UpdateContextOptions(aContextOptions);
5512 void WorkerPrivate::UpdateLanguagesInternal(
5513 const nsTArray<nsString>& aLanguages) {
5514 WorkerGlobalScope* globalScope = GlobalScope();
5515 RefPtr<WorkerNavigator> nav = globalScope->GetExistingNavigator();
5516 if (nav) {
5517 nav->SetLanguages(aLanguages);
5520 auto data = mWorkerThreadAccessible.Access();
5521 for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
5522 data->mChildWorkers[index]->UpdateLanguages(aLanguages);
5525 RefPtr<Event> event = NS_NewDOMEvent(globalScope, nullptr, nullptr);
5527 event->InitEvent(u"languagechange"_ns, false, false);
5528 event->SetTrusted(true);
5530 globalScope->DispatchEvent(*event);
5533 void WorkerPrivate::UpdateJSWorkerMemoryParameterInternal(
5534 JSContext* aCx, JSGCParamKey aKey, Maybe<uint32_t> aValue) {
5535 auto data = mWorkerThreadAccessible.Access();
5537 if (aValue) {
5538 JS_SetGCParameter(aCx, aKey, *aValue);
5539 } else {
5540 JS_ResetGCParameter(aCx, aKey);
5543 for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
5544 data->mChildWorkers[index]->UpdateJSWorkerMemoryParameter(aKey, aValue);
5548 #ifdef JS_GC_ZEAL
5549 void WorkerPrivate::UpdateGCZealInternal(JSContext* aCx, uint8_t aGCZeal,
5550 uint32_t aFrequency) {
5551 auto data = mWorkerThreadAccessible.Access();
5553 JS_SetGCZeal(aCx, aGCZeal, aFrequency);
5555 for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
5556 data->mChildWorkers[index]->UpdateGCZeal(aGCZeal, aFrequency);
5559 #endif
5561 void WorkerPrivate::SetLowMemoryStateInternal(JSContext* aCx, bool aState) {
5562 auto data = mWorkerThreadAccessible.Access();
5564 JS::SetLowMemoryState(aCx, aState);
5566 for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
5567 data->mChildWorkers[index]->SetLowMemoryState(aState);
5571 void WorkerPrivate::SetCCCollectedAnything(bool collectedAnything) {
5572 mWorkerThreadAccessible.Access()->mCCCollectedAnything = collectedAnything;
5575 bool WorkerPrivate::isLastCCCollectedAnything() {
5576 return mWorkerThreadAccessible.Access()->mCCCollectedAnything;
5579 void WorkerPrivate::GarbageCollectInternal(JSContext* aCx, bool aShrinking,
5580 bool aCollectChildren) {
5581 // Perform GC followed by CC (the CC is triggered by
5582 // WorkerJSRuntime::CustomGCCallback at the end of the collection).
5584 auto data = mWorkerThreadAccessible.Access();
5586 if (!GlobalScope()) {
5587 // We haven't compiled anything yet. Just bail out.
5588 return;
5591 if (aShrinking || aCollectChildren) {
5592 JS::PrepareForFullGC(aCx);
5594 if (aShrinking && mSyncLoopStack.IsEmpty()) {
5595 JS::NonIncrementalGC(aCx, JS::GCOptions::Shrink,
5596 JS::GCReason::DOM_WORKER);
5598 // Check whether the CC collected anything and if so GC again. This is
5599 // necessary to collect all garbage.
5600 if (data->mCCCollectedAnything) {
5601 JS::NonIncrementalGC(aCx, JS::GCOptions::Normal,
5602 JS::GCReason::DOM_WORKER);
5605 if (!aCollectChildren) {
5606 LOG(WorkerLog(), ("Worker %p collected idle garbage\n", this));
5608 } else {
5609 JS::NonIncrementalGC(aCx, JS::GCOptions::Normal,
5610 JS::GCReason::DOM_WORKER);
5611 LOG(WorkerLog(), ("Worker %p collected garbage\n", this));
5613 } else {
5614 JS_MaybeGC(aCx);
5615 LOG(WorkerLog(), ("Worker %p collected periodic garbage\n", this));
5618 if (aCollectChildren) {
5619 for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
5620 data->mChildWorkers[index]->GarbageCollect(aShrinking);
5625 void WorkerPrivate::CycleCollectInternal(bool aCollectChildren) {
5626 auto data = mWorkerThreadAccessible.Access();
5628 nsCycleCollector_collect(CCReason::WORKER, nullptr);
5630 if (aCollectChildren) {
5631 for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
5632 data->mChildWorkers[index]->CycleCollect();
5637 void WorkerPrivate::MemoryPressureInternal() {
5638 auto data = mWorkerThreadAccessible.Access();
5640 if (data->mScope) {
5641 RefPtr<Console> console = data->mScope->GetConsoleIfExists();
5642 if (console) {
5643 console->ClearStorage();
5646 RefPtr<Performance> performance = data->mScope->GetPerformanceIfExists();
5647 if (performance) {
5648 performance->MemoryPressure();
5651 data->mScope->RemoveReportRecords();
5654 if (data->mDebuggerScope) {
5655 RefPtr<Console> console = data->mDebuggerScope->GetConsoleIfExists();
5656 if (console) {
5657 console->ClearStorage();
5661 for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
5662 data->mChildWorkers[index]->MemoryPressure();
5666 void WorkerPrivate::SetThread(WorkerThread* aThread) {
5667 if (aThread) {
5668 #ifdef DEBUG
5670 bool isOnCurrentThread;
5671 MOZ_ASSERT(NS_SUCCEEDED(aThread->IsOnCurrentThread(&isOnCurrentThread)));
5672 MOZ_ASSERT(!isOnCurrentThread);
5674 #endif
5676 MOZ_ASSERT(!mPRThread);
5677 mPRThread = PRThreadFromThread(aThread);
5678 MOZ_ASSERT(mPRThread);
5680 mWorkerThreadAccessible.Transfer(mPRThread);
5681 } else {
5682 MOZ_ASSERT(mPRThread);
5686 void WorkerPrivate::SetWorkerPrivateInWorkerThread(
5687 WorkerThread* const aThread) {
5688 LOG(WorkerLog(),
5689 ("WorkerPrivate::SetWorkerPrivateInWorkerThread [%p]", this));
5690 MutexAutoLock lock(mMutex);
5692 MOZ_ASSERT(!mThread);
5693 MOZ_ASSERT(mStatus == Pending);
5695 mThread = aThread;
5696 mThread->SetWorker(WorkerThreadFriendKey{}, this);
5698 if (!mPreStartRunnables.IsEmpty()) {
5699 for (uint32_t index = 0; index < mPreStartRunnables.Length(); index++) {
5700 MOZ_ALWAYS_SUCCEEDS(mThread->DispatchAnyThread(
5701 WorkerThreadFriendKey{}, mPreStartRunnables[index].forget()));
5703 mPreStartRunnables.Clear();
5707 void WorkerPrivate::ResetWorkerPrivateInWorkerThread() {
5708 LOG(WorkerLog(),
5709 ("WorkerPrivate::ResetWorkerPrivateInWorkerThread [%p]", this));
5710 RefPtr<WorkerThread> doomedThread;
5712 // Release the mutex before doomedThread.
5713 MutexAutoLock lock(mMutex);
5715 MOZ_ASSERT(mThread);
5717 mThread->SetWorker(WorkerThreadFriendKey{}, nullptr);
5718 mThread.swap(doomedThread);
5721 void WorkerPrivate::BeginCTypesCall() {
5722 AssertIsOnWorkerThread();
5723 auto data = mWorkerThreadAccessible.Access();
5725 // Don't try to GC while we're blocked in a ctypes call.
5726 SetGCTimerMode(NoTimer);
5728 data->mYieldJSThreadExecution.EmplaceBack();
5731 void WorkerPrivate::EndCTypesCall() {
5732 AssertIsOnWorkerThread();
5733 auto data = mWorkerThreadAccessible.Access();
5735 data->mYieldJSThreadExecution.RemoveLastElement();
5737 // Make sure the periodic timer is running before we start running JS again.
5738 SetGCTimerMode(PeriodicTimer);
5741 void WorkerPrivate::BeginCTypesCallback() {
5742 AssertIsOnWorkerThread();
5744 // Make sure the periodic timer is running before we start running JS again.
5745 SetGCTimerMode(PeriodicTimer);
5747 // Re-requesting execution is not needed since the JSRuntime code calling
5748 // this will do an AutoEntryScript.
5751 void WorkerPrivate::EndCTypesCallback() {
5752 AssertIsOnWorkerThread();
5754 // Don't try to GC while we're blocked in a ctypes call.
5755 SetGCTimerMode(NoTimer);
5758 bool WorkerPrivate::ConnectMessagePort(JSContext* aCx,
5759 UniqueMessagePortId& aIdentifier) {
5760 AssertIsOnWorkerThread();
5762 WorkerGlobalScope* globalScope = GlobalScope();
5764 JS::Rooted<JSObject*> jsGlobal(aCx, globalScope->GetWrapper());
5765 MOZ_ASSERT(jsGlobal);
5767 // This UniqueMessagePortId is used to create a new port, still connected
5768 // with the other one, but in the worker thread.
5769 ErrorResult rv;
5770 RefPtr<MessagePort> port = MessagePort::Create(globalScope, aIdentifier, rv);
5771 if (NS_WARN_IF(rv.Failed())) {
5772 rv.SuppressException();
5773 return false;
5776 GlobalObject globalObject(aCx, jsGlobal);
5777 if (globalObject.Failed()) {
5778 return false;
5781 RootedDictionary<MessageEventInit> init(aCx);
5782 init.mData = JS_GetEmptyStringValue(aCx);
5783 init.mBubbles = false;
5784 init.mCancelable = false;
5785 init.mSource.SetValue().SetAsMessagePort() = port;
5786 if (!init.mPorts.AppendElement(port.forget(), fallible)) {
5787 return false;
5790 RefPtr<MessageEvent> event =
5791 MessageEvent::Constructor(globalObject, u"connect"_ns, init);
5793 event->SetTrusted(true);
5795 globalScope->DispatchEvent(*event);
5797 return true;
5800 WorkerGlobalScope* WorkerPrivate::GetOrCreateGlobalScope(JSContext* aCx) {
5801 auto data = mWorkerThreadAccessible.Access();
5803 if (data->mScope) {
5804 return data->mScope;
5807 if (IsSharedWorker()) {
5808 data->mScope =
5809 new SharedWorkerGlobalScope(this, CreateClientSource(), WorkerName());
5810 } else if (IsServiceWorker()) {
5811 data->mScope = new ServiceWorkerGlobalScope(
5812 this, CreateClientSource(), GetServiceWorkerRegistrationDescriptor());
5813 } else {
5814 data->mScope = new DedicatedWorkerGlobalScope(this, CreateClientSource(),
5815 WorkerName());
5818 JS::Rooted<JSObject*> global(aCx);
5819 NS_ENSURE_TRUE(data->mScope->WrapGlobalObject(aCx, &global), nullptr);
5821 JSAutoRealm ar(aCx, global);
5823 if (!RegisterBindings(aCx, global)) {
5824 data->mScope = nullptr;
5825 return nullptr;
5828 // Worker has already in "Canceling", let the WorkerGlobalScope start dying.
5829 if (data->mCancelBeforeWorkerScopeConstructed) {
5830 data->mScope->NoteTerminating();
5831 data->mScope->DisconnectGlobalTeardownObservers();
5834 JS_FireOnNewGlobalObject(aCx, global);
5836 return data->mScope;
5839 WorkerDebuggerGlobalScope* WorkerPrivate::CreateDebuggerGlobalScope(
5840 JSContext* aCx) {
5841 auto data = mWorkerThreadAccessible.Access();
5842 MOZ_ASSERT(!data->mDebuggerScope);
5844 // The debugger global gets a dummy client, not the "real" client used by the
5845 // debugee worker.
5846 auto clientSource = ClientManager::CreateSource(
5847 GetClientType(), HybridEventTarget(), NullPrincipalInfo());
5849 data->mDebuggerScope =
5850 new WorkerDebuggerGlobalScope(this, std::move(clientSource));
5852 JS::Rooted<JSObject*> global(aCx);
5853 NS_ENSURE_TRUE(data->mDebuggerScope->WrapGlobalObject(aCx, &global), nullptr);
5855 JSAutoRealm ar(aCx, global);
5857 if (!RegisterDebuggerBindings(aCx, global)) {
5858 data->mDebuggerScope = nullptr;
5859 return nullptr;
5862 JS_FireOnNewGlobalObject(aCx, global);
5864 return data->mDebuggerScope;
5867 bool WorkerPrivate::IsOnWorkerThread() const {
5868 // We can't use mThread because it must be protected by mMutex and sometimes
5869 // this method is called when mMutex is already locked. This method should
5870 // always work.
5871 MOZ_ASSERT(mPRThread,
5872 "AssertIsOnWorkerThread() called before a thread was assigned!");
5874 return mPRThread == PR_GetCurrentThread();
5877 #ifdef DEBUG
5878 void WorkerPrivate::AssertIsOnWorkerThread() const {
5879 MOZ_ASSERT(IsOnWorkerThread());
5881 #endif // DEBUG
5883 void WorkerPrivate::DumpCrashInformation(nsACString& aString) {
5884 auto data = mWorkerThreadAccessible.Access();
5886 aString.Append("IsChromeWorker(");
5887 if (IsChromeWorker()) {
5888 aString.Append(NS_ConvertUTF16toUTF8(ScriptURL()));
5889 } else {
5890 aString.Append("false");
5892 aString.Append(")");
5893 for (const auto* workerRef : data->mWorkerRefs.NonObservingRange()) {
5894 if (workerRef->IsPreventingShutdown()) {
5895 aString.Append("|");
5896 aString.Append(workerRef->Name());
5897 const nsCString status = GET_WORKERREF_DEBUG_STATUS(workerRef);
5898 if (!status.IsEmpty()) {
5899 aString.Append("[");
5900 aString.Append(status);
5901 aString.Append("]");
5907 PerformanceStorage* WorkerPrivate::GetPerformanceStorage() {
5908 MOZ_ASSERT(mPerformanceStorage);
5909 return mPerformanceStorage;
5912 bool WorkerPrivate::ShouldResistFingerprinting(RFPTarget aTarget) const {
5913 return mLoadInfo.mShouldResistFingerprinting &&
5914 nsRFPService::IsRFPEnabledFor(
5915 mLoadInfo.mOriginAttributes.mPrivateBrowsingId >
5916 nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID,
5917 aTarget, mLoadInfo.mOverriddenFingerprintingSettings);
5920 void WorkerPrivate::SetRemoteWorkerController(RemoteWorkerChild* aController) {
5921 AssertIsOnMainThread();
5922 MOZ_ASSERT(aController);
5923 MOZ_ASSERT(!mRemoteWorkerController);
5925 mRemoteWorkerController = aController;
5928 RemoteWorkerChild* WorkerPrivate::GetRemoteWorkerController() {
5929 AssertIsOnMainThread();
5930 MOZ_ASSERT(mRemoteWorkerController);
5931 return mRemoteWorkerController;
5934 RefPtr<GenericPromise> WorkerPrivate::SetServiceWorkerSkipWaitingFlag() {
5935 AssertIsOnWorkerThread();
5936 MOZ_ASSERT(IsServiceWorker());
5938 RefPtr<RemoteWorkerChild> rwc = mRemoteWorkerController;
5940 if (!rwc) {
5941 return GenericPromise::CreateAndReject(NS_ERROR_DOM_ABORT_ERR, __func__);
5944 RefPtr<GenericPromise> promise =
5945 rwc->MaybeSendSetServiceWorkerSkipWaitingFlag();
5947 return promise;
5950 const nsAString& WorkerPrivate::Id() {
5951 AssertIsOnMainThread();
5953 if (mId.IsEmpty()) {
5954 mId = ComputeWorkerPrivateId();
5957 MOZ_ASSERT(!mId.IsEmpty());
5959 return mId;
5962 bool WorkerPrivate::IsSharedMemoryAllowed() const {
5963 if (StaticPrefs::
5964 dom_postMessage_sharedArrayBuffer_bypassCOOP_COEP_insecure_enabled()) {
5965 return true;
5968 if (mIsPrivilegedAddonGlobal) {
5969 return true;
5972 return CrossOriginIsolated();
5975 bool WorkerPrivate::CrossOriginIsolated() const {
5976 if (!StaticPrefs::
5977 dom_postMessage_sharedArrayBuffer_withCOOP_COEP_AtStartup()) {
5978 return false;
5981 return mAgentClusterOpenerPolicy ==
5982 nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP;
5985 nsILoadInfo::CrossOriginEmbedderPolicy WorkerPrivate::GetEmbedderPolicy()
5986 const {
5987 if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
5988 return nsILoadInfo::EMBEDDER_POLICY_NULL;
5991 return mEmbedderPolicy.valueOr(nsILoadInfo::EMBEDDER_POLICY_NULL);
5994 Result<Ok, nsresult> WorkerPrivate::SetEmbedderPolicy(
5995 nsILoadInfo::CrossOriginEmbedderPolicy aPolicy) {
5996 MOZ_ASSERT(NS_IsMainThread());
5997 MOZ_ASSERT(mEmbedderPolicy.isNothing());
5999 if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
6000 return Ok();
6003 // https://html.spec.whatwg.org/multipage/browsers.html#check-a-global-object's-embedder-policy
6004 // If ownerPolicy's value is not compatible with cross-origin isolation or
6005 // policy's value is compatible with cross-origin isolation, then return true.
6006 EnsureOwnerEmbedderPolicy();
6007 nsILoadInfo::CrossOriginEmbedderPolicy ownerPolicy =
6008 mOwnerEmbedderPolicy.valueOr(nsILoadInfo::EMBEDDER_POLICY_NULL);
6009 if (nsContentSecurityManager::IsCompatibleWithCrossOriginIsolation(
6010 ownerPolicy) &&
6011 !nsContentSecurityManager::IsCompatibleWithCrossOriginIsolation(
6012 aPolicy)) {
6013 return Err(NS_ERROR_BLOCKED_BY_POLICY);
6016 mEmbedderPolicy.emplace(aPolicy);
6018 return Ok();
6021 void WorkerPrivate::InheritOwnerEmbedderPolicyOrNull(nsIRequest* aRequest) {
6022 MOZ_ASSERT(NS_IsMainThread());
6023 MOZ_ASSERT(aRequest);
6025 EnsureOwnerEmbedderPolicy();
6027 if (mOwnerEmbedderPolicy.isSome()) {
6028 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
6029 MOZ_ASSERT(channel);
6031 nsCOMPtr<nsIURI> scriptURI;
6032 MOZ_ALWAYS_SUCCEEDS(channel->GetURI(getter_AddRefs(scriptURI)));
6034 bool isLocalScriptURI = false;
6035 MOZ_ALWAYS_SUCCEEDS(NS_URIChainHasFlags(
6036 scriptURI, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
6037 &isLocalScriptURI));
6039 MOZ_RELEASE_ASSERT(isLocalScriptURI);
6042 mEmbedderPolicy.emplace(
6043 mOwnerEmbedderPolicy.valueOr(nsILoadInfo::EMBEDDER_POLICY_NULL));
6046 bool WorkerPrivate::MatchEmbedderPolicy(
6047 nsILoadInfo::CrossOriginEmbedderPolicy aPolicy) const {
6048 MOZ_ASSERT(NS_IsMainThread());
6050 if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
6051 return true;
6054 return mEmbedderPolicy.value() == aPolicy;
6057 nsILoadInfo::CrossOriginEmbedderPolicy WorkerPrivate::GetOwnerEmbedderPolicy()
6058 const {
6059 if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
6060 return nsILoadInfo::EMBEDDER_POLICY_NULL;
6063 return mOwnerEmbedderPolicy.valueOr(nsILoadInfo::EMBEDDER_POLICY_NULL);
6066 void WorkerPrivate::EnsureOwnerEmbedderPolicy() {
6067 MOZ_ASSERT(NS_IsMainThread());
6068 MOZ_ASSERT(mOwnerEmbedderPolicy.isNothing());
6070 if (GetParent()) {
6071 mOwnerEmbedderPolicy.emplace(GetParent()->GetEmbedderPolicy());
6072 } else if (GetWindow() && GetWindow()->GetWindowContext()) {
6073 mOwnerEmbedderPolicy.emplace(
6074 GetWindow()->GetWindowContext()->GetEmbedderPolicy());
6078 nsIPrincipal* WorkerPrivate::GetEffectiveStoragePrincipal() const {
6079 AssertIsOnWorkerThread();
6081 if (mLoadInfo.mUseRegularPrincipal) {
6082 return mLoadInfo.mPrincipal;
6085 return mLoadInfo.mPartitionedPrincipal;
6088 const mozilla::ipc::PrincipalInfo&
6089 WorkerPrivate::GetEffectiveStoragePrincipalInfo() const {
6090 AssertIsOnWorkerThread();
6092 if (mLoadInfo.mUseRegularPrincipal) {
6093 return *mLoadInfo.mPrincipalInfo;
6096 return *mLoadInfo.mPartitionedPrincipalInfo;
6099 NS_IMPL_ADDREF(WorkerPrivate::EventTarget)
6100 NS_IMPL_RELEASE(WorkerPrivate::EventTarget)
6102 NS_INTERFACE_MAP_BEGIN(WorkerPrivate::EventTarget)
6103 NS_INTERFACE_MAP_ENTRY(nsISerialEventTarget)
6104 NS_INTERFACE_MAP_ENTRY(nsIEventTarget)
6105 NS_INTERFACE_MAP_ENTRY(nsISupports)
6106 #ifdef DEBUG
6107 // kDEBUGWorkerEventTargetIID is special in that it does not AddRef its
6108 // result.
6109 if (aIID.Equals(kDEBUGWorkerEventTargetIID)) {
6110 *aInstancePtr = this;
6111 return NS_OK;
6112 } else
6113 #endif
6114 NS_INTERFACE_MAP_END
6116 NS_IMETHODIMP
6117 WorkerPrivate::EventTarget::DispatchFromScript(nsIRunnable* aRunnable,
6118 uint32_t aFlags) {
6119 nsCOMPtr<nsIRunnable> event(aRunnable);
6120 return Dispatch(event.forget(), aFlags);
6123 NS_IMETHODIMP
6124 WorkerPrivate::EventTarget::Dispatch(already_AddRefed<nsIRunnable> aRunnable,
6125 uint32_t aFlags) {
6126 // May be called on any thread!
6127 nsCOMPtr<nsIRunnable> event(aRunnable);
6129 // Workers only support asynchronous dispatch for now.
6130 if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL)) {
6131 return NS_ERROR_UNEXPECTED;
6134 RefPtr<WorkerRunnable> workerRunnable;
6136 MutexAutoLock lock(mMutex);
6138 if (mDisabled) {
6139 NS_WARNING(
6140 "A runnable was posted to a worker that is already shutting "
6141 "down!");
6142 return NS_ERROR_UNEXPECTED;
6145 MOZ_ASSERT(mWorkerPrivate);
6146 MOZ_ASSERT(mNestedEventTarget);
6148 if (event) {
6149 workerRunnable = mWorkerPrivate->MaybeWrapAsWorkerRunnable(event.forget());
6152 nsresult rv =
6153 mWorkerPrivate->Dispatch(workerRunnable.forget(), mNestedEventTarget);
6154 if (NS_WARN_IF(NS_FAILED(rv))) {
6155 return rv;
6158 return NS_OK;
6161 NS_IMETHODIMP
6162 WorkerPrivate::EventTarget::DelayedDispatch(already_AddRefed<nsIRunnable>,
6163 uint32_t)
6166 return NS_ERROR_NOT_IMPLEMENTED;
6169 NS_IMETHODIMP
6170 WorkerPrivate::EventTarget::RegisterShutdownTask(nsITargetShutdownTask* aTask) {
6171 return NS_ERROR_NOT_IMPLEMENTED;
6174 NS_IMETHODIMP
6175 WorkerPrivate::EventTarget::UnregisterShutdownTask(
6176 nsITargetShutdownTask* aTask) {
6177 return NS_ERROR_NOT_IMPLEMENTED;
6180 NS_IMETHODIMP
6181 WorkerPrivate::EventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread) {
6182 // May be called on any thread!
6184 MOZ_ASSERT(aIsOnCurrentThread);
6186 MutexAutoLock lock(mMutex);
6188 if (mShutdown) {
6189 NS_WARNING(
6190 "A worker's event target was used after the worker has shutdown!");
6191 return NS_ERROR_UNEXPECTED;
6194 MOZ_ASSERT(mNestedEventTarget);
6196 *aIsOnCurrentThread = mNestedEventTarget->IsOnCurrentThread();
6197 return NS_OK;
6200 NS_IMETHODIMP_(bool)
6201 WorkerPrivate::EventTarget::IsOnCurrentThreadInfallible() {
6202 // May be called on any thread!
6204 MutexAutoLock lock(mMutex);
6206 if (mShutdown) {
6207 NS_WARNING(
6208 "A worker's event target was used after the worker has shutdown!");
6209 return false;
6212 MOZ_ASSERT(mNestedEventTarget);
6214 return mNestedEventTarget->IsOnCurrentThread();
6217 WorkerPrivate::AutoPushEventLoopGlobal::AutoPushEventLoopGlobal(
6218 WorkerPrivate* aWorkerPrivate, JSContext* aCx)
6219 : mWorkerPrivate(aWorkerPrivate) {
6220 auto data = mWorkerPrivate->mWorkerThreadAccessible.Access();
6221 mOldEventLoopGlobal = std::move(data->mCurrentEventLoopGlobal);
6222 if (JSObject* global = JS::CurrentGlobalOrNull(aCx)) {
6223 data->mCurrentEventLoopGlobal = xpc::NativeGlobal(global);
6227 WorkerPrivate::AutoPushEventLoopGlobal::~AutoPushEventLoopGlobal() {
6228 auto data = mWorkerPrivate->mWorkerThreadAccessible.Access();
6229 data->mCurrentEventLoopGlobal = std::move(mOldEventLoopGlobal);
6232 } // namespace dom
6233 } // namespace mozilla