Backed out changeset b462e7b742d8 (bug 1908261) for causing multiple reftest failures...
[gecko.git] / dom / workers / WorkerPrivate.cpp
blob90635de898ec6b58dd7ddec3065f1a3cfecb8a00
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/ContentChild.h"
35 #include "mozilla/dom/Console.h"
36 #include "mozilla/dom/DocGroup.h"
37 #include "mozilla/dom/Document.h"
38 #include "mozilla/dom/DOMTypes.h"
39 #include "mozilla/dom/Event.h"
40 #include "mozilla/dom/Exceptions.h"
41 #include "mozilla/dom/FunctionBinding.h"
42 #include "mozilla/dom/IndexedDatabaseManager.h"
43 #include "mozilla/dom/MessageEvent.h"
44 #include "mozilla/dom/MessageEventBinding.h"
45 #include "mozilla/dom/MessagePort.h"
46 #include "mozilla/dom/MessagePortBinding.h"
47 #include "mozilla/dom/nsCSPContext.h"
48 #include "mozilla/dom/nsCSPUtils.h"
49 #include "mozilla/dom/Performance.h"
50 #include "mozilla/dom/PerformanceStorageWorker.h"
51 #include "mozilla/dom/PromiseDebugging.h"
52 #include "mozilla/dom/ReferrerInfo.h"
53 #include "mozilla/dom/RemoteWorkerChild.h"
54 #include "mozilla/dom/RemoteWorkerNonLifeCycleOpControllerChild.h"
55 #include "mozilla/dom/RemoteWorkerService.h"
56 #include "mozilla/dom/RootedDictionary.h"
57 #include "mozilla/dom/SimpleGlobalObject.h"
58 #include "mozilla/dom/TimeoutHandler.h"
59 #include "mozilla/dom/UseCounterMetrics.h"
60 #include "mozilla/dom/WorkerBinding.h"
61 #include "mozilla/dom/WorkerScope.h"
62 #include "mozilla/dom/WorkerStatus.h"
63 #include "mozilla/dom/WebTaskScheduler.h"
64 #include "mozilla/dom/JSExecutionManager.h"
65 #include "mozilla/dom/WindowContext.h"
66 #include "mozilla/extensions/ExtensionBrowser.h" // extensions::Create{AndDispatchInitWorkerContext,WorkerLoaded,WorkerDestroyed}Runnable
67 #include "mozilla/extensions/WebExtensionPolicy.h"
68 #include "mozilla/glean/DomUseCounterMetrics.h"
69 #include "mozilla/ipc/BackgroundChild.h"
70 #include "mozilla/ipc/PBackgroundChild.h"
71 #include "mozilla/StorageAccess.h"
72 #include "mozilla/StoragePrincipalHelper.h"
73 #include "mozilla/Telemetry.h"
74 #include "mozilla/ThreadEventQueue.h"
75 #include "mozilla/ThreadSafety.h"
76 #include "mozilla/ThrottledEventQueue.h"
77 #include "nsCycleCollector.h"
78 #include "nsGlobalWindowInner.h"
79 #include "nsIDUtils.h"
80 #include "nsNetUtil.h"
81 #include "nsIFile.h"
82 #include "nsIMemoryReporter.h"
83 #include "nsIPermissionManager.h"
84 #include "nsIProtocolHandler.h"
85 #include "nsIScriptError.h"
86 #include "nsIURI.h"
87 #include "nsIURL.h"
88 #include "nsIUUIDGenerator.h"
89 #include "nsPrintfCString.h"
90 #include "nsProxyRelease.h"
91 #include "nsQueryObject.h"
92 #include "nsRFPService.h"
93 #include "nsSandboxFlags.h"
94 #include "nsThreadUtils.h"
95 #include "nsUTF8Utils.h"
97 #include "RuntimeService.h"
98 #include "ScriptLoader.h"
99 #include "mozilla/dom/ServiceWorkerEvents.h"
100 #include "mozilla/dom/ServiceWorkerManager.h"
101 #include "mozilla/net/CookieJarSettings.h"
102 #include "WorkerCSPEventListener.h"
103 #include "WorkerDebugger.h"
104 #include "WorkerDebuggerManager.h"
105 #include "WorkerError.h"
106 #include "WorkerEventTarget.h"
107 #include "WorkerNavigator.h"
108 #include "WorkerRef.h"
109 #include "WorkerRunnable.h"
110 #include "WorkerThread.h"
111 #include "nsContentSecurityManager.h"
113 #include "nsThreadManager.h"
115 #ifdef XP_WIN
116 # undef PostMessage
117 #endif
119 // JS_MaybeGC will run once every second during normal execution.
120 #define PERIODIC_GC_TIMER_DELAY_SEC 1
122 // A shrinking GC will run five seconds after the last event is processed.
123 #define IDLE_GC_TIMER_DELAY_SEC 5
125 // Arbitrary short grace period for the currently running task to finish.
126 // There isn't an advantage for us to immediately interrupt JS in the middle of
127 // execution that might yield soon, especially when there is so much async
128 // variability in the data flow prior to us deciding to trigger the interrupt.
129 #define DEBUGGER_RUNNABLE_INTERRUPT_AFTER_MS 250
131 static mozilla::LazyLogModule sWorkerPrivateLog("WorkerPrivate");
132 static mozilla::LazyLogModule sWorkerTimeoutsLog("WorkerTimeouts");
134 mozilla::LogModule* WorkerLog() { return sWorkerPrivateLog; }
136 mozilla::LogModule* TimeoutsLog() { return sWorkerTimeoutsLog; }
138 #ifdef LOG
139 # undef LOG
140 #endif
141 #ifdef LOGV
142 # undef LOGV
143 #endif
144 #define LOG(log, _args) MOZ_LOG(log, LogLevel::Debug, _args);
145 #define LOGV(args) MOZ_LOG(sWorkerPrivateLog, LogLevel::Verbose, args);
147 namespace mozilla {
149 using namespace ipc;
151 namespace dom {
153 using namespace workerinternals;
155 MOZ_DEFINE_MALLOC_SIZE_OF(JsWorkerMallocSizeOf)
157 namespace {
159 #ifdef DEBUG
161 const nsIID kDEBUGWorkerEventTargetIID = {
162 0xccaba3fa,
163 0x5be2,
164 0x4de2,
165 {0xba, 0x87, 0x3b, 0x3b, 0x5b, 0x1d, 0x5, 0xfb}};
167 #endif
169 template <class T>
170 class UniquePtrComparator {
171 using A = UniquePtr<T>;
172 using B = T*;
174 public:
175 bool Equals(const A& a, const A& b) const {
176 return (a && b) ? (*a == *b) : (!a && !b);
178 bool LessThan(const A& a, const A& b) const {
179 return (a && b) ? (*a < *b) : !!b;
183 template <class T>
184 inline UniquePtrComparator<T> GetUniquePtrComparator(
185 const nsTArray<UniquePtr<T>>&) {
186 return UniquePtrComparator<T>();
189 // This class is used to wrap any runnables that the worker receives via the
190 // nsIEventTarget::Dispatch() method (either from NS_DispatchToCurrentThread or
191 // from the worker's EventTarget).
192 class ExternalRunnableWrapper final : public WorkerThreadRunnable {
193 nsCOMPtr<nsIRunnable> mWrappedRunnable;
195 public:
196 ExternalRunnableWrapper(WorkerPrivate* aWorkerPrivate,
197 nsIRunnable* aWrappedRunnable)
198 : WorkerThreadRunnable("ExternalRunnableWrapper"),
199 mWrappedRunnable(aWrappedRunnable) {
200 MOZ_ASSERT(aWorkerPrivate);
201 MOZ_ASSERT(aWrappedRunnable);
204 NS_INLINE_DECL_REFCOUNTING_INHERITED(ExternalRunnableWrapper,
205 WorkerThreadRunnable)
207 private:
208 ~ExternalRunnableWrapper() = default;
210 virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
211 // Silence bad assertions.
212 return true;
215 virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
216 bool aDispatchResult) override {
217 // Silence bad assertions.
220 virtual bool WorkerRun(JSContext* aCx,
221 WorkerPrivate* aWorkerPrivate) override {
222 nsresult rv = mWrappedRunnable->Run();
223 mWrappedRunnable = nullptr;
224 if (NS_FAILED(rv)) {
225 if (!JS_IsExceptionPending(aCx)) {
226 Throw(aCx, rv);
228 return false;
230 return true;
233 nsresult Cancel() override {
234 nsCOMPtr<nsIDiscardableRunnable> doomed =
235 do_QueryInterface(mWrappedRunnable);
236 if (doomed) {
237 doomed->OnDiscard();
239 mWrappedRunnable = nullptr;
240 return NS_OK;
243 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
244 NS_IMETHOD GetName(nsACString& aName) override {
245 aName.AssignLiteral("ExternalRunnableWrapper(");
246 if (nsCOMPtr<nsINamed> named = do_QueryInterface(mWrappedRunnable)) {
247 nsAutoCString containedName;
248 named->GetName(containedName);
249 aName.Append(containedName);
250 } else {
251 aName.AppendLiteral("?");
253 aName.AppendLiteral(")");
254 return NS_OK;
256 #endif
259 struct WindowAction {
260 nsPIDOMWindowInner* mWindow;
261 bool mDefaultAction;
263 MOZ_IMPLICIT WindowAction(nsPIDOMWindowInner* aWindow)
264 : mWindow(aWindow), mDefaultAction(true) {}
266 bool operator==(const WindowAction& aOther) const {
267 return mWindow == aOther.mWindow;
271 class WorkerFinishedRunnable final : public WorkerControlRunnable {
272 WorkerPrivate* mFinishedWorker;
274 public:
275 WorkerFinishedRunnable(WorkerPrivate* aWorkerPrivate,
276 WorkerPrivate* aFinishedWorker)
277 : WorkerControlRunnable("WorkerFinishedRunnable"),
278 mFinishedWorker(aFinishedWorker) {
279 aFinishedWorker->IncreaseWorkerFinishedRunnableCount();
282 private:
283 virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
284 // Silence bad assertions.
285 return true;
288 virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
289 bool aDispatchResult) override {
290 // Silence bad assertions.
293 virtual bool WorkerRun(JSContext* aCx,
294 WorkerPrivate* aWorkerPrivate) override {
295 // This may block on the main thread.
296 AutoYieldJSThreadExecution yield;
298 mFinishedWorker->DecreaseWorkerFinishedRunnableCount();
300 if (!mFinishedWorker->ProxyReleaseMainThreadObjects()) {
301 NS_WARNING("Failed to dispatch, going to leak!");
304 RuntimeService* runtime = RuntimeService::GetService();
305 NS_ASSERTION(runtime, "This should never be null!");
307 mFinishedWorker->DisableDebugger();
309 runtime->UnregisterWorker(*mFinishedWorker);
311 mFinishedWorker->ClearSelfAndParentEventTargetRef();
312 return true;
316 class TopLevelWorkerFinishedRunnable final : public Runnable {
317 WorkerPrivate* mFinishedWorker;
319 public:
320 explicit TopLevelWorkerFinishedRunnable(WorkerPrivate* aFinishedWorker)
321 : mozilla::Runnable("TopLevelWorkerFinishedRunnable"),
322 mFinishedWorker(aFinishedWorker) {
323 aFinishedWorker->AssertIsOnWorkerThread();
324 aFinishedWorker->IncreaseTopLevelWorkerFinishedRunnableCount();
327 NS_INLINE_DECL_REFCOUNTING_INHERITED(TopLevelWorkerFinishedRunnable, Runnable)
329 private:
330 ~TopLevelWorkerFinishedRunnable() = default;
332 NS_IMETHOD
333 Run() override {
334 AssertIsOnMainThread();
336 mFinishedWorker->DecreaseTopLevelWorkerFinishedRunnableCount();
338 RuntimeService* runtime = RuntimeService::GetService();
339 MOZ_ASSERT(runtime);
341 mFinishedWorker->DisableDebugger();
343 runtime->UnregisterWorker(*mFinishedWorker);
345 if (!mFinishedWorker->ProxyReleaseMainThreadObjects()) {
346 NS_WARNING("Failed to dispatch, going to leak!");
349 mFinishedWorker->ClearSelfAndParentEventTargetRef();
350 return NS_OK;
354 class CompileScriptRunnable final : public WorkerDebuggeeRunnable {
355 nsString mScriptURL;
356 const mozilla::Encoding* mDocumentEncoding;
357 UniquePtr<SerializedStackHolder> mOriginStack;
359 public:
360 explicit CompileScriptRunnable(WorkerPrivate* aWorkerPrivate,
361 UniquePtr<SerializedStackHolder> aOriginStack,
362 const nsAString& aScriptURL,
363 const mozilla::Encoding* aDocumentEncoding)
364 : WorkerDebuggeeRunnable("CompileScriptRunnable"),
365 mScriptURL(aScriptURL),
366 mDocumentEncoding(aDocumentEncoding),
367 mOriginStack(aOriginStack.release()) {}
369 private:
370 // We can't implement PreRun effectively, because at the point when that would
371 // run we have not yet done our load so don't know things like our final
372 // principal and whatnot.
374 virtual bool WorkerRun(JSContext* aCx,
375 WorkerPrivate* aWorkerPrivate) override {
376 aWorkerPrivate->AssertIsOnWorkerThread();
378 WorkerGlobalScope* globalScope =
379 aWorkerPrivate->GetOrCreateGlobalScope(aCx);
380 if (NS_WARN_IF(!globalScope)) {
381 return false;
384 if (NS_WARN_IF(!aWorkerPrivate->EnsureCSPEventListener())) {
385 return false;
388 ErrorResult rv;
389 workerinternals::LoadMainScript(aWorkerPrivate, std::move(mOriginStack),
390 mScriptURL, WorkerScript, rv,
391 mDocumentEncoding);
393 if (aWorkerPrivate->ExtensionAPIAllowed()) {
394 MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
395 RefPtr<Runnable> extWorkerRunnable =
396 extensions::CreateWorkerLoadedRunnable(
397 aWorkerPrivate->ServiceWorkerID(), aWorkerPrivate->GetBaseURI());
398 // Dispatch as a low priority runnable.
399 if (NS_FAILED(aWorkerPrivate->DispatchToMainThreadForMessaging(
400 extWorkerRunnable.forget()))) {
401 NS_WARNING(
402 "Failed to dispatch runnable to notify extensions worker loaded");
406 rv.WouldReportJSException();
407 // Explicitly ignore NS_BINDING_ABORTED on rv. Or more precisely, still
408 // return false and don't SetWorkerScriptExecutedSuccessfully() in that
409 // case, but don't throw anything on aCx. The idea is to not dispatch error
410 // events if our load is canceled with that error code.
411 if (rv.ErrorCodeIs(NS_BINDING_ABORTED)) {
412 rv.SuppressException();
413 return false;
416 // Make sure to propagate exceptions from rv onto aCx, so that they will get
417 // reported after we return. We want to propagate just JS exceptions,
418 // because all the other errors are handled when the script is loaded.
419 // See: https://dom.spec.whatwg.org/#concept-event-fire
420 if (rv.Failed() && !rv.IsJSException()) {
421 WorkerErrorReport::CreateAndDispatchGenericErrorRunnableToParent(
422 aWorkerPrivate);
423 rv.SuppressException();
424 return false;
427 // This is a little dumb, but aCx is in the null realm here because we
428 // set it up that way in our Run(), since we had not created the global at
429 // that point yet. So we need to enter the realm of our global,
430 // because setting a pending exception on aCx involves wrapping into its
431 // current compartment. Luckily we have a global now.
432 JSAutoRealm ar(aCx, globalScope->GetGlobalJSObject());
433 if (rv.MaybeSetPendingException(aCx)) {
434 // In the event of an uncaught exception, the worker should still keep
435 // running (return true) but should not be marked as having executed
436 // successfully (which will cause ServiceWorker installation to fail).
437 // In previous error handling cases in this method, we return false (to
438 // trigger CloseInternal) because the global is not in an operable
439 // state at all.
441 // For ServiceWorkers, this would correspond to the "Run Service Worker"
442 // algorithm returning an "abrupt completion" and _not_ failure.
444 // For DedicatedWorkers and SharedWorkers, this would correspond to the
445 // "run a worker" algorithm disregarding the return value of "run the
446 // classic script"/"run the module script" in step 24:
448 // "If script is a classic script, then run the classic script script.
449 // Otherwise, it is a module script; run the module script script."
450 return true;
453 aWorkerPrivate->SetWorkerScriptExecutedSuccessfully();
454 return true;
457 void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
458 bool aRunResult) override {
459 if (!aRunResult) {
460 aWorkerPrivate->CloseInternal();
462 WorkerThreadRunnable::PostRun(aCx, aWorkerPrivate, aRunResult);
466 class NotifyRunnable final : public WorkerControlRunnable {
467 WorkerStatus mStatus;
469 public:
470 NotifyRunnable(WorkerPrivate* aWorkerPrivate, WorkerStatus aStatus)
471 : WorkerControlRunnable("NotifyRunnable"), mStatus(aStatus) {
472 MOZ_ASSERT(aStatus == Closing || aStatus == Canceling ||
473 aStatus == Killing);
476 private:
477 virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
478 aWorkerPrivate->AssertIsOnParentThread();
479 return true;
482 virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
483 bool aDispatchResult) override {
484 aWorkerPrivate->AssertIsOnParentThread();
487 virtual bool WorkerRun(JSContext* aCx,
488 WorkerPrivate* aWorkerPrivate) override {
489 return aWorkerPrivate->NotifyInternal(mStatus);
492 virtual void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
493 bool aRunResult) override {}
496 class FreezeRunnable final : public WorkerControlRunnable {
497 public:
498 explicit FreezeRunnable(WorkerPrivate* aWorkerPrivate)
499 : WorkerControlRunnable("FreezeRunnable") {}
501 private:
502 virtual bool WorkerRun(JSContext* aCx,
503 WorkerPrivate* aWorkerPrivate) override {
504 return aWorkerPrivate->FreezeInternal();
508 class ThawRunnable final : public WorkerControlRunnable {
509 public:
510 explicit ThawRunnable(WorkerPrivate* aWorkerPrivate)
511 : WorkerControlRunnable("ThawRunnable") {}
513 private:
514 virtual bool WorkerRun(JSContext* aCx,
515 WorkerPrivate* aWorkerPrivate) override {
516 return aWorkerPrivate->ThawInternal();
520 class ChangeBackgroundStateRunnable final : public WorkerControlRunnable {
521 public:
522 ChangeBackgroundStateRunnable() = delete;
523 explicit ChangeBackgroundStateRunnable(WorkerPrivate* aWorkerPrivate) =
524 delete;
525 ChangeBackgroundStateRunnable(WorkerPrivate* aWorkerPrivate,
526 bool aIsBackground)
527 : WorkerControlRunnable("ChangeBackgroundStateRunnable"),
528 mIsBackground(aIsBackground) {}
530 private:
531 bool mIsBackground = false;
532 virtual bool WorkerRun(JSContext* aCx,
533 WorkerPrivate* aWorkerPrivate) override {
534 return aWorkerPrivate->ChangeBackgroundStateInternal(mIsBackground);
538 class PropagateStorageAccessPermissionGrantedRunnable final
539 : public WorkerControlRunnable {
540 public:
541 explicit PropagateStorageAccessPermissionGrantedRunnable(
542 WorkerPrivate* aWorkerPrivate)
543 : WorkerControlRunnable(
544 "PropagateStorageAccessPermissionGrantedRunnable") {}
546 private:
547 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
548 aWorkerPrivate->PropagateStorageAccessPermissionGrantedInternal();
549 return true;
553 class ReportErrorToConsoleRunnable final : public WorkerParentThreadRunnable {
554 public:
555 // aWorkerPrivate is the worker thread we're on (or the main thread, if null)
556 static void Report(WorkerPrivate* aWorkerPrivate, uint32_t aErrorFlags,
557 const nsCString& aCategory,
558 nsContentUtils::PropertiesFile aFile,
559 const nsCString& aMessageName,
560 const nsTArray<nsString>& aParams,
561 const mozilla::SourceLocation& aLocation) {
562 if (aWorkerPrivate) {
563 aWorkerPrivate->AssertIsOnWorkerThread();
564 } else {
565 AssertIsOnMainThread();
568 // Now fire a runnable to do the same on the parent's thread if we can.
569 if (aWorkerPrivate) {
570 RefPtr<ReportErrorToConsoleRunnable> runnable =
571 new ReportErrorToConsoleRunnable(aWorkerPrivate, aErrorFlags,
572 aCategory, aFile, aMessageName,
573 aParams, aLocation);
574 runnable->Dispatch(aWorkerPrivate);
575 return;
578 // Log a warning to the console.
579 nsContentUtils::ReportToConsole(aErrorFlags, aCategory, nullptr, aFile,
580 aMessageName.get(), aParams, aLocation);
583 private:
584 ReportErrorToConsoleRunnable(WorkerPrivate* aWorkerPrivate,
585 uint32_t aErrorFlags, const nsCString& aCategory,
586 nsContentUtils::PropertiesFile aFile,
587 const nsCString& aMessageName,
588 const nsTArray<nsString>& aParams,
589 const mozilla::SourceLocation& aLocation)
590 : WorkerParentThreadRunnable("ReportErrorToConsoleRunnable"),
591 mErrorFlags(aErrorFlags),
592 mCategory(aCategory),
593 mFile(aFile),
594 mMessageName(aMessageName),
595 mParams(aParams.Clone()),
596 mLocation(aLocation) {}
598 virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
599 bool aDispatchResult) override {
600 aWorkerPrivate->AssertIsOnWorkerThread();
602 // Dispatch may fail if the worker was canceled, no need to report that as
603 // an error, so don't call base class PostDispatch.
606 virtual bool WorkerRun(JSContext* aCx,
607 WorkerPrivate* aWorkerPrivate) override {
608 WorkerPrivate* parent = aWorkerPrivate->GetParent();
609 MOZ_ASSERT_IF(!parent, NS_IsMainThread());
610 Report(parent, mErrorFlags, mCategory, mFile, mMessageName, mParams,
611 mLocation);
612 return true;
615 const uint32_t mErrorFlags;
616 const nsCString mCategory;
617 const nsContentUtils::PropertiesFile mFile;
618 const nsCString mMessageName;
619 const nsTArray<nsString> mParams;
620 const mozilla::SourceLocation mLocation;
623 class RunExpiredTimoutsRunnable final : public WorkerThreadRunnable,
624 public nsITimerCallback {
625 public:
626 NS_DECL_ISUPPORTS_INHERITED
628 explicit RunExpiredTimoutsRunnable(WorkerPrivate* aWorkerPrivate)
629 : WorkerThreadRunnable("RunExpiredTimoutsRunnable") {}
631 private:
632 ~RunExpiredTimoutsRunnable() = default;
634 virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
635 // Silence bad assertions.
636 return true;
639 virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
640 bool aDispatchResult) override {
641 // Silence bad assertions.
644 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until worker runnables are generally
645 // MOZ_CAN_RUN_SCRIPT.
646 MOZ_CAN_RUN_SCRIPT_BOUNDARY
647 virtual bool WorkerRun(JSContext* aCx,
648 WorkerPrivate* aWorkerPrivate) override {
649 return aWorkerPrivate->RunExpiredTimeouts(aCx);
652 NS_IMETHOD
653 Notify(nsITimer* aTimer) override { return Run(); }
656 NS_IMPL_ISUPPORTS_INHERITED(RunExpiredTimoutsRunnable, WorkerThreadRunnable,
657 nsITimerCallback)
659 class DebuggerImmediateRunnable final : public WorkerThreadRunnable {
660 RefPtr<dom::Function> mHandler;
662 public:
663 explicit DebuggerImmediateRunnable(WorkerPrivate* aWorkerPrivate,
664 dom::Function& aHandler)
665 : WorkerThreadRunnable("DebuggerImmediateRunnable"),
666 mHandler(&aHandler) {}
668 private:
669 virtual bool IsDebuggerRunnable() const override { return true; }
671 virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
672 // Silence bad assertions.
673 return true;
676 virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
677 bool aDispatchResult) override {
678 // Silence bad assertions.
681 // Make as MOZ_CAN_RUN_SCRIPT_BOUNDARY for calling mHandler->Call();
682 // Since WorkerRunnable::WorkerRun has not to be MOZ_CAN_RUN_SCRIPT, but
683 // DebuggerImmediateRunnable is a special case that must to call the function
684 // defined in the debugger script.
685 MOZ_CAN_RUN_SCRIPT_BOUNDARY
686 virtual bool WorkerRun(JSContext* aCx,
687 WorkerPrivate* aWorkerPrivate) override {
688 JS::Rooted<JS::Value> rval(aCx);
689 IgnoredErrorResult rv;
690 MOZ_KnownLive(mHandler)->Call({}, &rval, rv);
692 return !rv.Failed();
696 // GetJSContext() is safe on the worker thread
697 void PeriodicGCTimerCallback(nsITimer* aTimer,
698 void* aClosure) MOZ_NO_THREAD_SAFETY_ANALYSIS {
699 auto* workerPrivate = static_cast<WorkerPrivate*>(aClosure);
700 MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
701 workerPrivate->AssertIsOnWorkerThread();
702 workerPrivate->GarbageCollectInternal(workerPrivate->GetJSContext(),
703 false /* shrinking */,
704 false /* collect children */);
705 LOG(WorkerLog(), ("Worker %p run periodic GC\n", workerPrivate));
708 void IdleGCTimerCallback(nsITimer* aTimer,
709 void* aClosure) MOZ_NO_THREAD_SAFETY_ANALYSIS {
710 auto* workerPrivate = static_cast<WorkerPrivate*>(aClosure);
711 MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
712 workerPrivate->AssertIsOnWorkerThread();
713 workerPrivate->GarbageCollectInternal(workerPrivate->GetJSContext(),
714 true /* shrinking */,
715 false /* collect children */);
716 LOG(WorkerLog(), ("Worker %p run idle GC\n", workerPrivate));
718 // After running idle GC we can cancel the current timers.
719 workerPrivate->CancelGCTimers();
722 class UpdateContextOptionsRunnable final : public WorkerControlRunnable {
723 JS::ContextOptions mContextOptions;
725 public:
726 UpdateContextOptionsRunnable(WorkerPrivate* aWorkerPrivate,
727 const JS::ContextOptions& aContextOptions)
728 : WorkerControlRunnable("UpdateContextOptionsRunnable"),
729 mContextOptions(aContextOptions) {}
731 private:
732 virtual bool WorkerRun(JSContext* aCx,
733 WorkerPrivate* aWorkerPrivate) override {
734 aWorkerPrivate->UpdateContextOptionsInternal(aCx, mContextOptions);
735 return true;
739 class UpdateLanguagesRunnable final : public WorkerThreadRunnable {
740 nsTArray<nsString> mLanguages;
742 public:
743 UpdateLanguagesRunnable(WorkerPrivate* aWorkerPrivate,
744 const nsTArray<nsString>& aLanguages)
745 : WorkerThreadRunnable("UpdateLanguagesRunnable"),
746 mLanguages(aLanguages.Clone()) {}
748 virtual bool WorkerRun(JSContext* aCx,
749 WorkerPrivate* aWorkerPrivate) override {
750 aWorkerPrivate->UpdateLanguagesInternal(mLanguages);
751 return true;
755 class UpdateJSWorkerMemoryParameterRunnable final
756 : public WorkerControlRunnable {
757 Maybe<uint32_t> mValue;
758 JSGCParamKey mKey;
760 public:
761 UpdateJSWorkerMemoryParameterRunnable(WorkerPrivate* aWorkerPrivate,
762 JSGCParamKey aKey,
763 Maybe<uint32_t> aValue)
764 : WorkerControlRunnable("UpdateJSWorkerMemoryParameterRunnable"),
765 mValue(aValue),
766 mKey(aKey) {}
768 private:
769 virtual bool WorkerRun(JSContext* aCx,
770 WorkerPrivate* aWorkerPrivate) override {
771 aWorkerPrivate->UpdateJSWorkerMemoryParameterInternal(aCx, mKey, mValue);
772 return true;
776 #ifdef JS_GC_ZEAL
777 class UpdateGCZealRunnable final : public WorkerControlRunnable {
778 uint8_t mGCZeal;
779 uint32_t mFrequency;
781 public:
782 UpdateGCZealRunnable(WorkerPrivate* aWorkerPrivate, uint8_t aGCZeal,
783 uint32_t aFrequency)
784 : WorkerControlRunnable("UpdateGCZealRunnable"),
785 mGCZeal(aGCZeal),
786 mFrequency(aFrequency) {}
788 private:
789 virtual bool WorkerRun(JSContext* aCx,
790 WorkerPrivate* aWorkerPrivate) override {
791 aWorkerPrivate->UpdateGCZealInternal(aCx, mGCZeal, mFrequency);
792 return true;
795 #endif
797 class SetLowMemoryStateRunnable final : public WorkerControlRunnable {
798 bool mState;
800 public:
801 SetLowMemoryStateRunnable(WorkerPrivate* aWorkerPrivate, bool aState)
802 : WorkerControlRunnable("SetLowMemoryStateRunnable"), mState(aState) {}
804 private:
805 virtual bool WorkerRun(JSContext* aCx,
806 WorkerPrivate* aWorkerPrivate) override {
807 aWorkerPrivate->SetLowMemoryStateInternal(aCx, mState);
808 return true;
812 class GarbageCollectRunnable final : public WorkerControlRunnable {
813 bool mShrinking;
814 bool mCollectChildren;
816 public:
817 GarbageCollectRunnable(WorkerPrivate* aWorkerPrivate, bool aShrinking,
818 bool aCollectChildren)
819 : WorkerControlRunnable("GarbageCollectRunnable"),
820 mShrinking(aShrinking),
821 mCollectChildren(aCollectChildren) {}
823 private:
824 virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
825 // Silence bad assertions, this can be dispatched from either the main
826 // thread or the timer thread..
827 return true;
830 virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
831 bool aDispatchResult) override {
832 // Silence bad assertions, this can be dispatched from either the main
833 // thread or the timer thread..
836 virtual bool WorkerRun(JSContext* aCx,
837 WorkerPrivate* aWorkerPrivate) override {
838 aWorkerPrivate->GarbageCollectInternal(aCx, mShrinking, mCollectChildren);
839 if (mShrinking) {
840 // Either we've run the idle GC or explicit GC call from the parent,
841 // we can cancel the current timers.
842 aWorkerPrivate->CancelGCTimers();
844 return true;
848 class CycleCollectRunnable final : public WorkerControlRunnable {
849 bool mCollectChildren;
851 public:
852 CycleCollectRunnable(WorkerPrivate* aWorkerPrivate, bool aCollectChildren)
853 : WorkerControlRunnable("CycleCollectRunnable"),
854 mCollectChildren(aCollectChildren) {}
856 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
857 aWorkerPrivate->CycleCollectInternal(mCollectChildren);
858 return true;
862 class OfflineStatusChangeRunnable final : public WorkerThreadRunnable {
863 public:
864 OfflineStatusChangeRunnable(WorkerPrivate* aWorkerPrivate, bool aIsOffline)
865 : WorkerThreadRunnable("OfflineStatusChangeRunnable"),
866 mIsOffline(aIsOffline) {}
868 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
869 aWorkerPrivate->OfflineStatusChangeEventInternal(mIsOffline);
870 return true;
873 private:
874 bool mIsOffline;
877 class MemoryPressureRunnable final : public WorkerControlRunnable {
878 public:
879 explicit MemoryPressureRunnable(WorkerPrivate* aWorkerPrivate)
880 : WorkerControlRunnable("MemoryPressureRunnable") {}
882 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
883 aWorkerPrivate->MemoryPressureInternal();
884 return true;
888 #ifdef DEBUG
889 static bool StartsWithExplicit(nsACString& s) {
890 return StringBeginsWith(s, "explicit/"_ns);
892 #endif
894 PRThread* PRThreadFromThread(nsIThread* aThread) {
895 MOZ_ASSERT(aThread);
897 PRThread* result;
898 MOZ_ALWAYS_SUCCEEDS(aThread->GetPRThread(&result));
899 MOZ_ASSERT(result);
901 return result;
904 // A runnable to cancel the worker from the parent thread when self.close() is
905 // called. This runnable is executed on the parent process in order to cancel
906 // the current runnable. It uses a normal WorkerDebuggeeRunnable in order to be
907 // sure that all the pending WorkerDebuggeeRunnables are executed before this.
908 class CancelingOnParentRunnable final : public WorkerParentDebuggeeRunnable {
909 public:
910 explicit CancelingOnParentRunnable(WorkerPrivate* aWorkerPrivate)
911 : WorkerParentDebuggeeRunnable("CancelingOnParentRunnable") {}
913 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
914 aWorkerPrivate->Cancel();
915 return true;
919 // A runnable to cancel the worker from the parent process.
920 class CancelingWithTimeoutOnParentRunnable final
921 : public WorkerParentControlRunnable {
922 public:
923 explicit CancelingWithTimeoutOnParentRunnable(WorkerPrivate* aWorkerPrivate)
924 : WorkerParentControlRunnable("CancelingWithTimeoutOnParentRunnable") {}
926 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
927 aWorkerPrivate->AssertIsOnParentThread();
928 aWorkerPrivate->StartCancelingTimer();
929 return true;
933 class CancelingTimerCallback final : public nsITimerCallback {
934 public:
935 NS_DECL_ISUPPORTS
937 explicit CancelingTimerCallback(WorkerPrivate* aWorkerPrivate)
938 : mWorkerPrivate(aWorkerPrivate) {}
940 NS_IMETHOD
941 Notify(nsITimer* aTimer) override {
942 mWorkerPrivate->AssertIsOnParentThread();
943 mWorkerPrivate->Cancel();
944 return NS_OK;
947 private:
948 ~CancelingTimerCallback() = default;
950 // Raw pointer here is OK because the timer is canceled during the shutdown
951 // steps.
952 WorkerPrivate* mWorkerPrivate;
955 NS_IMPL_ISUPPORTS(CancelingTimerCallback, nsITimerCallback)
957 // This runnable starts the canceling of a worker after a self.close().
958 class CancelingRunnable final : public Runnable {
959 public:
960 CancelingRunnable() : Runnable("CancelingRunnable") {}
962 NS_IMETHOD
963 Run() override {
964 LOG(WorkerLog(), ("CancelingRunnable::Run [%p]", this));
965 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
966 MOZ_ASSERT(workerPrivate);
967 workerPrivate->AssertIsOnWorkerThread();
969 // Now we can cancel the this worker from the parent process.
970 RefPtr<CancelingOnParentRunnable> r =
971 new CancelingOnParentRunnable(workerPrivate);
972 r->Dispatch(workerPrivate);
974 return NS_OK;
978 } /* anonymous namespace */
980 nsString ComputeWorkerPrivateId() {
981 nsID uuid = nsID::GenerateUUID();
982 return NSID_TrimBracketsUTF16(uuid);
985 class WorkerPrivate::EventTarget final : public nsISerialEventTarget {
986 // This mutex protects mWorkerPrivate and must be acquired *before* the
987 // WorkerPrivate's mutex whenever they must both be held.
988 mozilla::Mutex mMutex;
989 WorkerPrivate* mWorkerPrivate MOZ_GUARDED_BY(mMutex);
990 nsCOMPtr<nsIEventTarget> mNestedEventTarget MOZ_GUARDED_BY(mMutex);
991 bool mDisabled MOZ_GUARDED_BY(mMutex);
992 bool mShutdown MOZ_GUARDED_BY(mMutex);
994 public:
995 EventTarget(WorkerPrivate* aWorkerPrivate, nsIEventTarget* aNestedEventTarget)
996 : mMutex("WorkerPrivate::EventTarget::mMutex"),
997 mWorkerPrivate(aWorkerPrivate),
998 mNestedEventTarget(aNestedEventTarget),
999 mDisabled(false),
1000 mShutdown(false) {
1001 MOZ_ASSERT(aWorkerPrivate);
1002 MOZ_ASSERT(aNestedEventTarget);
1005 void Disable() {
1007 MutexAutoLock lock(mMutex);
1009 // Note, Disable() can be called more than once safely.
1010 mDisabled = true;
1014 void Shutdown() {
1015 nsCOMPtr<nsIEventTarget> nestedEventTarget;
1017 MutexAutoLock lock(mMutex);
1019 mWorkerPrivate = nullptr;
1020 mNestedEventTarget.swap(nestedEventTarget);
1021 MOZ_ASSERT(mDisabled);
1022 mShutdown = true;
1026 RefPtr<nsIEventTarget> GetNestedEventTarget() {
1027 RefPtr<nsIEventTarget> nestedEventTarget = nullptr;
1029 MutexAutoLock lock(mMutex);
1030 if (mWorkerPrivate) {
1031 mWorkerPrivate->AssertIsOnWorkerThread();
1032 nestedEventTarget = mNestedEventTarget.get();
1035 return nestedEventTarget;
1038 NS_DECL_THREADSAFE_ISUPPORTS
1039 NS_DECL_NSIEVENTTARGET_FULL
1041 private:
1042 ~EventTarget() = default;
1045 struct WorkerPrivate::TimeoutInfo {
1046 TimeoutInfo()
1047 : mId(0),
1048 mNestingLevel(0),
1049 mReason(Timeout::Reason::eTimeoutOrInterval),
1050 mIsInterval(false),
1051 mCanceled(false),
1052 mOnChromeWorker(false) {
1053 MOZ_COUNT_CTOR(mozilla::dom::WorkerPrivate::TimeoutInfo);
1056 ~TimeoutInfo() { MOZ_COUNT_DTOR(mozilla::dom::WorkerPrivate::TimeoutInfo); }
1058 bool operator==(const TimeoutInfo& aOther) const {
1059 return mTargetTime == aOther.mTargetTime;
1062 bool operator<(const TimeoutInfo& aOther) const {
1063 return mTargetTime < aOther.mTargetTime;
1066 void AccumulateNestingLevel(const uint32_t& aBaseLevel) {
1067 if (aBaseLevel < StaticPrefs::dom_clamp_timeout_nesting_level_AtStartup()) {
1068 mNestingLevel = aBaseLevel + 1;
1069 return;
1071 mNestingLevel = StaticPrefs::dom_clamp_timeout_nesting_level_AtStartup();
1074 void CalculateTargetTime() {
1075 auto target = mInterval;
1076 // Don't clamp timeout for chrome workers
1077 if (mNestingLevel >=
1078 StaticPrefs::dom_clamp_timeout_nesting_level_AtStartup() &&
1079 !mOnChromeWorker) {
1080 target = TimeDuration::Max(
1081 mInterval,
1082 TimeDuration::FromMilliseconds(StaticPrefs::dom_min_timeout_value()));
1084 mTargetTime = TimeStamp::Now() + target;
1087 RefPtr<TimeoutHandler> mHandler;
1088 mozilla::TimeStamp mTargetTime;
1089 mozilla::TimeDuration mInterval;
1090 int32_t mId;
1091 uint32_t mNestingLevel;
1092 Timeout::Reason mReason;
1093 bool mIsInterval;
1094 bool mCanceled;
1095 bool mOnChromeWorker;
1098 class WorkerJSContextStats final : public JS::RuntimeStats {
1099 const nsCString mRtPath;
1101 public:
1102 explicit WorkerJSContextStats(const nsACString& aRtPath)
1103 : JS::RuntimeStats(JsWorkerMallocSizeOf), mRtPath(aRtPath) {}
1105 ~WorkerJSContextStats() {
1106 for (JS::ZoneStats& stats : zoneStatsVector) {
1107 delete static_cast<xpc::ZoneStatsExtras*>(stats.extra);
1110 for (JS::RealmStats& stats : realmStatsVector) {
1111 delete static_cast<xpc::RealmStatsExtras*>(stats.extra);
1115 const nsCString& Path() const { return mRtPath; }
1117 virtual void initExtraZoneStats(JS::Zone* aZone, JS::ZoneStats* aZoneStats,
1118 const JS::AutoRequireNoGC& nogc) override {
1119 MOZ_ASSERT(!aZoneStats->extra);
1121 // ReportJSRuntimeExplicitTreeStats expects that
1122 // aZoneStats->extra is a xpc::ZoneStatsExtras pointer.
1123 xpc::ZoneStatsExtras* extras = new xpc::ZoneStatsExtras;
1124 extras->pathPrefix = mRtPath;
1125 extras->pathPrefix += nsPrintfCString("zone(0x%p)/", (void*)aZone);
1127 MOZ_ASSERT(StartsWithExplicit(extras->pathPrefix));
1129 aZoneStats->extra = extras;
1132 virtual void initExtraRealmStats(JS::Realm* aRealm,
1133 JS::RealmStats* aRealmStats,
1134 const JS::AutoRequireNoGC& nogc) override {
1135 MOZ_ASSERT(!aRealmStats->extra);
1137 // ReportJSRuntimeExplicitTreeStats expects that
1138 // aRealmStats->extra is a xpc::RealmStatsExtras pointer.
1139 xpc::RealmStatsExtras* extras = new xpc::RealmStatsExtras;
1141 // This is the |jsPathPrefix|. Each worker has exactly one realm.
1142 extras->jsPathPrefix.Assign(mRtPath);
1143 extras->jsPathPrefix +=
1144 nsPrintfCString("zone(0x%p)/", (void*)js::GetRealmZone(aRealm));
1145 extras->jsPathPrefix += "realm(web-worker)/"_ns;
1147 // This should never be used when reporting with workers (hence the "?!").
1148 extras->domPathPrefix.AssignLiteral("explicit/workers/?!/");
1150 MOZ_ASSERT(StartsWithExplicit(extras->jsPathPrefix));
1151 MOZ_ASSERT(StartsWithExplicit(extras->domPathPrefix));
1153 extras->location = nullptr;
1155 aRealmStats->extra = extras;
1159 class WorkerPrivate::MemoryReporter final : public nsIMemoryReporter {
1160 NS_DECL_THREADSAFE_ISUPPORTS
1162 friend class WorkerPrivate;
1164 SharedMutex mMutex;
1165 WorkerPrivate* mWorkerPrivate;
1167 public:
1168 explicit MemoryReporter(WorkerPrivate* aWorkerPrivate)
1169 : mMutex(aWorkerPrivate->mMutex), mWorkerPrivate(aWorkerPrivate) {
1170 aWorkerPrivate->AssertIsOnWorkerThread();
1173 NS_IMETHOD
1174 CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData,
1175 bool aAnonymize) override;
1177 private:
1178 class FinishCollectRunnable;
1180 class CollectReportsRunnable final : public MainThreadWorkerControlRunnable {
1181 RefPtr<FinishCollectRunnable> mFinishCollectRunnable;
1182 const bool mAnonymize;
1184 public:
1185 CollectReportsRunnable(WorkerPrivate* aWorkerPrivate,
1186 nsIHandleReportCallback* aHandleReport,
1187 nsISupports* aHandlerData, bool aAnonymize,
1188 const nsACString& aPath);
1190 private:
1191 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override;
1193 ~CollectReportsRunnable() {
1194 if (NS_IsMainThread()) {
1195 mFinishCollectRunnable->Run();
1196 return;
1199 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
1200 MOZ_ASSERT(workerPrivate);
1201 MOZ_ALWAYS_SUCCEEDS(workerPrivate->DispatchToMainThreadForMessaging(
1202 mFinishCollectRunnable.forget()));
1206 class FinishCollectRunnable final : public Runnable {
1207 nsCOMPtr<nsIHandleReportCallback> mHandleReport;
1208 nsCOMPtr<nsISupports> mHandlerData;
1209 size_t mPerformanceUserEntries;
1210 size_t mPerformanceResourceEntries;
1211 const bool mAnonymize;
1212 bool mSuccess;
1214 public:
1215 WorkerJSContextStats mCxStats;
1217 explicit FinishCollectRunnable(nsIHandleReportCallback* aHandleReport,
1218 nsISupports* aHandlerData, bool aAnonymize,
1219 const nsACString& aPath);
1221 NS_IMETHOD Run() override;
1223 void SetPerformanceSizes(size_t userEntries, size_t resourceEntries) {
1224 mPerformanceUserEntries = userEntries;
1225 mPerformanceResourceEntries = resourceEntries;
1228 void SetSuccess(bool success) { mSuccess = success; }
1230 FinishCollectRunnable(const FinishCollectRunnable&) = delete;
1231 FinishCollectRunnable& operator=(const FinishCollectRunnable&) = delete;
1232 FinishCollectRunnable& operator=(const FinishCollectRunnable&&) = delete;
1234 private:
1235 ~FinishCollectRunnable() {
1236 // mHandleReport and mHandlerData are released on the main thread.
1237 AssertIsOnMainThread();
1241 ~MemoryReporter() = default;
1243 void Disable() {
1244 // Called from WorkerPrivate::DisableMemoryReporter.
1245 mMutex.AssertCurrentThreadOwns();
1247 NS_ASSERTION(mWorkerPrivate, "Disabled more than once!");
1248 mWorkerPrivate = nullptr;
1252 NS_IMPL_ISUPPORTS(WorkerPrivate::MemoryReporter, nsIMemoryReporter)
1254 NS_IMETHODIMP
1255 WorkerPrivate::MemoryReporter::CollectReports(
1256 nsIHandleReportCallback* aHandleReport, nsISupports* aData,
1257 bool aAnonymize) {
1258 AssertIsOnMainThread();
1260 RefPtr<CollectReportsRunnable> runnable;
1263 MutexAutoLock lock(mMutex);
1265 if (!mWorkerPrivate) {
1266 // This will effectively report 0 memory.
1267 nsCOMPtr<nsIMemoryReporterManager> manager =
1268 do_GetService("@mozilla.org/memory-reporter-manager;1");
1269 if (manager) {
1270 manager->EndReport();
1272 return NS_OK;
1275 nsAutoCString path;
1276 path.AppendLiteral("explicit/workers/workers(");
1277 if (aAnonymize && !mWorkerPrivate->Domain().IsEmpty()) {
1278 path.AppendLiteral("<anonymized-domain>)/worker(<anonymized-url>");
1279 } else {
1280 nsAutoCString escapedDomain(mWorkerPrivate->Domain());
1281 if (escapedDomain.IsEmpty()) {
1282 escapedDomain += "chrome";
1283 } else {
1284 escapedDomain.ReplaceChar('/', '\\');
1286 path.Append(escapedDomain);
1287 path.AppendLiteral(")/worker(");
1288 NS_ConvertUTF16toUTF8 escapedURL(mWorkerPrivate->ScriptURL());
1289 escapedURL.ReplaceChar('/', '\\');
1290 path.Append(escapedURL);
1292 path.AppendPrintf(", 0x%p)/", static_cast<void*>(mWorkerPrivate));
1294 runnable = new CollectReportsRunnable(mWorkerPrivate, aHandleReport, aData,
1295 aAnonymize, path);
1298 if (!runnable->Dispatch(mWorkerPrivate)) {
1299 return NS_ERROR_UNEXPECTED;
1302 return NS_OK;
1305 WorkerPrivate::MemoryReporter::CollectReportsRunnable::CollectReportsRunnable(
1306 WorkerPrivate* aWorkerPrivate, nsIHandleReportCallback* aHandleReport,
1307 nsISupports* aHandlerData, bool aAnonymize, const nsACString& aPath)
1308 : MainThreadWorkerControlRunnable("CollectReportsRunnable"),
1309 mFinishCollectRunnable(new FinishCollectRunnable(
1310 aHandleReport, aHandlerData, aAnonymize, aPath)),
1311 mAnonymize(aAnonymize) {}
1313 bool WorkerPrivate::MemoryReporter::CollectReportsRunnable::WorkerRun(
1314 JSContext* aCx, WorkerPrivate* aWorkerPrivate) {
1315 aWorkerPrivate->AssertIsOnWorkerThread();
1317 RefPtr<WorkerGlobalScope> scope = aWorkerPrivate->GlobalScope();
1318 RefPtr<Performance> performance =
1319 scope ? scope->GetPerformanceIfExists() : nullptr;
1320 if (performance) {
1321 size_t userEntries = performance->SizeOfUserEntries(JsWorkerMallocSizeOf);
1322 size_t resourceEntries =
1323 performance->SizeOfResourceEntries(JsWorkerMallocSizeOf);
1324 mFinishCollectRunnable->SetPerformanceSizes(userEntries, resourceEntries);
1327 mFinishCollectRunnable->SetSuccess(aWorkerPrivate->CollectRuntimeStats(
1328 &mFinishCollectRunnable->mCxStats, mAnonymize));
1330 return true;
1333 WorkerPrivate::MemoryReporter::FinishCollectRunnable::FinishCollectRunnable(
1334 nsIHandleReportCallback* aHandleReport, nsISupports* aHandlerData,
1335 bool aAnonymize, const nsACString& aPath)
1336 : mozilla::Runnable(
1337 "dom::WorkerPrivate::MemoryReporter::FinishCollectRunnable"),
1338 mHandleReport(aHandleReport),
1339 mHandlerData(aHandlerData),
1340 mPerformanceUserEntries(0),
1341 mPerformanceResourceEntries(0),
1342 mAnonymize(aAnonymize),
1343 mSuccess(false),
1344 mCxStats(aPath) {}
1346 NS_IMETHODIMP
1347 WorkerPrivate::MemoryReporter::FinishCollectRunnable::Run() {
1348 AssertIsOnMainThread();
1350 nsCOMPtr<nsIMemoryReporterManager> manager =
1351 do_GetService("@mozilla.org/memory-reporter-manager;1");
1353 if (!manager) return NS_OK;
1355 if (mSuccess) {
1356 xpc::ReportJSRuntimeExplicitTreeStats(
1357 mCxStats, mCxStats.Path(), mHandleReport, mHandlerData, mAnonymize);
1359 if (mPerformanceUserEntries) {
1360 nsCString path = mCxStats.Path();
1361 path.AppendLiteral("dom/performance/user-entries");
1362 mHandleReport->Callback(""_ns, path, nsIMemoryReporter::KIND_HEAP,
1363 nsIMemoryReporter::UNITS_BYTES,
1364 static_cast<int64_t>(mPerformanceUserEntries),
1365 "Memory used for performance user entries."_ns,
1366 mHandlerData);
1369 if (mPerformanceResourceEntries) {
1370 nsCString path = mCxStats.Path();
1371 path.AppendLiteral("dom/performance/resource-entries");
1372 mHandleReport->Callback(
1373 ""_ns, path, nsIMemoryReporter::KIND_HEAP,
1374 nsIMemoryReporter::UNITS_BYTES,
1375 static_cast<int64_t>(mPerformanceResourceEntries),
1376 "Memory used for performance resource entries."_ns, mHandlerData);
1380 manager->EndReport();
1382 return NS_OK;
1385 WorkerPrivate::SyncLoopInfo::SyncLoopInfo(EventTarget* aEventTarget)
1386 : mEventTarget(aEventTarget),
1387 mResult(NS_ERROR_FAILURE),
1388 mCompleted(false)
1389 #ifdef DEBUG
1391 mHasRun(false)
1392 #endif
1396 Document* WorkerPrivate::GetDocument() const {
1397 AssertIsOnMainThread();
1398 if (nsPIDOMWindowInner* window = GetAncestorWindow()) {
1399 return window->GetExtantDoc();
1401 // couldn't query a document, give up and return nullptr
1402 return nullptr;
1405 nsPIDOMWindowInner* WorkerPrivate::GetAncestorWindow() const {
1406 AssertIsOnMainThread();
1408 // We should query the window from the top level worker in case of a nested
1409 // worker, as only the top level one can have a window.
1410 WorkerPrivate* top = GetTopLevelWorker();
1411 return top->GetWindow();
1414 class EvictFromBFCacheRunnable final : public WorkerProxyToMainThreadRunnable {
1415 public:
1416 void RunOnMainThread(WorkerPrivate* aWorkerPrivate) override {
1417 MOZ_ASSERT(aWorkerPrivate);
1418 AssertIsOnMainThread();
1419 if (nsCOMPtr<nsPIDOMWindowInner> win =
1420 aWorkerPrivate->GetAncestorWindow()) {
1421 win->RemoveFromBFCacheSync();
1425 void RunBackOnWorkerThreadForCleanup(WorkerPrivate* aWorkerPrivate) override {
1426 MOZ_ASSERT(aWorkerPrivate);
1427 aWorkerPrivate->AssertIsOnWorkerThread();
1431 void WorkerPrivate::EvictFromBFCache() {
1432 AssertIsOnWorkerThread();
1433 RefPtr<EvictFromBFCacheRunnable> runnable = new EvictFromBFCacheRunnable();
1434 runnable->Dispatch(this);
1437 void WorkerPrivate::SetCsp(nsIContentSecurityPolicy* aCSP) {
1438 AssertIsOnMainThread();
1439 if (!aCSP) {
1440 return;
1442 aCSP->EnsureEventTarget(mMainThreadEventTarget);
1444 mLoadInfo.mCSP = aCSP;
1445 mLoadInfo.mCSPInfo = MakeUnique<CSPInfo>();
1446 nsresult rv = CSPToCSPInfo(mLoadInfo.mCSP, mLoadInfo.mCSPInfo.get());
1447 if (NS_WARN_IF(NS_FAILED(rv))) {
1448 return;
1452 nsresult WorkerPrivate::SetCSPFromHeaderValues(
1453 const nsACString& aCSPHeaderValue,
1454 const nsACString& aCSPReportOnlyHeaderValue) {
1455 AssertIsOnMainThread();
1456 MOZ_DIAGNOSTIC_ASSERT(!mLoadInfo.mCSP);
1458 NS_ConvertASCIItoUTF16 cspHeaderValue(aCSPHeaderValue);
1459 NS_ConvertASCIItoUTF16 cspROHeaderValue(aCSPReportOnlyHeaderValue);
1461 nsresult rv;
1462 nsCOMPtr<nsIContentSecurityPolicy> csp = new nsCSPContext();
1464 // First, we try to query the URI from the Principal, but
1465 // in case selfURI remains empty (e.g in case the Principal
1466 // is a SystemPrincipal) then we fall back and use the
1467 // base URI as selfURI for CSP.
1468 nsCOMPtr<nsIURI> selfURI;
1469 // Its not recommended to use the BasePrincipal to get the URI
1470 // but in this case we need to make an exception
1471 auto* basePrin = BasePrincipal::Cast(mLoadInfo.mPrincipal);
1472 if (basePrin) {
1473 basePrin->GetURI(getter_AddRefs(selfURI));
1475 if (!selfURI) {
1476 selfURI = mLoadInfo.mBaseURI;
1478 MOZ_ASSERT(selfURI, "need a self URI for CSP");
1480 rv = csp->SetRequestContextWithPrincipal(mLoadInfo.mPrincipal, selfURI, ""_ns,
1482 NS_ENSURE_SUCCESS(rv, rv);
1484 csp->EnsureEventTarget(mMainThreadEventTarget);
1486 // If there's a CSP header, apply it.
1487 if (!cspHeaderValue.IsEmpty()) {
1488 rv = CSP_AppendCSPFromHeader(csp, cspHeaderValue, false);
1489 NS_ENSURE_SUCCESS(rv, rv);
1491 // If there's a report-only CSP header, apply it.
1492 if (!cspROHeaderValue.IsEmpty()) {
1493 rv = CSP_AppendCSPFromHeader(csp, cspROHeaderValue, true);
1494 NS_ENSURE_SUCCESS(rv, rv);
1497 RefPtr<extensions::WebExtensionPolicy> addonPolicy;
1499 if (basePrin) {
1500 addonPolicy = basePrin->AddonPolicy();
1503 // For extension workers there aren't any csp header values,
1504 // instead it will inherit the Extension CSP.
1505 if (addonPolicy) {
1506 csp->AppendPolicy(addonPolicy->BaseCSP(), false, false);
1507 csp->AppendPolicy(addonPolicy->ExtensionPageCSP(), false, false);
1510 mLoadInfo.mCSP = csp;
1512 // Set evalAllowed, default value is set in GetAllowsEval
1513 bool evalAllowed = false;
1514 bool reportEvalViolations = false;
1515 rv = csp->GetAllowsEval(&reportEvalViolations, &evalAllowed);
1516 NS_ENSURE_SUCCESS(rv, rv);
1518 mLoadInfo.mEvalAllowed = evalAllowed;
1519 mLoadInfo.mReportEvalCSPViolations = reportEvalViolations;
1521 // Set wasmEvalAllowed
1522 bool wasmEvalAllowed = false;
1523 bool reportWasmEvalViolations = false;
1524 rv = csp->GetAllowsWasmEval(&reportWasmEvalViolations, &wasmEvalAllowed);
1525 NS_ENSURE_SUCCESS(rv, rv);
1527 // As for nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction,
1528 // for MV2 extensions we have to allow wasm by default and report violations
1529 // for historical reasons.
1530 // TODO bug 1770909: remove this exception.
1531 if (!wasmEvalAllowed && addonPolicy && addonPolicy->ManifestVersion() == 2) {
1532 wasmEvalAllowed = true;
1533 reportWasmEvalViolations = true;
1536 mLoadInfo.mWasmEvalAllowed = wasmEvalAllowed;
1537 mLoadInfo.mReportWasmEvalCSPViolations = reportWasmEvalViolations;
1539 mLoadInfo.mCSPInfo = MakeUnique<CSPInfo>();
1540 rv = CSPToCSPInfo(csp, mLoadInfo.mCSPInfo.get());
1541 if (NS_WARN_IF(NS_FAILED(rv))) {
1542 return rv;
1544 return NS_OK;
1547 bool WorkerPrivate::IsFrozenForWorkerThread() const {
1548 auto data = mWorkerThreadAccessible.Access();
1549 return data->mFrozen;
1552 bool WorkerPrivate::IsFrozen() const {
1553 AssertIsOnParentThread();
1554 return mParentFrozen;
1557 void WorkerPrivate::StoreCSPOnClient() {
1558 auto data = mWorkerThreadAccessible.Access();
1559 MOZ_ASSERT(data->mScope);
1560 if (mLoadInfo.mCSPInfo) {
1561 data->mScope->MutableClientSourceRef().SetCspInfo(*mLoadInfo.mCSPInfo);
1565 void WorkerPrivate::UpdateReferrerInfoFromHeader(
1566 const nsACString& aReferrerPolicyHeaderValue) {
1567 NS_ConvertUTF8toUTF16 headerValue(aReferrerPolicyHeaderValue);
1569 if (headerValue.IsEmpty()) {
1570 return;
1573 ReferrerPolicy policy =
1574 ReferrerInfo::ReferrerPolicyFromHeaderString(headerValue);
1575 if (policy == ReferrerPolicy::_empty) {
1576 return;
1579 nsCOMPtr<nsIReferrerInfo> referrerInfo =
1580 static_cast<ReferrerInfo*>(GetReferrerInfo())->CloneWithNewPolicy(policy);
1581 SetReferrerInfo(referrerInfo);
1584 void WorkerPrivate::Traverse(nsCycleCollectionTraversalCallback& aCb) {
1585 AssertIsOnParentThread();
1587 // The WorkerPrivate::mParentEventTargetRef has a reference to the exposed
1588 // Worker object, which is really held by the worker thread. We traverse this
1589 // reference if and only if all main thread event queues are empty, no
1590 // shutdown tasks, no StrongWorkerRefs, no child workers, no timeouts, no
1591 // blocking background actors, and we have not released the main thread
1592 // reference. We do not unlink it. This allows the CC to break cycles
1593 // involving the Worker and begin shutting it down (which does happen in
1594 // unlink) but ensures that the WorkerPrivate won't be deleted before we're
1595 // done shutting down the thread.
1596 if (IsEligibleForCC() && !mMainThreadObjectsForgotten) {
1597 nsCycleCollectionTraversalCallback& cb = aCb;
1598 WorkerPrivate* tmp = this;
1599 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParentEventTargetRef);
1603 nsresult WorkerPrivate::Dispatch(already_AddRefed<WorkerRunnable> aRunnable,
1604 nsIEventTarget* aSyncLoopTarget) {
1605 // May be called on any thread!
1606 RefPtr<WorkerRunnable> runnable(aRunnable);
1608 LOGV(("WorkerPrivate::Dispatch [%p] runnable %p", this, runnable.get()));
1609 if (!aSyncLoopTarget) {
1610 // Dispatch control runnable
1611 if (runnable->IsControlRunnable()) {
1612 return DispatchControlRunnable(runnable.forget());
1615 // Dispatch debugger runnable
1616 if (runnable->IsDebuggerRunnable()) {
1617 return DispatchDebuggerRunnable(runnable.forget());
1620 MutexAutoLock lock(mMutex);
1621 return DispatchLockHeld(runnable.forget(), aSyncLoopTarget, lock);
1624 nsresult WorkerPrivate::DispatchToParent(
1625 already_AddRefed<WorkerRunnable> aRunnable) {
1626 RefPtr<WorkerRunnable> runnable(aRunnable);
1628 LOGV(("WorkerPrivate::DispatchToParent [%p] runnable %p", this,
1629 runnable.get()));
1631 WorkerPrivate* parent = GetParent();
1632 // Dispatch to parent worker
1633 if (parent) {
1634 if (runnable->IsControlRunnable()) {
1635 return parent->DispatchControlRunnable(runnable.forget());
1637 return parent->Dispatch(runnable.forget());
1640 // Dispatch to main thread
1641 if (runnable->IsDebuggeeRunnable()) {
1642 RefPtr<WorkerParentDebuggeeRunnable> debuggeeRunnable =
1643 runnable.forget().downcast<WorkerParentDebuggeeRunnable>();
1644 return DispatchDebuggeeToMainThread(debuggeeRunnable.forget(),
1645 NS_DISPATCH_NORMAL);
1647 return DispatchToMainThread(runnable.forget());
1650 nsresult WorkerPrivate::DispatchLockHeld(
1651 already_AddRefed<WorkerRunnable> aRunnable, nsIEventTarget* aSyncLoopTarget,
1652 const MutexAutoLock& aProofOfLock) {
1653 // May be called on any thread!
1654 RefPtr<WorkerRunnable> runnable(aRunnable);
1655 LOGV(("WorkerPrivate::DispatchLockHeld [%p] runnable: %p", this,
1656 runnable.get()));
1658 MOZ_ASSERT_IF(aSyncLoopTarget, mThread);
1660 // Dispatch normal worker runnable
1661 if (mStatus == Dead || (!aSyncLoopTarget && ParentStatus() > Canceling)) {
1662 NS_WARNING(
1663 "A runnable was posted to a worker that is already shutting "
1664 "down!");
1665 return NS_ERROR_UNEXPECTED;
1668 if (runnable->IsDebuggeeRunnable() && !mDebuggerReady) {
1669 MOZ_RELEASE_ASSERT(!aSyncLoopTarget);
1670 mDelayedDebuggeeRunnables.AppendElement(runnable);
1671 return NS_OK;
1674 if (!mThread) {
1675 if (ParentStatus() == Pending || mStatus == Pending) {
1676 LOGV(
1677 ("WorkerPrivate::DispatchLockHeld [%p] runnable %p is queued in "
1678 "mPreStartRunnables",
1679 this, runnable.get()));
1680 RefPtr<WorkerThreadRunnable> workerThreadRunnable =
1681 static_cast<WorkerThreadRunnable*>(runnable.get());
1682 mPreStartRunnables.AppendElement(workerThreadRunnable);
1683 return NS_OK;
1686 NS_WARNING(
1687 "Using a worker event target after the thread has already"
1688 "been released!");
1689 return NS_ERROR_UNEXPECTED;
1692 nsresult rv;
1693 if (aSyncLoopTarget) {
1694 LOGV(
1695 ("WorkerPrivate::DispatchLockHeld [%p] runnable %p dispatch to a "
1696 "SyncLoop(%p)",
1697 this, runnable.get(), aSyncLoopTarget));
1698 rv = aSyncLoopTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
1699 } else {
1700 // If mStatus is Pending, the WorkerPrivate initialization still can fail.
1701 // Append this WorkerThreadRunnable to WorkerPrivate::mPreStartRunnables,
1702 // such that this WorkerThreadRunnable can get the correct value of
1703 // mCleanPreStartDispatching in WorkerPrivate::RunLoopNeverRan().
1704 if (mStatus == Pending) {
1705 LOGV(
1706 ("WorkerPrivate::DispatchLockHeld [%p] runnable %p is append in "
1707 "mPreStartRunnables",
1708 this, runnable.get()));
1709 RefPtr<WorkerThreadRunnable> workerThreadRunnable =
1710 static_cast<WorkerThreadRunnable*>(runnable.get());
1711 mPreStartRunnables.AppendElement(workerThreadRunnable);
1714 // WorkerDebuggeeRunnables don't need any special treatment here. True,
1715 // they should not be delivered to a frozen worker. But frozen workers
1716 // aren't drawing from the thread's main event queue anyway, only from
1717 // mControlQueue.
1718 LOGV(
1719 ("WorkerPrivate::DispatchLockHeld [%p] runnable %p dispatch to the "
1720 "main event queue",
1721 this, runnable.get()));
1722 rv = mThread->DispatchAnyThread(WorkerThreadFriendKey(), runnable.forget());
1725 if (NS_WARN_IF(NS_FAILED(rv))) {
1726 LOGV(("WorkerPrivate::Dispatch Failed [%p]", this));
1727 return rv;
1730 mCondVar.Notify();
1731 return NS_OK;
1734 void WorkerPrivate::EnableDebugger() {
1735 AssertIsOnParentThread();
1737 if (NS_FAILED(RegisterWorkerDebugger(this))) {
1738 NS_WARNING("Failed to register worker debugger!");
1739 return;
1743 void WorkerPrivate::DisableDebugger() {
1744 AssertIsOnParentThread();
1746 // RegisterDebuggerMainThreadRunnable might be dispatched but not executed.
1747 // Wait for its execution before unregistraion.
1748 if (!NS_IsMainThread()) {
1749 WaitForIsDebuggerRegistered(true);
1752 if (NS_FAILED(UnregisterWorkerDebugger(this))) {
1753 NS_WARNING("Failed to unregister worker debugger!");
1757 nsresult WorkerPrivate::DispatchControlRunnable(
1758 already_AddRefed<WorkerRunnable> aWorkerRunnable) {
1759 // May be called on any thread!
1760 RefPtr<WorkerRunnable> runnable(aWorkerRunnable);
1761 MOZ_ASSERT_DEBUG_OR_FUZZING(runnable && runnable->IsControlRunnable());
1763 LOG(WorkerLog(), ("WorkerPrivate::DispatchControlRunnable [%p] runnable %p",
1764 this, runnable.get()));
1767 MutexAutoLock lock(mMutex);
1769 if (mStatus == Dead) {
1770 return NS_ERROR_UNEXPECTED;
1773 // Transfer ownership to the control queue.
1774 mControlQueue.Push(runnable.forget().take());
1776 if (JSContext* cx = mJSContext) {
1777 MOZ_ASSERT(mThread);
1778 JS_RequestInterruptCallback(cx);
1781 mCondVar.Notify();
1784 return NS_OK;
1787 void DebuggerInterruptTimerCallback(nsITimer* aTimer, void* aClosure)
1788 MOZ_NO_THREAD_SAFETY_ANALYSIS {
1789 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
1790 MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
1791 workerPrivate->DebuggerInterruptRequest();
1794 nsresult WorkerPrivate::DispatchDebuggerRunnable(
1795 already_AddRefed<WorkerRunnable> aDebuggerRunnable) {
1796 // May be called on any thread!
1798 RefPtr<WorkerRunnable> runnable(aDebuggerRunnable);
1800 MOZ_ASSERT(runnable);
1802 MutexAutoLock lock(mMutex);
1803 if (!mDebuggerInterruptTimer) {
1804 // There is no timer, so we need to create one. For locking discipline
1805 // purposes we can't manipulate the timer while our mutex is held so
1806 // drop the mutex while we build and configure the timer. Only this
1807 // function here on the main thread will create a timer, so we're not
1808 // racing anyone to create or assign the timer.
1809 nsCOMPtr<nsITimer> timer;
1811 MutexAutoUnlock unlock(mMutex);
1812 timer = NS_NewTimer();
1813 MOZ_ALWAYS_SUCCEEDS(timer->SetTarget(mWorkerControlEventTarget));
1815 // Whenever an event is scheduled on the WorkerControlEventTarget an
1816 // interrupt is automatically requested which causes us to yield JS
1817 // execution and the next JS execution in the queue to execute. This
1818 // allows for simple code reuse of the existing interrupt callback code
1819 // used for control events.
1820 MOZ_ALWAYS_SUCCEEDS(timer->InitWithNamedFuncCallback(
1821 DebuggerInterruptTimerCallback, nullptr,
1822 DEBUGGER_RUNNABLE_INTERRUPT_AFTER_MS, nsITimer::TYPE_ONE_SHOT,
1823 "dom:DebuggerInterruptTimer"));
1826 // okay, we have our mutex back now, put the timer in place.
1827 mDebuggerInterruptTimer.swap(timer);
1830 if (mStatus == Dead) {
1831 NS_WARNING(
1832 "A debugger runnable was posted to a worker that is already "
1833 "shutting down!");
1834 return NS_ERROR_UNEXPECTED;
1837 // Transfer ownership to the debugger queue.
1838 mDebuggerQueue.Push(runnable.forget().take());
1840 mCondVar.Notify();
1842 return NS_OK;
1845 void WorkerPrivate::DebuggerInterruptRequest() {
1846 AssertIsOnWorkerThread();
1848 auto data = mWorkerThreadAccessible.Access();
1849 data->mDebuggerInterruptRequested = true;
1852 already_AddRefed<WorkerRunnable> WorkerPrivate::MaybeWrapAsWorkerRunnable(
1853 already_AddRefed<nsIRunnable> aRunnable) {
1854 // May be called on any thread!
1856 nsCOMPtr<nsIRunnable> runnable(aRunnable);
1857 MOZ_ASSERT(runnable);
1859 LOGV(("WorkerPrivate::MaybeWrapAsWorkerRunnable [%p] runnable: %p", this,
1860 runnable.get()));
1862 RefPtr<WorkerRunnable> workerRunnable =
1863 WorkerRunnable::FromRunnable(runnable);
1864 if (workerRunnable) {
1865 return workerRunnable.forget();
1868 workerRunnable = new ExternalRunnableWrapper(this, runnable);
1869 return workerRunnable.forget();
1872 bool WorkerPrivate::Start() {
1873 // May be called on any thread!
1874 LOG(WorkerLog(), ("WorkerPrivate::Start [%p]", this));
1876 MutexAutoLock lock(mMutex);
1877 NS_ASSERTION(mParentStatus != Running, "How can this be?!");
1879 if (mParentStatus == Pending) {
1880 mParentStatus = Running;
1881 return true;
1885 return false;
1888 // aCx is null when called from the finalizer
1889 bool WorkerPrivate::Notify(WorkerStatus aStatus) {
1890 AssertIsOnParentThread();
1891 // This method is only called for Canceling or later.
1892 MOZ_DIAGNOSTIC_ASSERT(aStatus >= Canceling);
1894 bool pending;
1896 MutexAutoLock lock(mMutex);
1898 if (mParentStatus >= aStatus) {
1899 return true;
1902 pending = mParentStatus == Pending;
1903 mParentStatus = aStatus;
1906 if (mCancellationCallback) {
1907 mCancellationCallback(!pending);
1908 mCancellationCallback = nullptr;
1911 mParentRef->DropWorkerPrivate();
1913 if (pending) {
1914 #ifdef DEBUG
1916 // Fake a thread here just so that our assertions don't go off for no
1917 // reason.
1918 nsIThread* currentThread = NS_GetCurrentThread();
1919 MOZ_ASSERT(currentThread);
1921 MOZ_ASSERT(!mPRThread);
1922 mPRThread = PRThreadFromThread(currentThread);
1923 MOZ_ASSERT(mPRThread);
1925 #endif
1927 // Worker never got a chance to run, go ahead and delete it.
1928 ScheduleDeletion(WorkerPrivate::WorkerNeverRan);
1929 return true;
1932 // No Canceling timeout is needed.
1933 if (mCancelingTimer) {
1934 mCancelingTimer->Cancel();
1935 mCancelingTimer = nullptr;
1938 // The NotifyRunnable kicks off a series of events that need the
1939 // CancelingOnParentRunnable to be executed always.
1940 // Note that we already advanced mParentStatus above and we check that
1941 // status in all other (asynchronous) call sites of SetIsPaused.
1942 if (!mParent) {
1943 MOZ_ALWAYS_SUCCEEDS(mMainThreadDebuggeeEventTarget->SetIsPaused(false));
1946 RefPtr<NotifyRunnable> runnable = new NotifyRunnable(this, aStatus);
1947 return runnable->Dispatch(this);
1950 bool WorkerPrivate::Freeze(const nsPIDOMWindowInner* aWindow) {
1951 AssertIsOnParentThread();
1953 mParentFrozen = true;
1955 bool isCanceling = false;
1957 MutexAutoLock lock(mMutex);
1959 isCanceling = mParentStatus >= Canceling;
1962 // WorkerDebuggeeRunnables sent from a worker to content must not be
1963 // delivered while the worker is frozen.
1965 // Since a top-level worker and all its children share the same
1966 // mMainThreadDebuggeeEventTarget, it's sufficient to do this only in the
1967 // top-level worker.
1968 if (aWindow) {
1969 // This is called from WorkerPrivate construction, and We may not have
1970 // allocated mMainThreadDebuggeeEventTarget yet.
1971 if (mMainThreadDebuggeeEventTarget) {
1972 // Pausing a ThrottledEventQueue is infallible.
1973 MOZ_ALWAYS_SUCCEEDS(
1974 mMainThreadDebuggeeEventTarget->SetIsPaused(!isCanceling));
1978 if (isCanceling) {
1979 return true;
1982 DisableDebugger();
1984 RefPtr<FreezeRunnable> runnable = new FreezeRunnable(this);
1985 return runnable->Dispatch(this);
1988 bool WorkerPrivate::Thaw(const nsPIDOMWindowInner* aWindow) {
1989 AssertIsOnParentThread();
1990 MOZ_ASSERT(mParentFrozen);
1992 mParentFrozen = false;
1995 bool isCanceling = false;
1998 MutexAutoLock lock(mMutex);
2000 isCanceling = mParentStatus >= Canceling;
2003 // Delivery of WorkerDebuggeeRunnables to the window may resume.
2005 // Since a top-level worker and all its children share the same
2006 // mMainThreadDebuggeeEventTarget, it's sufficient to do this only in the
2007 // top-level worker.
2008 if (aWindow) {
2009 // Since the worker is no longer frozen, only a paused parent window
2010 // should require the queue to remain paused.
2012 // This can only fail if the ThrottledEventQueue cannot dispatch its
2013 // executor to the main thread, in which case the main thread was never
2014 // going to draw runnables from it anyway, so the failure doesn't matter.
2015 Unused << mMainThreadDebuggeeEventTarget->SetIsPaused(
2016 IsParentWindowPaused() && !isCanceling);
2019 if (isCanceling) {
2020 return true;
2024 EnableDebugger();
2026 RefPtr<ThawRunnable> runnable = new ThawRunnable(this);
2027 return runnable->Dispatch(this);
2030 void WorkerPrivate::ParentWindowPaused() {
2031 AssertIsOnMainThread();
2032 MOZ_ASSERT(!mParentWindowPaused);
2033 mParentWindowPaused = true;
2035 // This is called from WorkerPrivate construction, and we may not have
2036 // allocated mMainThreadDebuggeeEventTarget yet.
2037 if (mMainThreadDebuggeeEventTarget) {
2038 bool isCanceling = false;
2041 MutexAutoLock lock(mMutex);
2043 isCanceling = mParentStatus >= Canceling;
2046 // If we are already canceling we might wait for CancelingOnParentRunnable
2047 // to be executed, so do not pause.
2048 MOZ_ALWAYS_SUCCEEDS(
2049 mMainThreadDebuggeeEventTarget->SetIsPaused(!isCanceling));
2053 void WorkerPrivate::ParentWindowResumed() {
2054 AssertIsOnMainThread();
2056 MOZ_ASSERT(mParentWindowPaused);
2057 mParentWindowPaused = false;
2059 bool isCanceling = false;
2061 MutexAutoLock lock(mMutex);
2063 isCanceling = mParentStatus >= Canceling;
2066 // Since the window is no longer paused, the queue should only remain paused
2067 // if the worker is frozen.
2069 // This can only fail if the ThrottledEventQueue cannot dispatch its executor
2070 // to the main thread, in which case the main thread was never going to draw
2071 // runnables from it anyway, so the failure doesn't matter.
2072 Unused << mMainThreadDebuggeeEventTarget->SetIsPaused(IsFrozen() &&
2073 !isCanceling);
2076 void WorkerPrivate::PropagateStorageAccessPermissionGranted() {
2077 AssertIsOnParentThread();
2080 MutexAutoLock lock(mMutex);
2082 if (mParentStatus >= Canceling) {
2083 return;
2087 RefPtr<PropagateStorageAccessPermissionGrantedRunnable> runnable =
2088 new PropagateStorageAccessPermissionGrantedRunnable(this);
2089 Unused << NS_WARN_IF(!runnable->Dispatch(this));
2092 void WorkerPrivate::NotifyStorageKeyUsed() {
2093 AssertIsOnWorkerThread();
2095 // Only notify once per global.
2096 if (hasNotifiedStorageKeyUsed) {
2097 return;
2099 hasNotifiedStorageKeyUsed = true;
2101 // Notify about storage access on the main thread.
2102 RefPtr<StrongWorkerRef> strongRef =
2103 StrongWorkerRef::Create(this, "WorkerPrivate::NotifyStorageKeyUsed");
2104 if (!strongRef) {
2105 return;
2107 RefPtr<ThreadSafeWorkerRef> ref = new ThreadSafeWorkerRef(strongRef);
2108 DispatchToMainThread(NS_NewRunnableFunction(
2109 "WorkerPrivate::NotifyStorageKeyUsed", [ref = std::move(ref)] {
2110 nsGlobalWindowInner* window =
2111 nsGlobalWindowInner::Cast(ref->Private()->GetAncestorWindow());
2112 if (window) {
2113 window->MaybeNotifyStorageKeyUsed();
2115 }));
2118 bool WorkerPrivate::Close() {
2119 mMutex.AssertCurrentThreadOwns();
2120 if (mParentStatus < Closing) {
2121 mParentStatus = Closing;
2124 return true;
2127 bool WorkerPrivate::ProxyReleaseMainThreadObjects() {
2128 AssertIsOnParentThread();
2129 MOZ_ASSERT(!mMainThreadObjectsForgotten);
2131 nsCOMPtr<nsILoadGroup> loadGroupToCancel;
2132 // If we're not overriden, then do nothing here. Let the load group get
2133 // handled in ForgetMainThreadObjects().
2134 if (mLoadInfo.mInterfaceRequestor) {
2135 mLoadInfo.mLoadGroup.swap(loadGroupToCancel);
2138 bool result = mLoadInfo.ProxyReleaseMainThreadObjects(
2139 this, std::move(loadGroupToCancel));
2141 mMainThreadObjectsForgotten = true;
2143 return result;
2146 void WorkerPrivate::UpdateContextOptions(
2147 const JS::ContextOptions& aContextOptions) {
2148 AssertIsOnParentThread();
2151 MutexAutoLock lock(mMutex);
2152 mJSSettings.contextOptions = aContextOptions;
2155 RefPtr<UpdateContextOptionsRunnable> runnable =
2156 new UpdateContextOptionsRunnable(this, aContextOptions);
2157 if (!runnable->Dispatch(this)) {
2158 NS_WARNING("Failed to update worker context options!");
2162 void WorkerPrivate::UpdateLanguages(const nsTArray<nsString>& aLanguages) {
2163 AssertIsOnParentThread();
2165 RefPtr<UpdateLanguagesRunnable> runnable =
2166 new UpdateLanguagesRunnable(this, aLanguages);
2167 if (!runnable->Dispatch(this)) {
2168 NS_WARNING("Failed to update worker languages!");
2172 void WorkerPrivate::UpdateJSWorkerMemoryParameter(JSGCParamKey aKey,
2173 Maybe<uint32_t> aValue) {
2174 AssertIsOnParentThread();
2176 bool changed = false;
2179 MutexAutoLock lock(mMutex);
2180 changed = mJSSettings.ApplyGCSetting(aKey, aValue);
2183 if (changed) {
2184 RefPtr<UpdateJSWorkerMemoryParameterRunnable> runnable =
2185 new UpdateJSWorkerMemoryParameterRunnable(this, aKey, aValue);
2186 if (!runnable->Dispatch(this)) {
2187 NS_WARNING("Failed to update memory parameter!");
2192 #ifdef JS_GC_ZEAL
2193 void WorkerPrivate::UpdateGCZeal(uint8_t aGCZeal, uint32_t aFrequency) {
2194 AssertIsOnParentThread();
2197 MutexAutoLock lock(mMutex);
2198 mJSSettings.gcZeal = aGCZeal;
2199 mJSSettings.gcZealFrequency = aFrequency;
2202 RefPtr<UpdateGCZealRunnable> runnable =
2203 new UpdateGCZealRunnable(this, aGCZeal, aFrequency);
2204 if (!runnable->Dispatch(this)) {
2205 NS_WARNING("Failed to update worker gczeal!");
2208 #endif
2210 void WorkerPrivate::SetLowMemoryState(bool aState) {
2211 AssertIsOnParentThread();
2213 RefPtr<SetLowMemoryStateRunnable> runnable =
2214 new SetLowMemoryStateRunnable(this, aState);
2215 if (!runnable->Dispatch(this)) {
2216 NS_WARNING("Failed to set low memory state!");
2220 void WorkerPrivate::GarbageCollect(bool aShrinking) {
2221 AssertIsOnParentThread();
2223 RefPtr<GarbageCollectRunnable> runnable = new GarbageCollectRunnable(
2224 this, aShrinking, /* aCollectChildren = */ true);
2225 if (!runnable->Dispatch(this)) {
2226 NS_WARNING("Failed to GC worker!");
2230 void WorkerPrivate::CycleCollect() {
2231 AssertIsOnParentThread();
2233 RefPtr<CycleCollectRunnable> runnable =
2234 new CycleCollectRunnable(this, /* aCollectChildren = */ true);
2235 if (!runnable->Dispatch(this)) {
2236 NS_WARNING("Failed to CC worker!");
2240 void WorkerPrivate::OfflineStatusChangeEvent(bool aIsOffline) {
2241 AssertIsOnParentThread();
2243 RefPtr<OfflineStatusChangeRunnable> runnable =
2244 new OfflineStatusChangeRunnable(this, aIsOffline);
2245 if (!runnable->Dispatch(this)) {
2246 NS_WARNING("Failed to dispatch offline status change event!");
2250 void WorkerPrivate::OfflineStatusChangeEventInternal(bool aIsOffline) {
2251 auto data = mWorkerThreadAccessible.Access();
2253 // The worker is already in this state. No need to dispatch an event.
2254 if (data->mOnLine == !aIsOffline) {
2255 return;
2258 for (uint32_t index = 0; index < data->mChildWorkers.Length(); ++index) {
2259 data->mChildWorkers[index]->OfflineStatusChangeEvent(aIsOffline);
2262 data->mOnLine = !aIsOffline;
2263 WorkerGlobalScope* globalScope = GlobalScope();
2264 RefPtr<WorkerNavigator> nav = globalScope->GetExistingNavigator();
2265 if (nav) {
2266 nav->SetOnLine(data->mOnLine);
2269 nsString eventType;
2270 if (aIsOffline) {
2271 eventType.AssignLiteral("offline");
2272 } else {
2273 eventType.AssignLiteral("online");
2276 RefPtr<Event> event = NS_NewDOMEvent(globalScope, nullptr, nullptr);
2278 event->InitEvent(eventType, false, false);
2279 event->SetTrusted(true);
2281 globalScope->DispatchEvent(*event);
2284 void WorkerPrivate::MemoryPressure() {
2285 AssertIsOnParentThread();
2287 RefPtr<MemoryPressureRunnable> runnable = new MemoryPressureRunnable(this);
2288 Unused << NS_WARN_IF(!runnable->Dispatch(this));
2291 RefPtr<WorkerPrivate::JSMemoryUsagePromise> WorkerPrivate::GetJSMemoryUsage() {
2292 AssertIsOnMainThread();
2295 MutexAutoLock lock(mMutex);
2296 // If we have started shutting down the worker, do not dispatch a runnable
2297 // to measure its memory.
2298 if (ParentStatus() > Running) {
2299 return nullptr;
2303 return InvokeAsync(ControlEventTarget(), __func__, []() {
2304 WorkerPrivate* wp = GetCurrentThreadWorkerPrivate();
2305 MOZ_ASSERT(wp);
2306 wp->AssertIsOnWorkerThread();
2307 MutexAutoLock lock(wp->mMutex);
2308 return JSMemoryUsagePromise::CreateAndResolve(
2309 js::GetGCHeapUsage(wp->mJSContext), __func__);
2313 void WorkerPrivate::WorkerScriptLoaded() {
2314 AssertIsOnMainThread();
2316 if (IsSharedWorker() || IsServiceWorker()) {
2317 // No longer need to hold references to the window or document we came from.
2318 mLoadInfo.mWindow = nullptr;
2319 mLoadInfo.mScriptContext = nullptr;
2323 void WorkerPrivate::SetBaseURI(nsIURI* aBaseURI) {
2324 AssertIsOnMainThread();
2326 if (!mLoadInfo.mBaseURI) {
2327 NS_ASSERTION(GetParent(), "Shouldn't happen without a parent!");
2328 mLoadInfo.mResolvedScriptURI = aBaseURI;
2331 mLoadInfo.mBaseURI = aBaseURI;
2333 if (NS_FAILED(aBaseURI->GetSpec(mLocationInfo.mHref))) {
2334 mLocationInfo.mHref.Truncate();
2337 mLocationInfo.mHostname.Truncate();
2338 nsContentUtils::GetHostOrIPv6WithBrackets(aBaseURI, mLocationInfo.mHostname);
2340 nsCOMPtr<nsIURL> url(do_QueryInterface(aBaseURI));
2341 if (!url || NS_FAILED(url->GetFilePath(mLocationInfo.mPathname))) {
2342 mLocationInfo.mPathname.Truncate();
2345 nsCString temp;
2347 if (url && NS_SUCCEEDED(url->GetQuery(temp)) && !temp.IsEmpty()) {
2348 mLocationInfo.mSearch.Assign('?');
2349 mLocationInfo.mSearch.Append(temp);
2352 if (NS_SUCCEEDED(aBaseURI->GetRef(temp)) && !temp.IsEmpty()) {
2353 if (mLocationInfo.mHash.IsEmpty()) {
2354 mLocationInfo.mHash.Assign('#');
2355 mLocationInfo.mHash.Append(temp);
2359 if (NS_SUCCEEDED(aBaseURI->GetScheme(mLocationInfo.mProtocol))) {
2360 mLocationInfo.mProtocol.Append(':');
2361 } else {
2362 mLocationInfo.mProtocol.Truncate();
2365 int32_t port;
2366 if (NS_SUCCEEDED(aBaseURI->GetPort(&port)) && port != -1) {
2367 mLocationInfo.mPort.AppendInt(port);
2369 nsAutoCString host(mLocationInfo.mHostname);
2370 host.Append(':');
2371 host.Append(mLocationInfo.mPort);
2373 mLocationInfo.mHost.Assign(host);
2374 } else {
2375 mLocationInfo.mHost.Assign(mLocationInfo.mHostname);
2378 nsContentUtils::GetWebExposedOriginSerialization(aBaseURI,
2379 mLocationInfo.mOrigin);
2382 nsresult WorkerPrivate::SetPrincipalsAndCSPOnMainThread(
2383 nsIPrincipal* aPrincipal, nsIPrincipal* aPartitionedPrincipal,
2384 nsILoadGroup* aLoadGroup, nsIContentSecurityPolicy* aCsp) {
2385 return mLoadInfo.SetPrincipalsAndCSPOnMainThread(
2386 aPrincipal, aPartitionedPrincipal, aLoadGroup, aCsp);
2389 nsresult WorkerPrivate::SetPrincipalsAndCSPFromChannel(nsIChannel* aChannel) {
2390 return mLoadInfo.SetPrincipalsAndCSPFromChannel(aChannel);
2393 bool WorkerPrivate::FinalChannelPrincipalIsValid(nsIChannel* aChannel) {
2394 return mLoadInfo.FinalChannelPrincipalIsValid(aChannel);
2397 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
2398 bool WorkerPrivate::PrincipalURIMatchesScriptURL() {
2399 return mLoadInfo.PrincipalURIMatchesScriptURL();
2401 #endif
2403 void WorkerPrivate::UpdateOverridenLoadGroup(nsILoadGroup* aBaseLoadGroup) {
2404 AssertIsOnMainThread();
2406 // The load group should have been overriden at init time.
2407 mLoadInfo.mInterfaceRequestor->MaybeAddBrowserChild(aBaseLoadGroup);
2410 void WorkerPrivate::UpdateIsOnContentBlockingAllowList(
2411 bool aOnContentBlockingAllowList) {
2412 AssertIsOnWorkerThread();
2413 MOZ_DIAGNOSTIC_ASSERT(IsServiceWorker());
2415 RefPtr<StrongWorkerRef> strongRef = StrongWorkerRef::Create(
2416 this, "WorkerPrivate::UpdateIsOnContentBlockingAllowList");
2417 if (!strongRef) {
2418 return;
2420 RefPtr<ThreadSafeWorkerRef> ref = new ThreadSafeWorkerRef(strongRef);
2421 DispatchToMainThread(NS_NewRunnableFunction(
2422 "WorkerPrivate::UpdateIsOnContentBlockingAllowList",
2423 [ref = std::move(ref), aOnContentBlockingAllowList] {
2424 ref->Private()
2425 ->mLoadInfo.mCookieJarSettingsArgs.isOnContentBlockingAllowList() =
2426 aOnContentBlockingAllowList;
2428 nsCOMPtr<nsICookieJarSettings> workerCookieJarSettings;
2429 net::CookieJarSettings::Deserialize(
2430 ref->Private()->mLoadInfo.mCookieJarSettingsArgs,
2431 getter_AddRefs(workerCookieJarSettings));
2432 bool shouldResistFingerprinting =
2433 nsContentUtils::ShouldResistFingerprinting_dangerous(
2434 ref->Private()->mLoadInfo.mPrincipal,
2435 "Service Workers exist outside a Document or Channel; as a "
2436 "property of the domain (and origin attributes). We don't have "
2437 "a "
2438 "CookieJarSettings to perform the *nested check*, but we can "
2439 "rely "
2440 "on the FPI/dFPI partition key check. The WorkerPrivate's "
2441 "ShouldResistFingerprinting function for the ServiceWorker "
2442 "depends "
2443 "on this boolean and will also consider an explicit RFPTarget.",
2444 RFPTarget::IsAlwaysEnabledForPrecompute) &&
2445 !nsContentUtils::ETPSaysShouldNotResistFingerprinting(
2446 workerCookieJarSettings, false);
2448 ref->Private()
2449 ->mLoadInfo.mCookieJarSettingsArgs.shouldResistFingerprinting() =
2450 shouldResistFingerprinting;
2451 ref->Private()->mLoadInfo.mShouldResistFingerprinting =
2452 shouldResistFingerprinting;
2453 }));
2455 /* From:
2456 https://searchfox.org/mozilla-central/rev/964b8aa226c68bbf83c9ffc38984804734bb0de2/js/public/RealmOptions.h#316-318
2457 > RealmCreationOptions specify fundamental realm characteristics that must
2458 be specified when the realm is created, that can't be changed after the
2459 realm is created.
2462 nsCString locale;
2463 if (aEnabled) {
2464 locale = nsRFPService::GetSpoofedJSLocale();
2467 MutexAutoLock lock(mMutex);
2468 mJSSettings.chromeRealmOptions.creationOptions().setForceUTC(aEnabled);
2469 mJSSettings.chromeRealmOptions.creationOptions().setAlwaysUseFdlibm(aEnabled);
2470 if (aEnabled) {
2471 mJSSettings.chromeRealmOptions.creationOptions().setLocaleCopyZ(
2472 locale.get());
2475 mJSSettings.contentRealmOptions.creationOptions().setForceUTC(aEnabled);
2476 mJSSettings.contentRealmOptions.creationOptions().setAlwaysUseFdlibm(
2477 aEnabled);
2478 if (aEnabled) {
2479 mJSSettings.contentRealmOptions.creationOptions().setLocaleCopyZ(
2480 locale.get());
2485 bool WorkerPrivate::IsOnParentThread() const {
2486 if (GetParent()) {
2487 return GetParent()->IsOnWorkerThread();
2489 return NS_IsMainThread();
2492 #ifdef DEBUG
2494 void WorkerPrivate::AssertIsOnParentThread() const {
2495 if (GetParent()) {
2496 GetParent()->AssertIsOnWorkerThread();
2497 } else {
2498 AssertIsOnMainThread();
2502 void WorkerPrivate::AssertInnerWindowIsCorrect() const {
2503 AssertIsOnParentThread();
2505 // Only care about top level workers from windows.
2506 if (mParent || !mLoadInfo.mWindow) {
2507 return;
2510 AssertIsOnMainThread();
2512 nsPIDOMWindowOuter* outer = mLoadInfo.mWindow->GetOuterWindow();
2513 NS_ASSERTION(outer && outer->GetCurrentInnerWindow() == mLoadInfo.mWindow,
2514 "Inner window no longer correct!");
2517 #endif
2519 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
2520 bool WorkerPrivate::PrincipalIsValid() const {
2521 return mLoadInfo.PrincipalIsValid();
2523 #endif
2525 WorkerPrivate::WorkerThreadAccessible::WorkerThreadAccessible(
2526 WorkerPrivate* const aParent)
2527 : mNumWorkerRefsPreventingShutdownStart(0),
2528 mDebuggerEventLoopLevel(0),
2529 mNonblockingCCBackgroundActorCount(0),
2530 mErrorHandlerRecursionCount(0),
2531 mNextTimeoutId(1),
2532 mCurrentTimerNestingLevel(0),
2533 mFrozen(false),
2534 mDebuggerInterruptRequested(false),
2535 mTimerRunning(false),
2536 mRunningExpiredTimeouts(false),
2537 mPeriodicGCTimerRunning(false),
2538 mIdleGCTimerRunning(false),
2539 mOnLine(aParent ? aParent->OnLine() : !NS_IsOffline()),
2540 mJSThreadExecutionGranted(false),
2541 mCCCollectedAnything(false) {}
2543 namespace {
2545 bool IsNewWorkerSecureContext(const WorkerPrivate* const aParent,
2546 const WorkerKind aWorkerKind,
2547 const WorkerLoadInfo& aLoadInfo) {
2548 if (aParent) {
2549 return aParent->IsSecureContext();
2552 // Our secure context state depends on the kind of worker we have.
2554 if (aLoadInfo.mPrincipal && aLoadInfo.mPrincipal->IsSystemPrincipal()) {
2555 return true;
2558 if (aWorkerKind == WorkerKindService) {
2559 return true;
2562 if (aLoadInfo.mSecureContext != WorkerLoadInfo::eNotSet) {
2563 return aLoadInfo.mSecureContext == WorkerLoadInfo::eSecureContext;
2566 MOZ_ASSERT_UNREACHABLE(
2567 "non-chrome worker that is not a service worker "
2568 "that has no parent and no associated window");
2570 return false;
2573 } // namespace
2575 WorkerPrivate::WorkerPrivate(
2576 WorkerPrivate* aParent, const nsAString& aScriptURL, bool aIsChromeWorker,
2577 WorkerKind aWorkerKind, RequestCredentials aRequestCredentials,
2578 enum WorkerType aWorkerType, const nsAString& aWorkerName,
2579 const nsACString& aServiceWorkerScope, WorkerLoadInfo& aLoadInfo,
2580 nsString&& aId, const nsID& aAgentClusterId,
2581 const nsILoadInfo::CrossOriginOpenerPolicy aAgentClusterOpenerPolicy,
2582 CancellationCallback&& aCancellationCallback,
2583 TerminationCallback&& aTerminationCallback,
2584 mozilla::ipc::Endpoint<PRemoteWorkerNonLifeCycleOpControllerChild>&&
2585 aChildEp)
2586 : mMutex("WorkerPrivate Mutex"),
2587 mCondVar(mMutex, "WorkerPrivate CondVar"),
2588 mParent(aParent),
2589 mScriptURL(aScriptURL),
2590 mWorkerName(aWorkerName),
2591 mCredentialsMode(aRequestCredentials),
2592 mWorkerType(aWorkerType), // If the worker runs as a script or a module
2593 mWorkerKind(aWorkerKind),
2594 mCancellationCallback(std::move(aCancellationCallback)),
2595 mTerminationCallback(std::move(aTerminationCallback)),
2596 mLoadInfo(std::move(aLoadInfo)),
2597 mDebugger(nullptr),
2598 mJSContext(nullptr),
2599 mPRThread(nullptr),
2600 mWorkerControlEventTarget(new WorkerEventTarget(
2601 this, WorkerEventTarget::Behavior::ControlOnly)),
2602 mWorkerHybridEventTarget(
2603 new WorkerEventTarget(this, WorkerEventTarget::Behavior::Hybrid)),
2604 mChildEp(std::move(aChildEp)),
2605 mParentStatus(Pending),
2606 mStatus(Pending),
2607 mCreationTimeStamp(TimeStamp::Now()),
2608 mCreationTimeHighRes((double)PR_Now() / PR_USEC_PER_MSEC),
2609 mReportedUseCounters(false),
2610 mAgentClusterId(aAgentClusterId),
2611 mWorkerThreadAccessible(aParent),
2612 mPostSyncLoopOperations(0),
2613 mParentWindowPaused(false),
2614 mWorkerScriptExecutedSuccessfully(false),
2615 mFetchHandlerWasAdded(false),
2616 mMainThreadObjectsForgotten(false),
2617 mIsChromeWorker(aIsChromeWorker),
2618 mParentFrozen(false),
2619 mIsSecureContext(
2620 IsNewWorkerSecureContext(mParent, mWorkerKind, mLoadInfo)),
2621 mDebuggerRegistered(false),
2622 mIsInBackground(false),
2623 mDebuggerReady(true),
2624 mExtensionAPIAllowed(false),
2625 mIsInAutomation(false),
2626 mId(std::move(aId)),
2627 mAgentClusterOpenerPolicy(aAgentClusterOpenerPolicy),
2628 mIsPrivilegedAddonGlobal(false),
2629 mTopLevelWorkerFinishedRunnableCount(0),
2630 mWorkerFinishedRunnableCount(0) {
2631 LOG(WorkerLog(), ("WorkerPrivate::WorkerPrivate [%p]", this));
2632 MOZ_ASSERT_IF(!IsDedicatedWorker(), NS_IsMainThread());
2634 if (aParent) {
2635 aParent->AssertIsOnWorkerThread();
2637 // Note that this copies our parent's secure context state into mJSSettings.
2638 aParent->CopyJSSettings(mJSSettings);
2640 MOZ_ASSERT_IF(mIsChromeWorker, mIsSecureContext);
2642 mIsInAutomation = aParent->IsInAutomation();
2644 MOZ_ASSERT(IsDedicatedWorker());
2646 if (aParent->mParentFrozen) {
2647 Freeze(nullptr);
2650 if (aParent->IsRunningInBackground()) {
2651 mIsInBackground = true;
2654 mIsPrivilegedAddonGlobal = aParent->mIsPrivilegedAddonGlobal;
2655 } else {
2656 AssertIsOnMainThread();
2658 RuntimeService::GetDefaultJSSettings(mJSSettings);
2661 JS::RealmOptions& chromeRealmOptions = mJSSettings.chromeRealmOptions;
2662 JS::RealmOptions& contentRealmOptions = mJSSettings.contentRealmOptions;
2664 xpc::InitGlobalObjectOptions(
2665 chromeRealmOptions, UsesSystemPrincipal(), mIsSecureContext,
2666 ShouldResistFingerprinting(RFPTarget::JSDateTimeUTC),
2667 ShouldResistFingerprinting(RFPTarget::JSMathFdlibm),
2668 ShouldResistFingerprinting(RFPTarget::JSLocale));
2669 xpc::InitGlobalObjectOptions(
2670 contentRealmOptions, UsesSystemPrincipal(), mIsSecureContext,
2671 ShouldResistFingerprinting(RFPTarget::JSDateTimeUTC),
2672 ShouldResistFingerprinting(RFPTarget::JSMathFdlibm),
2673 ShouldResistFingerprinting(RFPTarget::JSLocale));
2675 // Check if it's a privileged addon executing in order to allow access
2676 // to SharedArrayBuffer
2677 if (mLoadInfo.mPrincipal) {
2678 if (auto* policy =
2679 BasePrincipal::Cast(mLoadInfo.mPrincipal)->AddonPolicy()) {
2680 if (policy->IsPrivileged() &&
2681 ExtensionPolicyService::GetSingleton().IsExtensionProcess()) {
2682 // Privileged extensions are allowed to use SharedArrayBuffer in
2683 // their extension process, but never in content scripts in
2684 // content processes.
2685 mIsPrivilegedAddonGlobal = true;
2688 if (StaticPrefs::
2689 extensions_backgroundServiceWorker_enabled_AtStartup() &&
2690 mWorkerKind == WorkerKindService &&
2691 policy->IsManifestBackgroundWorker(mScriptURL)) {
2692 // Only allows ExtensionAPI for extension service workers
2693 // that are declared in the extension manifest json as
2694 // the background service worker.
2695 mExtensionAPIAllowed = true;
2700 // The SharedArrayBuffer global constructor property should not be present
2701 // in a fresh global object when shared memory objects aren't allowed
2702 // (because COOP/COEP support isn't enabled, or because COOP/COEP don't
2703 // act to isolate this worker to a separate process).
2704 const bool defineSharedArrayBufferConstructor = IsSharedMemoryAllowed();
2705 chromeRealmOptions.creationOptions()
2706 .setDefineSharedArrayBufferConstructor(
2707 defineSharedArrayBufferConstructor);
2708 contentRealmOptions.creationOptions()
2709 .setDefineSharedArrayBufferConstructor(
2710 defineSharedArrayBufferConstructor);
2713 mIsInAutomation = xpc::IsInAutomation();
2715 // Our parent can get suspended after it initiates the async creation
2716 // of a new worker thread. In this case suspend the new worker as well.
2717 if (mLoadInfo.mWindow &&
2718 nsGlobalWindowInner::Cast(mLoadInfo.mWindow)->IsSuspended()) {
2719 ParentWindowPaused();
2722 if (mLoadInfo.mWindow &&
2723 nsGlobalWindowInner::Cast(mLoadInfo.mWindow)->IsFrozen()) {
2724 Freeze(mLoadInfo.mWindow);
2727 if (mLoadInfo.mWindow && mLoadInfo.mWindow->GetOuterWindow() &&
2728 mLoadInfo.mWindow->GetOuterWindow()->IsBackground()) {
2729 mIsInBackground = true;
2733 nsCOMPtr<nsISerialEventTarget> target;
2735 // A child worker just inherits the parent workers ThrottledEventQueue
2736 // and main thread target for now. This is mainly due to the restriction
2737 // that ThrottledEventQueue can only be created on the main thread at the
2738 // moment.
2739 if (aParent) {
2740 mMainThreadEventTargetForMessaging =
2741 aParent->mMainThreadEventTargetForMessaging;
2742 mMainThreadEventTarget = aParent->mMainThreadEventTarget;
2743 mMainThreadDebuggeeEventTarget = aParent->mMainThreadDebuggeeEventTarget;
2744 return;
2747 MOZ_ASSERT(NS_IsMainThread());
2748 target = GetWindow()
2749 ? GetWindow()->GetBrowsingContextGroup()->GetWorkerEventQueue()
2750 : nullptr;
2752 if (!target) {
2753 target = GetMainThreadSerialEventTarget();
2754 MOZ_DIAGNOSTIC_ASSERT(target);
2757 // Throttle events to the main thread using a ThrottledEventQueue specific to
2758 // this tree of worker threads.
2759 mMainThreadEventTargetForMessaging =
2760 ThrottledEventQueue::Create(target, "Worker queue for messaging");
2761 mMainThreadEventTarget = ThrottledEventQueue::Create(
2762 GetMainThreadSerialEventTarget(), "Worker queue",
2763 nsIRunnablePriority::PRIORITY_MEDIUMHIGH);
2764 mMainThreadDebuggeeEventTarget =
2765 ThrottledEventQueue::Create(target, "Worker debuggee queue");
2766 if (IsParentWindowPaused() || IsFrozen()) {
2767 MOZ_ALWAYS_SUCCEEDS(mMainThreadDebuggeeEventTarget->SetIsPaused(true));
2771 WorkerPrivate::~WorkerPrivate() {
2772 MOZ_DIAGNOSTIC_ASSERT(mTopLevelWorkerFinishedRunnableCount == 0);
2773 MOZ_DIAGNOSTIC_ASSERT(mWorkerFinishedRunnableCount == 0);
2775 mWorkerControlEventTarget->ForgetWorkerPrivate(this);
2777 // We force the hybrid event target to forget the thread when we
2778 // enter the Killing state, but we do it again here to be safe.
2779 // Its possible that we may be created and destroyed without progressing
2780 // to Killing via some obscure code path.
2781 mWorkerHybridEventTarget->ForgetWorkerPrivate(this);
2784 WorkerPrivate::AgentClusterIdAndCoop
2785 WorkerPrivate::ComputeAgentClusterIdAndCoop(WorkerPrivate* aParent,
2786 WorkerKind aWorkerKind,
2787 WorkerLoadInfo* aLoadInfo,
2788 bool aIsChromeWorker) {
2789 nsILoadInfo::CrossOriginOpenerPolicy agentClusterCoop =
2790 nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
2792 if (aParent) {
2793 MOZ_ASSERT(aWorkerKind == WorkerKind::WorkerKindDedicated);
2795 return {aParent->AgentClusterId(), aParent->mAgentClusterOpenerPolicy};
2798 AssertIsOnMainThread();
2800 if (aWorkerKind == WorkerKind::WorkerKindService ||
2801 aWorkerKind == WorkerKind::WorkerKindShared) {
2802 return {aLoadInfo->mAgentClusterId, agentClusterCoop};
2805 if (aLoadInfo->mWindow) {
2806 Document* doc = aLoadInfo->mWindow->GetExtantDoc();
2807 MOZ_DIAGNOSTIC_ASSERT(doc);
2808 RefPtr<DocGroup> docGroup = doc->GetDocGroup();
2810 nsID agentClusterId =
2811 docGroup ? docGroup->AgentClusterId() : nsID::GenerateUUID();
2813 BrowsingContext* bc = aLoadInfo->mWindow->GetBrowsingContext();
2814 MOZ_DIAGNOSTIC_ASSERT(bc);
2815 return {agentClusterId, bc->Top()->GetOpenerPolicy()};
2818 // Chrome workers share an AgentCluster with the XPC module global. This
2819 // allows things like shared memory and WASM modules to be transferred between
2820 // chrome workers and system JS.
2821 // Also set COOP+COEP flags to allow access to shared memory.
2822 if (aIsChromeWorker) {
2823 if (nsIGlobalObject* systemGlobal =
2824 xpc::NativeGlobal(xpc::PrivilegedJunkScope())) {
2825 nsID agentClusterId = systemGlobal->GetAgentClusterId().valueOrFrom(
2826 [] { return nsID::GenerateUUID(); });
2827 return {
2828 agentClusterId,
2829 nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP};
2833 // If the window object was failed to be set into the WorkerLoadInfo, we
2834 // make the worker into another agent cluster group instead of failures.
2835 return {nsID::GenerateUUID(), agentClusterCoop};
2838 // static
2839 already_AddRefed<WorkerPrivate> WorkerPrivate::Constructor(
2840 JSContext* aCx, const nsAString& aScriptURL, bool aIsChromeWorker,
2841 WorkerKind aWorkerKind, RequestCredentials aRequestCredentials,
2842 enum WorkerType aWorkerType, const nsAString& aWorkerName,
2843 const nsACString& aServiceWorkerScope, WorkerLoadInfo* aLoadInfo,
2844 ErrorResult& aRv, nsString aId,
2845 CancellationCallback&& aCancellationCallback,
2846 TerminationCallback&& aTerminationCallback,
2847 mozilla::ipc::Endpoint<PRemoteWorkerNonLifeCycleOpControllerChild>&&
2848 aChildEp) {
2849 WorkerPrivate* parent =
2850 NS_IsMainThread() ? nullptr : GetCurrentThreadWorkerPrivate();
2852 // If this is a sub-worker, we need to keep the parent worker alive until this
2853 // one is registered.
2854 RefPtr<StrongWorkerRef> workerRef;
2855 if (parent) {
2856 parent->AssertIsOnWorkerThread();
2858 workerRef = StrongWorkerRef::Create(parent, "WorkerPrivate::Constructor");
2859 if (NS_WARN_IF(!workerRef)) {
2860 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
2861 return nullptr;
2863 } else {
2864 AssertIsOnMainThread();
2867 Maybe<WorkerLoadInfo> stackLoadInfo;
2868 if (!aLoadInfo) {
2869 stackLoadInfo.emplace();
2871 nsresult rv = GetLoadInfo(
2872 aCx, nullptr, parent, aScriptURL, aWorkerType, aRequestCredentials,
2873 aIsChromeWorker, InheritLoadGroup, aWorkerKind, stackLoadInfo.ptr());
2874 aRv.MightThrowJSException();
2875 if (NS_FAILED(rv)) {
2876 workerinternals::ReportLoadError(aRv, rv, aScriptURL);
2877 return nullptr;
2880 aLoadInfo = stackLoadInfo.ptr();
2883 // NB: This has to be done before creating the WorkerPrivate, because it will
2884 // attempt to use static variables that are initialized in the RuntimeService
2885 // constructor.
2886 RuntimeService* runtimeService;
2888 if (!parent) {
2889 runtimeService = RuntimeService::GetOrCreateService();
2890 if (!runtimeService) {
2891 aRv.Throw(NS_ERROR_FAILURE);
2892 return nullptr;
2894 } else {
2895 runtimeService = RuntimeService::GetService();
2898 MOZ_ASSERT(runtimeService);
2900 // Don't create a worker with the shutting down RuntimeService.
2901 if (runtimeService->IsShuttingDown()) {
2902 aRv.Throw(NS_ERROR_UNEXPECTED);
2903 return nullptr;
2906 AgentClusterIdAndCoop idAndCoop = ComputeAgentClusterIdAndCoop(
2907 parent, aWorkerKind, aLoadInfo, aIsChromeWorker);
2909 RefPtr<WorkerPrivate> worker = new WorkerPrivate(
2910 parent, aScriptURL, aIsChromeWorker, aWorkerKind, aRequestCredentials,
2911 aWorkerType, aWorkerName, aServiceWorkerScope, *aLoadInfo, std::move(aId),
2912 idAndCoop.mId, idAndCoop.mCoop, std::move(aCancellationCallback),
2913 std::move(aTerminationCallback), std::move(aChildEp));
2915 // Gecko contexts always have an explicitly-set default locale (set by
2916 // XPJSRuntime::Initialize for the main thread, set by
2917 // WorkerThreadPrimaryRunnable::Run for workers just before running worker
2918 // code), so this is never SpiderMonkey's builtin default locale.
2919 JS::UniqueChars defaultLocale = JS_GetDefaultLocale(aCx);
2920 if (NS_WARN_IF(!defaultLocale)) {
2921 aRv.Throw(NS_ERROR_UNEXPECTED);
2922 return nullptr;
2925 worker->mDefaultLocale = std::move(defaultLocale);
2927 if (!runtimeService->RegisterWorker(*worker)) {
2928 aRv.Throw(NS_ERROR_UNEXPECTED);
2929 return nullptr;
2932 // From this point on (worker thread has been started) we
2933 // must keep ourself alive. We can now only be cleared by
2934 // ClearSelfAndParentEventTargetRef().
2935 worker->mSelfRef = worker;
2936 worker->mParentRef = MakeRefPtr<WorkerParentRef>(worker);
2938 worker->EnableDebugger();
2940 MOZ_DIAGNOSTIC_ASSERT(worker->PrincipalIsValid());
2942 UniquePtr<SerializedStackHolder> stack;
2943 if (worker->IsWatchedByDevTools()) {
2944 stack = GetCurrentStackForNetMonitor(aCx);
2947 // This should be non-null for dedicated workers and null for Shared and
2948 // Service workers. All Encoding values are static and will live as long
2949 // as the process and the convention is to therefore use raw pointers.
2950 const mozilla::Encoding* aDocumentEncoding =
2951 NS_IsMainThread() && !worker->GetParent() && worker->GetDocument()
2952 ? worker->GetDocument()->GetDocumentCharacterSet().get()
2953 : nullptr;
2955 RefPtr<CompileScriptRunnable> compiler = new CompileScriptRunnable(
2956 worker, std::move(stack), aScriptURL, aDocumentEncoding);
2957 if (!compiler->Dispatch(worker)) {
2958 aRv.Throw(NS_ERROR_UNEXPECTED);
2959 return nullptr;
2962 return worker.forget();
2965 // Mark worker private as running in the background tab
2966 // for further throttling
2967 void WorkerPrivate::SetIsRunningInBackground() {
2968 AssertIsOnParentThread();
2970 RefPtr<ChangeBackgroundStateRunnable> runnable =
2971 new ChangeBackgroundStateRunnable(this, true);
2972 runnable->Dispatch(this);
2974 LOG(WorkerLog(), ("SetIsRunningInBackground [%p]", this));
2977 void WorkerPrivate::SetIsRunningInForeground() {
2978 AssertIsOnParentThread();
2980 RefPtr<ChangeBackgroundStateRunnable> runnable =
2981 new ChangeBackgroundStateRunnable(this, false);
2982 runnable->Dispatch(this);
2984 LOG(WorkerLog(), ("SetIsRunningInForeground [%p]", this));
2987 nsresult WorkerPrivate::SetIsDebuggerReady(bool aReady) {
2988 AssertIsOnMainThread();
2989 MutexAutoLock lock(mMutex);
2991 if (mDebuggerReady == aReady) {
2992 return NS_OK;
2995 if (!aReady && mDebuggerRegistered) {
2996 // The debugger can only be marked as not ready during registration.
2997 return NS_ERROR_FAILURE;
3000 mDebuggerReady = aReady;
3002 if (aReady && mDebuggerRegistered) {
3003 // Dispatch all the delayed runnables without releasing the lock, to ensure
3004 // that the order in which debuggee runnables execute is the same as the
3005 // order in which they were originally dispatched.
3006 auto pending = std::move(mDelayedDebuggeeRunnables);
3007 for (uint32_t i = 0; i < pending.Length(); i++) {
3008 RefPtr<WorkerRunnable> runnable = std::move(pending[i]);
3009 nsresult rv = DispatchLockHeld(runnable.forget(), nullptr, lock);
3010 NS_ENSURE_SUCCESS(rv, rv);
3012 MOZ_RELEASE_ASSERT(mDelayedDebuggeeRunnables.IsEmpty());
3015 return NS_OK;
3018 // static
3019 nsresult WorkerPrivate::GetLoadInfo(
3020 JSContext* aCx, nsPIDOMWindowInner* aWindow, WorkerPrivate* aParent,
3021 const nsAString& aScriptURL, const enum WorkerType& aWorkerType,
3022 const RequestCredentials& aCredentials, bool aIsChromeWorker,
3023 LoadGroupBehavior aLoadGroupBehavior, WorkerKind aWorkerKind,
3024 WorkerLoadInfo* aLoadInfo) {
3025 using namespace mozilla::dom::workerinternals;
3027 MOZ_ASSERT(aCx);
3028 MOZ_ASSERT_IF(NS_IsMainThread(),
3029 aCx == nsContentUtils::GetCurrentJSContext());
3031 if (aWindow) {
3032 AssertIsOnMainThread();
3035 WorkerLoadInfo loadInfo;
3036 nsresult rv;
3038 if (aParent) {
3039 aParent->AssertIsOnWorkerThread();
3041 // If the parent is going away give up now.
3042 WorkerStatus parentStatus;
3044 MutexAutoLock lock(aParent->mMutex);
3045 parentStatus = aParent->mStatus;
3048 if (parentStatus > Running) {
3049 return NS_ERROR_FAILURE;
3052 // Passing a pointer to our stack loadInfo is safe here because this
3053 // method uses a sync runnable to get the channel from the main thread.
3054 rv = ChannelFromScriptURLWorkerThread(aCx, aParent, aScriptURL, aWorkerType,
3055 aCredentials, loadInfo);
3056 if (NS_FAILED(rv)) {
3057 MOZ_ALWAYS_TRUE(loadInfo.ProxyReleaseMainThreadObjects(aParent));
3058 return rv;
3061 // Now that we've spun the loop there's no guarantee that our parent is
3062 // still alive. We may have received control messages initiating shutdown.
3064 MutexAutoLock lock(aParent->mMutex);
3065 parentStatus = aParent->mStatus;
3068 if (parentStatus > Running) {
3069 MOZ_ALWAYS_TRUE(loadInfo.ProxyReleaseMainThreadObjects(aParent));
3070 return NS_ERROR_FAILURE;
3073 loadInfo.mTrials = aParent->Trials();
3074 loadInfo.mDomain = aParent->Domain();
3075 loadInfo.mFromWindow = aParent->IsFromWindow();
3076 loadInfo.mWindowID = aParent->WindowID();
3077 loadInfo.mAssociatedBrowsingContextID =
3078 aParent->AssociatedBrowsingContextID();
3079 loadInfo.mStorageAccess = aParent->StorageAccess();
3080 loadInfo.mUseRegularPrincipal = aParent->UseRegularPrincipal();
3081 loadInfo.mUsingStorageAccess = aParent->UsingStorageAccess();
3082 loadInfo.mCookieJarSettings = aParent->CookieJarSettings();
3083 if (loadInfo.mCookieJarSettings) {
3084 loadInfo.mCookieJarSettingsArgs = aParent->CookieJarSettingsArgs();
3086 loadInfo.mOriginAttributes = aParent->GetOriginAttributes();
3087 loadInfo.mServiceWorkersTestingInWindow =
3088 aParent->ServiceWorkersTestingInWindow();
3089 loadInfo.mIsThirdPartyContext = aParent->IsThirdPartyContext();
3090 loadInfo.mShouldResistFingerprinting = aParent->ShouldResistFingerprinting(
3091 RFPTarget::IsAlwaysEnabledForPrecompute);
3092 loadInfo.mOverriddenFingerprintingSettings =
3093 aParent->GetOverriddenFingerprintingSettings();
3094 loadInfo.mParentController = aParent->GlobalScope()->GetController();
3095 loadInfo.mWatchedByDevTools = aParent->IsWatchedByDevTools();
3096 loadInfo.mIsOn3PCBExceptionList = aParent->IsOn3PCBExceptionList();
3097 } else {
3098 AssertIsOnMainThread();
3100 // Make sure that the IndexedDatabaseManager is set up
3101 IndexedDatabaseManager* idm = IndexedDatabaseManager::GetOrCreate();
3102 if (idm) {
3103 Unused << NS_WARN_IF(NS_FAILED(idm->EnsureLocale()));
3104 } else {
3105 NS_WARNING("Failed to get IndexedDatabaseManager!");
3108 nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
3109 MOZ_ASSERT(ssm);
3111 bool isChrome = nsContentUtils::IsSystemCaller(aCx);
3113 // First check to make sure the caller has permission to make a privileged
3114 // worker if they called the ChromeWorker/ChromeSharedWorker constructor.
3115 if (aIsChromeWorker && !isChrome) {
3116 return NS_ERROR_DOM_SECURITY_ERR;
3119 // Chrome callers (whether creating a ChromeWorker or Worker) always get the
3120 // system principal here as they're allowed to load anything. The script
3121 // loader will refuse to run any script that does not also have the system
3122 // principal.
3123 if (isChrome) {
3124 rv = ssm->GetSystemPrincipal(getter_AddRefs(loadInfo.mLoadingPrincipal));
3125 NS_ENSURE_SUCCESS(rv, rv);
3128 // See if we're being called from a window.
3129 nsCOMPtr<nsPIDOMWindowInner> globalWindow = aWindow;
3130 if (!globalWindow) {
3131 globalWindow = xpc::CurrentWindowOrNull(aCx);
3134 nsCOMPtr<Document> document;
3135 Maybe<ClientInfo> clientInfo;
3137 if (globalWindow) {
3138 // Only use the current inner window, and only use it if the caller can
3139 // access it.
3140 if (nsPIDOMWindowOuter* outerWindow = globalWindow->GetOuterWindow()) {
3141 loadInfo.mWindow = outerWindow->GetCurrentInnerWindow();
3144 loadInfo.mTrials =
3145 OriginTrials::FromWindow(nsGlobalWindowInner::Cast(loadInfo.mWindow));
3147 BrowsingContext* browsingContext = globalWindow->GetBrowsingContext();
3149 // TODO: fix this for SharedWorkers with multiple documents (bug
3150 // 1177935)
3151 loadInfo.mServiceWorkersTestingInWindow =
3152 browsingContext &&
3153 browsingContext->Top()->ServiceWorkersTestingEnabled();
3155 if (!loadInfo.mWindow ||
3156 (globalWindow != loadInfo.mWindow &&
3157 !nsContentUtils::CanCallerAccess(loadInfo.mWindow))) {
3158 return NS_ERROR_DOM_SECURITY_ERR;
3161 nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(loadInfo.mWindow);
3162 MOZ_ASSERT(sgo);
3164 loadInfo.mScriptContext = sgo->GetContext();
3165 NS_ENSURE_TRUE(loadInfo.mScriptContext, NS_ERROR_FAILURE);
3167 // If we're called from a window then we can dig out the principal and URI
3168 // from the document.
3169 document = loadInfo.mWindow->GetExtantDoc();
3170 NS_ENSURE_TRUE(document, NS_ERROR_FAILURE);
3172 loadInfo.mBaseURI = document->GetDocBaseURI();
3173 loadInfo.mLoadGroup = document->GetDocumentLoadGroup();
3174 NS_ENSURE_TRUE(loadInfo.mLoadGroup, NS_ERROR_FAILURE);
3176 clientInfo = globalWindow->GetClientInfo();
3178 // Use the document's NodePrincipal as loading principal if we're not
3179 // being called from chrome.
3180 if (!loadInfo.mLoadingPrincipal) {
3181 loadInfo.mLoadingPrincipal = document->NodePrincipal();
3182 NS_ENSURE_TRUE(loadInfo.mLoadingPrincipal, NS_ERROR_FAILURE);
3184 // We use the document's base domain to limit the number of workers
3185 // each domain can create. For sandboxed documents, we use the domain
3186 // of their first non-sandboxed document, walking up until we find
3187 // one. If we can't find one, we fall back to using the GUID of the
3188 // null principal as the base domain.
3189 if (document->GetSandboxFlags() & SANDBOXED_ORIGIN) {
3190 nsCOMPtr<Document> tmpDoc = document;
3191 do {
3192 tmpDoc = tmpDoc->GetInProcessParentDocument();
3193 } while (tmpDoc && tmpDoc->GetSandboxFlags() & SANDBOXED_ORIGIN);
3195 if (tmpDoc) {
3196 // There was an unsandboxed ancestor, yay!
3197 nsCOMPtr<nsIPrincipal> tmpPrincipal = tmpDoc->NodePrincipal();
3198 rv = tmpPrincipal->GetBaseDomain(loadInfo.mDomain);
3199 NS_ENSURE_SUCCESS(rv, rv);
3200 } else {
3201 // No unsandboxed ancestor, use our GUID.
3202 rv = loadInfo.mLoadingPrincipal->GetBaseDomain(loadInfo.mDomain);
3203 NS_ENSURE_SUCCESS(rv, rv);
3205 } else {
3206 // Document creating the worker is not sandboxed.
3207 rv = loadInfo.mLoadingPrincipal->GetBaseDomain(loadInfo.mDomain);
3208 NS_ENSURE_SUCCESS(rv, rv);
3212 NS_ENSURE_TRUE(NS_LoadGroupMatchesPrincipal(loadInfo.mLoadGroup,
3213 loadInfo.mLoadingPrincipal),
3214 NS_ERROR_FAILURE);
3216 nsCOMPtr<nsIPermissionManager> permMgr =
3217 do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
3218 NS_ENSURE_SUCCESS(rv, rv);
3220 uint32_t perm;
3221 rv = permMgr->TestPermissionFromPrincipal(loadInfo.mLoadingPrincipal,
3222 "systemXHR"_ns, &perm);
3223 NS_ENSURE_SUCCESS(rv, rv);
3225 loadInfo.mXHRParamsAllowed = perm == nsIPermissionManager::ALLOW_ACTION;
3227 loadInfo.mWatchedByDevTools =
3228 browsingContext && browsingContext->WatchedByDevTools();
3230 loadInfo.mReferrerInfo =
3231 ReferrerInfo::CreateForFetch(loadInfo.mLoadingPrincipal, document);
3232 loadInfo.mFromWindow = true;
3233 loadInfo.mWindowID = globalWindow->WindowID();
3234 loadInfo.mAssociatedBrowsingContextID =
3235 globalWindow->GetBrowsingContext()->Id();
3236 loadInfo.mStorageAccess = StorageAllowedForWindow(globalWindow);
3237 loadInfo.mUseRegularPrincipal = document->UseRegularPrincipal();
3238 loadInfo.mUsingStorageAccess = document->UsingStorageAccess();
3239 loadInfo.mShouldResistFingerprinting =
3240 document->ShouldResistFingerprinting(
3241 RFPTarget::IsAlwaysEnabledForPrecompute);
3242 loadInfo.mOverriddenFingerprintingSettings =
3243 document->GetOverriddenFingerprintingSettings();
3244 loadInfo.mIsOn3PCBExceptionList = document->IsOn3PCBExceptionList();
3246 // This is an hack to deny the storage-access-permission for workers of
3247 // sub-iframes.
3248 if (loadInfo.mUsingStorageAccess &&
3249 StorageAllowedForDocument(document) != StorageAccess::eAllow) {
3250 loadInfo.mUsingStorageAccess = false;
3252 loadInfo.mIsThirdPartyContext =
3253 AntiTrackingUtils::IsThirdPartyWindow(globalWindow, nullptr);
3254 loadInfo.mCookieJarSettings = document->CookieJarSettings();
3255 if (loadInfo.mCookieJarSettings) {
3256 auto* cookieJarSettings =
3257 net::CookieJarSettings::Cast(loadInfo.mCookieJarSettings);
3258 cookieJarSettings->Serialize(loadInfo.mCookieJarSettingsArgs);
3260 StoragePrincipalHelper::GetRegularPrincipalOriginAttributes(
3261 document, loadInfo.mOriginAttributes);
3262 loadInfo.mParentController = globalWindow->GetController();
3263 loadInfo.mSecureContext = loadInfo.mWindow->IsSecureContext()
3264 ? WorkerLoadInfo::eSecureContext
3265 : WorkerLoadInfo::eInsecureContext;
3266 } else {
3267 // Not a window
3268 MOZ_ASSERT(isChrome);
3270 // We're being created outside of a window. Need to figure out the script
3271 // that is creating us in order for us to use relative URIs later on.
3272 JS::AutoFilename fileName;
3273 if (JS::DescribeScriptedCaller(&fileName, aCx)) {
3274 // In most cases, fileName is URI. In a few other cases
3275 // (e.g. xpcshell), fileName is a file path. Ideally, we would
3276 // prefer testing whether fileName parses as an URI and fallback
3277 // to file path in case of error, but Windows file paths have
3278 // the interesting property that they can be parsed as bogus
3279 // URIs (e.g. C:/Windows/Tmp is interpreted as scheme "C",
3280 // hostname "Windows", path "Tmp"), which defeats this algorithm.
3281 // Therefore, we adopt the opposite convention.
3282 nsCOMPtr<nsIFile> scriptFile;
3283 rv = NS_NewNativeLocalFile(nsDependentCString(fileName.get()),
3284 getter_AddRefs(scriptFile));
3285 if (NS_SUCCEEDED(rv)) {
3286 rv = NS_NewFileURI(getter_AddRefs(loadInfo.mBaseURI), scriptFile);
3288 if (NS_FAILED(rv)) {
3289 // As expected, fileName is not a path, so proceed with
3290 // a uri.
3291 rv = NS_NewURI(getter_AddRefs(loadInfo.mBaseURI), fileName.get());
3293 if (NS_FAILED(rv)) {
3294 return rv;
3297 loadInfo.mXHRParamsAllowed = true;
3298 loadInfo.mFromWindow = false;
3299 loadInfo.mWindowID = UINT64_MAX;
3300 loadInfo.mStorageAccess = StorageAccess::eAllow;
3301 loadInfo.mUseRegularPrincipal = true;
3302 loadInfo.mUsingStorageAccess = false;
3303 loadInfo.mCookieJarSettings =
3304 mozilla::net::CookieJarSettings::Create(loadInfo.mLoadingPrincipal);
3305 loadInfo.mShouldResistFingerprinting =
3306 nsContentUtils::ShouldResistFingerprinting_dangerous(
3307 loadInfo.mLoadingPrincipal,
3308 "Unusual situation - we have no document or CookieJarSettings",
3309 RFPTarget::IsAlwaysEnabledForPrecompute);
3310 MOZ_ASSERT(loadInfo.mCookieJarSettings);
3311 auto* cookieJarSettings =
3312 net::CookieJarSettings::Cast(loadInfo.mCookieJarSettings);
3313 cookieJarSettings->Serialize(loadInfo.mCookieJarSettingsArgs);
3315 loadInfo.mOriginAttributes = OriginAttributes();
3316 loadInfo.mIsThirdPartyContext = false;
3317 loadInfo.mIsOn3PCBExceptionList = false;
3320 MOZ_ASSERT(loadInfo.mLoadingPrincipal);
3321 MOZ_ASSERT(isChrome || !loadInfo.mDomain.IsEmpty());
3323 if (!loadInfo.mLoadGroup || aLoadGroupBehavior == OverrideLoadGroup) {
3324 OverrideLoadInfoLoadGroup(loadInfo, loadInfo.mLoadingPrincipal);
3326 MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(loadInfo.mLoadGroup,
3327 loadInfo.mLoadingPrincipal));
3329 // Top level workers' main script use the document charset for the script
3330 // uri encoding.
3331 nsCOMPtr<nsIURI> url;
3332 rv = nsContentUtils::NewURIWithDocumentCharset(
3333 getter_AddRefs(url), aScriptURL, document, loadInfo.mBaseURI);
3334 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
3336 rv = ChannelFromScriptURLMainThread(
3337 loadInfo.mLoadingPrincipal, document, loadInfo.mLoadGroup, url,
3338 aWorkerType, aCredentials, clientInfo, ContentPolicyType(aWorkerKind),
3339 loadInfo.mCookieJarSettings, loadInfo.mReferrerInfo,
3340 getter_AddRefs(loadInfo.mChannel));
3341 NS_ENSURE_SUCCESS(rv, rv);
3343 rv = NS_GetFinalChannelURI(loadInfo.mChannel,
3344 getter_AddRefs(loadInfo.mResolvedScriptURI));
3345 NS_ENSURE_SUCCESS(rv, rv);
3347 // We need the correct hasStoragePermission flag for the channel here since
3348 // we will do a content blocking check later when we set the storage
3349 // principal for the worker. The channel here won't be opened when we do the
3350 // check later, so the hasStoragePermission flag is incorrect. To address
3351 // this, We copy the hasStoragePermission flag from the document if there is
3352 // a window. The worker is created as the same origin of the window. So, the
3353 // worker is supposed to have the same storage permission as the window as
3354 // well as the hasStoragePermission flag.
3355 nsCOMPtr<nsILoadInfo> channelLoadInfo = loadInfo.mChannel->LoadInfo();
3356 rv = channelLoadInfo->SetStoragePermission(
3357 loadInfo.mUsingStorageAccess ? nsILoadInfo::HasStoragePermission
3358 : nsILoadInfo::NoStoragePermission);
3359 NS_ENSURE_SUCCESS(rv, rv);
3361 rv = loadInfo.SetPrincipalsAndCSPFromChannel(loadInfo.mChannel);
3362 NS_ENSURE_SUCCESS(rv, rv);
3365 MOZ_DIAGNOSTIC_ASSERT(loadInfo.mLoadingPrincipal);
3366 MOZ_DIAGNOSTIC_ASSERT(loadInfo.PrincipalIsValid());
3368 *aLoadInfo = std::move(loadInfo);
3369 return NS_OK;
3372 // static
3373 void WorkerPrivate::OverrideLoadInfoLoadGroup(WorkerLoadInfo& aLoadInfo,
3374 nsIPrincipal* aPrincipal) {
3375 MOZ_ASSERT(!aLoadInfo.mInterfaceRequestor);
3376 MOZ_ASSERT(aLoadInfo.mLoadingPrincipal == aPrincipal);
3378 aLoadInfo.mInterfaceRequestor =
3379 new WorkerLoadInfo::InterfaceRequestor(aPrincipal, aLoadInfo.mLoadGroup);
3380 aLoadInfo.mInterfaceRequestor->MaybeAddBrowserChild(aLoadInfo.mLoadGroup);
3382 // NOTE: this defaults the load context to:
3383 // - private browsing = false
3384 // - content = true
3385 // - use remote tabs = false
3386 nsCOMPtr<nsILoadGroup> loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
3388 nsresult rv =
3389 loadGroup->SetNotificationCallbacks(aLoadInfo.mInterfaceRequestor);
3390 MOZ_ALWAYS_SUCCEEDS(rv);
3392 aLoadInfo.mLoadGroup = std::move(loadGroup);
3394 MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(aLoadInfo.mLoadGroup, aPrincipal));
3397 void WorkerPrivate::RunLoopNeverRan() {
3398 LOG(WorkerLog(), ("WorkerPrivate::RunLoopNeverRan [%p]", this));
3399 // RunLoopNeverRan is only called in WorkerThreadPrimaryRunnable::Run() to
3400 // handle cases
3401 // 1. Fail to get BackgroundChild for the worker thread or
3402 // 2. Fail to initialize the worker's JS context
3403 // However, mPreStartRunnables had already dispatched in
3404 // WorkerThread::SetWorkerPrivateInWorkerThread() where beforing above jobs
3405 // start. So we need to clean up these dispatched runnables for the worker
3406 // thread.
3408 auto data = mWorkerThreadAccessible.Access();
3409 RefPtr<WorkerThread> thread;
3411 MutexAutoLock lock(mMutex);
3413 if (!mPreStartRunnables.IsEmpty()) {
3414 for (const RefPtr<WorkerThreadRunnable>& runnable : mPreStartRunnables) {
3415 runnable->mCleanPreStartDispatching = true;
3417 mPreStartRunnables.Clear();
3420 // Switch State to Dead
3421 mStatus = Dead;
3422 thread = mThread;
3425 // Clear the dispatched mPreStartRunnables.
3426 if (thread && NS_HasPendingEvents(thread)) {
3427 NS_ProcessPendingEvents(nullptr);
3430 // After mStatus is set to Dead there can be no more
3431 // WorkerControlRunnables so no need to lock here.
3432 if (!mControlQueue.IsEmpty()) {
3433 WorkerRunnable* runnable = nullptr;
3434 while (mControlQueue.Pop(runnable)) {
3435 runnable->Release();
3439 // There should be no StrongWorkerRefs, child Workers, and Timeouts, but
3440 // WeakWorkerRefs could. WorkerThreadPrimaryRunnable could have created a
3441 // PerformanceStorageWorker which holds a WeakWorkerRef.
3442 // Notify WeakWorkerRefs with Dead status.
3443 NotifyWorkerRefs(Dead);
3446 void WorkerPrivate::UnrootGlobalScopes() {
3447 LOG(WorkerLog(), ("WorkerPrivate::UnrootGlobalScopes [%p]", this));
3448 auto data = mWorkerThreadAccessible.Access();
3450 RefPtr<WorkerDebuggerGlobalScope> debugScope = data->mDebuggerScope.forget();
3451 if (debugScope) {
3452 MOZ_ASSERT(debugScope->mWorkerPrivate == this);
3454 RefPtr<WorkerGlobalScope> scope = data->mScope.forget();
3455 if (scope) {
3456 MOZ_ASSERT(scope->mWorkerPrivate == this);
3460 void WorkerPrivate::DoRunLoop(JSContext* aCx) {
3461 LOG(WorkerLog(), ("WorkerPrivate::DoRunLoop [%p]", this));
3462 auto data = mWorkerThreadAccessible.Access();
3463 MOZ_RELEASE_ASSERT(!GetExecutionManager());
3465 RefPtr<WorkerThread> thread;
3467 MutexAutoLock lock(mMutex);
3468 mJSContext = aCx;
3469 // mThread is set before we enter, and is never changed during DoRunLoop.
3470 // copy to local so we don't trigger mutex analysis
3471 MOZ_ASSERT(mThread);
3472 thread = mThread;
3474 MOZ_ASSERT(mStatus == Pending);
3475 mStatus = Running;
3477 // Now, start to run the event loop, mPreStartRunnables can be cleared,
3478 // since when get here, Worker initialization has done successfully.
3479 mPreStartRunnables.Clear();
3482 // Create IPC between the content process worker thread and the parent
3483 // process background thread for non-life cycle related operations of
3484 // SharedWorker/ServiceWorker
3485 if (mChildEp.IsValid()) {
3486 mRemoteWorkerNonLifeCycleOpController =
3487 RemoteWorkerNonLifeCycleOpControllerChild::Create();
3488 MOZ_ASSERT_DEBUG_OR_FUZZING(mRemoteWorkerNonLifeCycleOpController);
3489 mChildEp.Bind(mRemoteWorkerNonLifeCycleOpController);
3492 // Now that we've done that, we can go ahead and set up our AutoJSAPI. We
3493 // can't before this point, because it can't find the right JSContext before
3494 // then, since it gets it from our mJSContext.
3495 AutoJSAPI jsapi;
3496 jsapi.Init();
3497 MOZ_ASSERT(jsapi.cx() == aCx);
3499 EnableMemoryReporter();
3501 InitializeGCTimers();
3503 bool checkFinalGCCC =
3504 StaticPrefs::dom_workers_GCCC_on_potentially_last_event();
3506 bool debuggerRunnablesPending = false;
3507 bool normalRunnablesPending = false;
3508 auto noRunnablesPendingAndKeepAlive =
3509 [&debuggerRunnablesPending, &normalRunnablesPending, &thread, this]()
3510 MOZ_REQUIRES(mMutex) {
3511 // We want to keep both pending flags always updated while looping.
3512 debuggerRunnablesPending = !mDebuggerQueue.IsEmpty();
3513 normalRunnablesPending = NS_HasPendingEvents(thread);
3515 bool anyRunnablesPending = !mControlQueue.IsEmpty() ||
3516 debuggerRunnablesPending ||
3517 normalRunnablesPending;
3518 bool keepWorkerAlive = mStatus == Running || HasActiveWorkerRefs();
3520 return (!anyRunnablesPending && keepWorkerAlive);
3523 for (;;) {
3524 WorkerStatus currentStatus;
3526 if (checkFinalGCCC) {
3527 // If we get here after the last event ran but someone holds a WorkerRef
3528 // and there is no other logic to release that WorkerRef than lazily
3529 // through GC/CC, we might block forever on the next WaitForWorkerEvents.
3530 // Every object holding a WorkerRef should really have a straight,
3531 // deterministic line from the WorkerRef's callback being invoked to the
3532 // WorkerRef being released which is supported by strong-references that
3533 // can't form a cycle.
3534 bool mayNeedFinalGCCC = false;
3536 MutexAutoLock lock(mMutex);
3538 currentStatus = mStatus;
3539 mayNeedFinalGCCC =
3540 (mStatus >= Canceling && HasActiveWorkerRefs() &&
3541 !debuggerRunnablesPending && !normalRunnablesPending &&
3542 data->mPerformedShutdownAfterLastContentTaskExecuted);
3544 if (mayNeedFinalGCCC) {
3545 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
3546 // WorkerRef::ReleaseWorker will check this flag via
3547 // AssertIsNotPotentiallyLastGCCCRunning
3548 data->mIsPotentiallyLastGCCCRunning = true;
3549 #endif
3550 // GarbageCollectInternal will trigger both GC and CC
3551 GarbageCollectInternal(aCx, true /* aShrinking */,
3552 true /* aCollectChildren */);
3553 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
3554 data->mIsPotentiallyLastGCCCRunning = false;
3555 #endif
3560 MutexAutoLock lock(mMutex);
3561 if (checkFinalGCCC && currentStatus != mStatus) {
3562 // Something moved our status while we were supposed to check for a
3563 // potentially needed GC/CC. Just check again.
3564 continue;
3567 // Wait for a runnable to arrive that we can execute, or for it to be okay
3568 // to shutdown this worker once all holders have been removed.
3569 // Holders may be removed from inside normal runnables, but we don't
3570 // check for that after processing normal runnables, so we need to let
3571 // control flow to the shutdown logic without blocking.
3572 while (noRunnablesPendingAndKeepAlive()) {
3573 // We pop out to this loop when there are no pending events.
3574 // If we don't reset these, we may not re-enter ProcessNextEvent()
3575 // until we have events to process, and it may seem like we have
3576 // an event running for a very long time.
3577 thread->SetRunningEventDelay(TimeDuration(), TimeStamp());
3579 mWorkerLoopIsIdle = true;
3581 WaitForWorkerEvents();
3583 mWorkerLoopIsIdle = false;
3586 auto result = ProcessAllControlRunnablesLocked();
3587 if (result != ProcessAllControlRunnablesResult::Nothing) {
3588 // Update all saved runnable flags for side effect for the
3589 // loop check about transitioning to Killing below.
3590 (void)noRunnablesPendingAndKeepAlive();
3593 currentStatus = mStatus;
3596 // Status transitions to Closing/Canceling and there are no SyncLoops,
3597 // set global start dying, disconnect EventTargetObjects and
3598 // WebTaskScheduler.
3599 // The Worker might switch to the "Killing" immediately then directly exits
3600 // DoRunLoop(). Before exiting the DoRunLoop(), explicitly disconnecting the
3601 // WorkerGlobalScope's EventTargetObject here would help to fail runnable
3602 // dispatching when the Worker is in the status changing.
3603 if (currentStatus >= Closing &&
3604 !data->mPerformedShutdownAfterLastContentTaskExecuted) {
3605 data->mPerformedShutdownAfterLastContentTaskExecuted.Flip();
3606 if (data->mScope) {
3607 data->mScope->NoteTerminating();
3608 data->mScope->DisconnectGlobalTeardownObservers();
3609 if (data->mScope->GetExistingScheduler()) {
3610 data->mScope->GetExistingScheduler()->Disconnect();
3615 // Transition from Canceling to Killing and exit this loop when:
3616 // * All (non-weak) WorkerRefs have been released.
3617 // * There are no runnables pending. This is intended to let same-thread
3618 // dispatches as part of cleanup be able to run to completion, but any
3619 // logic that still wants async things to happen should be holding a
3620 // StrongWorkerRef.
3621 if (currentStatus != Running && !HasActiveWorkerRefs() &&
3622 !normalRunnablesPending && !debuggerRunnablesPending) {
3623 // Now we are ready to kill the worker thread.
3624 if (currentStatus == Canceling) {
3625 NotifyInternal(Killing);
3627 #ifdef DEBUG
3629 MutexAutoLock lock(mMutex);
3630 currentStatus = mStatus;
3632 MOZ_ASSERT(currentStatus == Killing);
3633 #else
3634 currentStatus = Killing;
3635 #endif
3638 // If we're supposed to die then we should exit the loop.
3639 if (currentStatus == Killing) {
3640 // We are about to destroy worker, report all use counters.
3641 ReportUseCounters();
3643 // Flush uncaught rejections immediately, without
3644 // waiting for a next tick.
3645 PromiseDebugging::FlushUncaughtRejections();
3647 ShutdownGCTimers();
3649 DisableMemoryReporter();
3651 // Move the timer out with the mutex held but only drop the ref
3652 // when the mutex is not held.
3653 nsCOMPtr<nsITimer> timer;
3655 MutexAutoLock lock(mMutex);
3657 mStatus = Dead;
3658 mJSContext = nullptr;
3659 mDebuggerInterruptTimer.swap(timer);
3661 timer = nullptr;
3663 // After mStatus is set to Dead there can be no more
3664 // WorkerControlRunnables so no need to lock here.
3665 if (!mControlQueue.IsEmpty()) {
3666 LOG(WorkerLog(),
3667 ("WorkerPrivate::DoRunLoop [%p] dropping control runnables in "
3668 "Dead status",
3669 this));
3670 WorkerRunnable* runnable = nullptr;
3671 while (mControlQueue.Pop(runnable)) {
3672 runnable->Cancel();
3673 runnable->Release();
3677 // We do not need the timeouts any more, they have been canceled
3678 // by NotifyInternal(Killing) above if they were active.
3679 UnlinkTimeouts();
3681 return;
3685 if (debuggerRunnablesPending || normalRunnablesPending) {
3686 // Start the periodic GC timer if it is not already running.
3687 SetGCTimerMode(PeriodicTimer);
3690 if (debuggerRunnablesPending) {
3691 ProcessSingleDebuggerRunnable();
3694 MutexAutoLock lock(mMutex);
3695 debuggerRunnablesPending = !mDebuggerQueue.IsEmpty();
3698 if (debuggerRunnablesPending) {
3699 WorkerDebuggerGlobalScope* globalScope = DebuggerGlobalScope();
3700 // If the worker was canceled before ever creating its content global
3701 // then mCancelBeforeWorkerScopeConstructed could have been flipped and
3702 // all of the WorkerDebuggerRunnables canceled, so the debugger global
3703 // would never have been created.
3704 if (globalScope) {
3705 // Now *might* be a good time to GC. Let the JS engine make the
3706 // decision.
3707 JSAutoRealm ar(aCx, globalScope->GetGlobalJSObject());
3708 JS_MaybeGC(aCx);
3711 } else if (normalRunnablesPending) {
3712 // Process a single runnable from the main queue.
3713 NS_ProcessNextEvent(thread, false);
3715 normalRunnablesPending = NS_HasPendingEvents(thread);
3716 if (normalRunnablesPending && GlobalScope()) {
3717 // Now *might* be a good time to GC. Let the JS engine make the
3718 // decision.
3719 JSAutoRealm ar(aCx, GlobalScope()->GetGlobalJSObject());
3720 JS_MaybeGC(aCx);
3724 // Checking the background actors if needed, any runnable execution could
3725 // release background actors which blocks GC/CC on
3726 // WorkerPrivate::mParentEventTargetRef.
3727 if (currentStatus < Canceling) {
3728 UpdateCCFlag(CCFlag::CheckBackgroundActors);
3731 if (!debuggerRunnablesPending && !normalRunnablesPending) {
3732 // Both the debugger event queue and the normal event queue has been
3733 // exhausted, cancel the periodic GC timer and schedule the idle GC timer.
3734 SetGCTimerMode(IdleTimer);
3737 // If the worker thread is spamming the main thread faster than it can
3738 // process the work, then pause the worker thread until the main thread
3739 // catches up.
3740 size_t queuedEvents = mMainThreadEventTargetForMessaging->Length() +
3741 mMainThreadDebuggeeEventTarget->Length();
3742 if (queuedEvents > 5000) {
3743 // Note, postMessage uses mMainThreadDebuggeeEventTarget!
3744 mMainThreadDebuggeeEventTarget->AwaitIdle();
3748 MOZ_CRASH("Shouldn't get here!");
3751 namespace {
3753 * If there is a current CycleCollectedJSContext, return its recursion depth,
3754 * otherwise return 1.
3756 * In the edge case where a worker is starting up so late that PBackground is
3757 * already shutting down, the cycle collected context will never be created,
3758 * but we will need to drain the event loop in ClearMainEventQueue. This will
3759 * result in a normal NS_ProcessPendingEvents invocation which will call
3760 * WorkerPrivate::OnProcessNextEvent and WorkerPrivate::AfterProcessNextEvent
3761 * which want to handle the need to process control runnables and perform a
3762 * sanity check assertion, respectively.
3764 * We claim a depth of 1 when there's no CCJS because this most corresponds to
3765 * reality, but this doesn't meant that other code might want to drain various
3766 * runnable queues as part of this cleanup.
3768 uint32_t GetEffectiveEventLoopRecursionDepth() {
3769 auto* ccjs = CycleCollectedJSContext::Get();
3770 if (ccjs) {
3771 return ccjs->RecursionDepth();
3774 return 1;
3777 } // namespace
3779 void WorkerPrivate::OnProcessNextEvent() {
3780 AssertIsOnWorkerThread();
3782 uint32_t recursionDepth = GetEffectiveEventLoopRecursionDepth();
3783 MOZ_ASSERT(recursionDepth);
3785 // Normally we process control runnables in DoRunLoop or RunCurrentSyncLoop.
3786 // However, it's possible that non-worker C++ could spin its own nested event
3787 // loop, and in that case we must ensure that we continue to process control
3788 // runnables here.
3789 if (recursionDepth > 1 && mSyncLoopStack.Length() < recursionDepth - 1) {
3790 Unused << ProcessAllControlRunnables();
3791 // There's no running JS, and no state to revalidate, so we can ignore the
3792 // return value.
3796 void WorkerPrivate::AfterProcessNextEvent() {
3797 AssertIsOnWorkerThread();
3798 MOZ_ASSERT(GetEffectiveEventLoopRecursionDepth());
3801 nsISerialEventTarget* WorkerPrivate::MainThreadEventTargetForMessaging() {
3802 return mMainThreadEventTargetForMessaging;
3805 nsresult WorkerPrivate::DispatchToMainThreadForMessaging(nsIRunnable* aRunnable,
3806 uint32_t aFlags) {
3807 nsCOMPtr<nsIRunnable> r = aRunnable;
3808 return DispatchToMainThreadForMessaging(r.forget(), aFlags);
3811 nsresult WorkerPrivate::DispatchToMainThreadForMessaging(
3812 already_AddRefed<nsIRunnable> aRunnable, uint32_t aFlags) {
3813 return mMainThreadEventTargetForMessaging->Dispatch(std::move(aRunnable),
3814 aFlags);
3817 nsISerialEventTarget* WorkerPrivate::MainThreadEventTarget() {
3818 return mMainThreadEventTarget;
3821 nsresult WorkerPrivate::DispatchToMainThread(nsIRunnable* aRunnable,
3822 uint32_t aFlags) {
3823 nsCOMPtr<nsIRunnable> r = aRunnable;
3824 return DispatchToMainThread(r.forget(), aFlags);
3827 nsresult WorkerPrivate::DispatchToMainThread(
3828 already_AddRefed<nsIRunnable> aRunnable, uint32_t aFlags) {
3829 return mMainThreadEventTarget->Dispatch(std::move(aRunnable), aFlags);
3832 nsresult WorkerPrivate::DispatchDebuggeeToMainThread(
3833 already_AddRefed<WorkerRunnable> aRunnable, uint32_t aFlags) {
3834 RefPtr<WorkerRunnable> debuggeeRunnable = std::move(aRunnable);
3835 MOZ_ASSERT_DEBUG_OR_FUZZING(debuggeeRunnable->IsDebuggeeRunnable());
3836 return mMainThreadDebuggeeEventTarget->Dispatch(debuggeeRunnable.forget(),
3837 aFlags);
3840 nsISerialEventTarget* WorkerPrivate::ControlEventTarget() {
3841 return mWorkerControlEventTarget;
3844 nsISerialEventTarget* WorkerPrivate::HybridEventTarget() {
3845 return mWorkerHybridEventTarget;
3848 ClientType WorkerPrivate::GetClientType() const {
3849 switch (Kind()) {
3850 case WorkerKindDedicated:
3851 return ClientType::Worker;
3852 case WorkerKindShared:
3853 return ClientType::Sharedworker;
3854 case WorkerKindService:
3855 return ClientType::Serviceworker;
3856 default:
3857 MOZ_CRASH("unknown worker type!");
3861 UniquePtr<ClientSource> WorkerPrivate::CreateClientSource() {
3862 auto data = mWorkerThreadAccessible.Access();
3863 MOZ_ASSERT(!data->mScope, "Client should be created before the global");
3865 UniquePtr<ClientSource> clientSource;
3866 if (IsServiceWorker()) {
3867 clientSource = ClientManager::CreateSourceFromInfo(
3868 GetSourceInfo(), mWorkerHybridEventTarget);
3869 } else {
3870 clientSource = ClientManager::CreateSource(
3871 GetClientType(), mWorkerHybridEventTarget,
3872 StoragePrincipalHelper::ShouldUsePartitionPrincipalForServiceWorker(
3873 this)
3874 ? GetPartitionedPrincipalInfo()
3875 : GetPrincipalInfo());
3877 MOZ_DIAGNOSTIC_ASSERT(clientSource);
3879 clientSource->SetAgentClusterId(mAgentClusterId);
3881 if (data->mFrozen) {
3882 clientSource->Freeze();
3885 // Shortly after the client is reserved we will try loading the main script
3886 // for the worker. This may get intercepted by the ServiceWorkerManager
3887 // which will then try to create a ClientHandle. Its actually possible for
3888 // the main thread to create this ClientHandle before our IPC message creating
3889 // the ClientSource completes. To avoid this race we synchronously ping our
3890 // parent Client actor here. This ensure the worker ClientSource is created
3891 // in the parent before the main thread might try reaching it with a
3892 // ClientHandle.
3894 // An alternative solution would have been to handle the out-of-order
3895 // operations on the parent side. We could have created a small window where
3896 // we allow ClientHandle objects to exist without a ClientSource. We would
3897 // then time out these handles if they stayed orphaned for too long. This
3898 // approach would be much more complex, but also avoid this extra bit of
3899 // latency when starting workers.
3901 // Note, we only have to do this for workers that can be controlled by a
3902 // service worker. So avoid the sync overhead here if we are starting a
3903 // service worker or a chrome worker.
3904 if (Kind() != WorkerKindService && !IsChromeWorker()) {
3905 clientSource->WorkerSyncPing(this);
3908 return clientSource;
3911 bool WorkerPrivate::EnsureCSPEventListener() {
3912 if (!mCSPEventListener) {
3913 mCSPEventListener = WorkerCSPEventListener::Create(this);
3914 if (NS_WARN_IF(!mCSPEventListener)) {
3915 return false;
3918 return true;
3921 nsICSPEventListener* WorkerPrivate::CSPEventListener() const {
3922 MOZ_ASSERT(mCSPEventListener);
3923 return mCSPEventListener;
3926 void WorkerPrivate::EnsurePerformanceStorage() {
3927 AssertIsOnWorkerThread();
3929 if (!mPerformanceStorage) {
3930 mPerformanceStorage = PerformanceStorageWorker::Create(this);
3934 bool WorkerPrivate::GetExecutionGranted() const {
3935 auto data = mWorkerThreadAccessible.Access();
3936 return data->mJSThreadExecutionGranted;
3939 void WorkerPrivate::SetExecutionGranted(bool aGranted) {
3940 auto data = mWorkerThreadAccessible.Access();
3941 data->mJSThreadExecutionGranted = aGranted;
3944 void WorkerPrivate::ScheduleTimeSliceExpiration(uint32_t aDelay) {
3945 auto data = mWorkerThreadAccessible.Access();
3947 if (!data->mTSTimer) {
3948 data->mTSTimer = NS_NewTimer();
3949 MOZ_ALWAYS_SUCCEEDS(data->mTSTimer->SetTarget(mWorkerControlEventTarget));
3952 // Whenever an event is scheduled on the WorkerControlEventTarget an
3953 // interrupt is automatically requested which causes us to yield JS execution
3954 // and the next JS execution in the queue to execute.
3955 // This allows for simple code reuse of the existing interrupt callback code
3956 // used for control events.
3957 MOZ_ALWAYS_SUCCEEDS(data->mTSTimer->InitWithNamedFuncCallback(
3958 [](nsITimer* Timer, void* aClosure) { return; }, nullptr, aDelay,
3959 nsITimer::TYPE_ONE_SHOT, "TimeSliceExpirationTimer"));
3962 void WorkerPrivate::CancelTimeSliceExpiration() {
3963 auto data = mWorkerThreadAccessible.Access();
3964 MOZ_ALWAYS_SUCCEEDS(data->mTSTimer->Cancel());
3967 JSExecutionManager* WorkerPrivate::GetExecutionManager() const {
3968 auto data = mWorkerThreadAccessible.Access();
3969 return data->mExecutionManager.get();
3972 void WorkerPrivate::SetExecutionManager(JSExecutionManager* aManager) {
3973 auto data = mWorkerThreadAccessible.Access();
3974 data->mExecutionManager = aManager;
3977 void WorkerPrivate::ExecutionReady() {
3978 auto data = mWorkerThreadAccessible.Access();
3980 MutexAutoLock lock(mMutex);
3981 if (mStatus >= Canceling) {
3982 return;
3986 data->mScope->MutableClientSourceRef().WorkerExecutionReady(this);
3988 if (ExtensionAPIAllowed()) {
3989 extensions::CreateAndDispatchInitWorkerContextRunnable();
3993 void WorkerPrivate::InitializeGCTimers() {
3994 auto data = mWorkerThreadAccessible.Access();
3996 // We need timers for GC. The basic plan is to run a non-shrinking GC
3997 // periodically (PERIODIC_GC_TIMER_DELAY_SEC) while the worker is running.
3998 // Once the worker goes idle we set a short (IDLE_GC_TIMER_DELAY_SEC) timer to
3999 // run a shrinking GC.
4000 data->mPeriodicGCTimer = NS_NewTimer();
4001 data->mIdleGCTimer = NS_NewTimer();
4003 data->mPeriodicGCTimerRunning = false;
4004 data->mIdleGCTimerRunning = false;
4007 void WorkerPrivate::SetGCTimerMode(GCTimerMode aMode) {
4008 auto data = mWorkerThreadAccessible.Access();
4010 if (!data->mPeriodicGCTimer || !data->mIdleGCTimer) {
4011 // GC timers have been cleared already.
4012 return;
4015 if (aMode == NoTimer) {
4016 MOZ_ALWAYS_SUCCEEDS(data->mPeriodicGCTimer->Cancel());
4017 data->mPeriodicGCTimerRunning = false;
4018 MOZ_ALWAYS_SUCCEEDS(data->mIdleGCTimer->Cancel());
4019 data->mIdleGCTimerRunning = false;
4020 return;
4023 WorkerStatus status;
4025 MutexAutoLock lock(mMutex);
4026 status = mStatus;
4029 if (status >= Killing) {
4030 ShutdownGCTimers();
4031 return;
4034 // If the idle timer is running, don't cancel it when the periodic timer
4035 // is scheduled since we do want shrinking GC to be called occasionally.
4036 if (aMode == PeriodicTimer && data->mPeriodicGCTimerRunning) {
4037 return;
4040 if (aMode == IdleTimer) {
4041 if (!data->mPeriodicGCTimerRunning) {
4042 // Since running idle GC cancels both GC timers, after that we want
4043 // first at least periodic GC timer getting activated, since that tells
4044 // us that there have been some non-control tasks to process. Otherwise
4045 // idle GC timer would keep running all the time.
4046 return;
4049 // Cancel the periodic timer now, since the event loop is (in the common
4050 // case) empty now.
4051 MOZ_ALWAYS_SUCCEEDS(data->mPeriodicGCTimer->Cancel());
4052 data->mPeriodicGCTimerRunning = false;
4054 if (data->mIdleGCTimerRunning) {
4055 return;
4059 MOZ_ASSERT(aMode == PeriodicTimer || aMode == IdleTimer);
4061 uint32_t delay = 0;
4062 int16_t type = nsITimer::TYPE_ONE_SHOT;
4063 nsTimerCallbackFunc callback = nullptr;
4064 const char* name = nullptr;
4065 nsITimer* timer = nullptr;
4067 if (aMode == PeriodicTimer) {
4068 delay = PERIODIC_GC_TIMER_DELAY_SEC * 1000;
4069 type = nsITimer::TYPE_REPEATING_SLACK;
4070 callback = PeriodicGCTimerCallback;
4071 name = "dom::PeriodicGCTimerCallback";
4072 timer = data->mPeriodicGCTimer;
4073 data->mPeriodicGCTimerRunning = true;
4074 LOG(WorkerLog(), ("Worker %p scheduled periodic GC timer\n", this));
4075 } else {
4076 delay = IDLE_GC_TIMER_DELAY_SEC * 1000;
4077 type = nsITimer::TYPE_ONE_SHOT;
4078 callback = IdleGCTimerCallback;
4079 name = "dom::IdleGCTimerCallback";
4080 timer = data->mIdleGCTimer;
4081 data->mIdleGCTimerRunning = true;
4082 LOG(WorkerLog(), ("Worker %p scheduled idle GC timer\n", this));
4085 MOZ_ALWAYS_SUCCEEDS(timer->SetTarget(mWorkerControlEventTarget));
4086 MOZ_ALWAYS_SUCCEEDS(
4087 timer->InitWithNamedFuncCallback(callback, this, delay, type, name));
4090 void WorkerPrivate::ShutdownGCTimers() {
4091 auto data = mWorkerThreadAccessible.Access();
4093 MOZ_ASSERT(!data->mPeriodicGCTimer == !data->mIdleGCTimer);
4095 if (!data->mPeriodicGCTimer && !data->mIdleGCTimer) {
4096 return;
4099 // Always make sure the timers are canceled.
4100 MOZ_ALWAYS_SUCCEEDS(data->mPeriodicGCTimer->Cancel());
4101 MOZ_ALWAYS_SUCCEEDS(data->mIdleGCTimer->Cancel());
4103 LOG(WorkerLog(), ("Worker %p killed the GC timers\n", this));
4105 data->mPeriodicGCTimer = nullptr;
4106 data->mIdleGCTimer = nullptr;
4107 data->mPeriodicGCTimerRunning = false;
4108 data->mIdleGCTimerRunning = false;
4111 bool WorkerPrivate::InterruptCallback(JSContext* aCx) {
4112 auto data = mWorkerThreadAccessible.Access();
4114 AutoYieldJSThreadExecution yield;
4116 // If we are here it's because a WorkerControlRunnable has been dispatched.
4117 // The runnable could be processed here or it could have already been
4118 // processed by a sync event loop.
4119 // The most important thing this method must do, is to decide if the JS
4120 // execution should continue or not. If the runnable returns an error or if
4121 // the worker status is >= Canceling, we should stop the JS execution.
4123 MOZ_ASSERT(!JS_IsExceptionPending(aCx));
4125 bool mayContinue = true;
4126 bool scheduledIdleGC = false;
4128 for (;;) {
4129 // Run all control events now.
4130 auto result = ProcessAllControlRunnables();
4131 if (result == ProcessAllControlRunnablesResult::Abort) {
4132 mayContinue = false;
4135 bool mayFreeze = data->mFrozen;
4138 MutexAutoLock lock(mMutex);
4140 if (mayFreeze) {
4141 mayFreeze = mStatus <= Running;
4144 if (mStatus >= Canceling) {
4145 mayContinue = false;
4149 if (!mayContinue || !mayFreeze) {
4150 break;
4153 // Cancel the periodic GC timer here before freezing. The idle GC timer
4154 // will clean everything up once it runs.
4155 if (!scheduledIdleGC) {
4156 SetGCTimerMode(IdleTimer);
4157 scheduledIdleGC = true;
4160 while ((mayContinue = MayContinueRunning())) {
4161 MutexAutoLock lock(mMutex);
4162 if (!mControlQueue.IsEmpty()) {
4163 break;
4166 WaitForWorkerEvents();
4170 if (!mayContinue) {
4171 // We want only uncatchable exceptions here.
4172 NS_ASSERTION(!JS_IsExceptionPending(aCx),
4173 "Should not have an exception set here!");
4174 return false;
4177 // Make sure the periodic timer gets turned back on here.
4178 SetGCTimerMode(PeriodicTimer);
4180 if (data->mDebuggerInterruptRequested) {
4181 bool debuggerRunnablesPending = false;
4183 MutexAutoLock lock(mMutex);
4184 debuggerRunnablesPending = !mDebuggerQueue.IsEmpty();
4186 if (debuggerRunnablesPending) {
4187 // Prevents interrupting the debugger's own logic unless it has called
4188 // back into content
4189 WorkerGlobalScope* globalScope = GlobalScope();
4190 if (globalScope) {
4191 JSObject* global = JS::CurrentGlobalOrNull(aCx);
4192 if (global && global == globalScope->GetGlobalJSObject()) {
4193 while (debuggerRunnablesPending) {
4194 ProcessSingleDebuggerRunnable();
4196 MutexAutoLock lock(mMutex);
4197 debuggerRunnablesPending = !mDebuggerQueue.IsEmpty();
4203 data->mDebuggerInterruptRequested = false;
4206 return true;
4209 void WorkerPrivate::CloseInternal() {
4210 AssertIsOnWorkerThread();
4211 NotifyInternal(Closing);
4214 bool WorkerPrivate::IsOnCurrentThread() {
4215 // May be called on any thread!
4217 MOZ_ASSERT(mPRThread);
4218 return PR_GetCurrentThread() == mPRThread;
4221 void WorkerPrivate::ScheduleDeletion(WorkerRanOrNot aRanOrNot) {
4222 AssertIsOnWorkerThread();
4224 // mWorkerThreadAccessible's accessor must be destructed before
4225 // the scheduled Runnable gets to run.
4226 auto data = mWorkerThreadAccessible.Access();
4227 MOZ_ASSERT(data->mChildWorkers.IsEmpty());
4229 MOZ_RELEASE_ASSERT(!data->mDeletionScheduled);
4230 data->mDeletionScheduled.Flip();
4232 MOZ_ASSERT(mSyncLoopStack.IsEmpty());
4233 MOZ_ASSERT(mPostSyncLoopOperations == 0);
4235 // If Worker is never ran, clear the mPreStartRunnables. To let the resource
4236 // hold by the pre-submmited runnables.
4237 if (WorkerNeverRan == aRanOrNot) {
4238 ClearPreStartRunnables();
4241 #ifdef DEBUG
4242 if (WorkerRan == aRanOrNot) {
4243 nsIThread* currentThread = NS_GetCurrentThread();
4244 MOZ_ASSERT(currentThread);
4245 // On the worker thread WorkerRunnable will refuse to run if not nested
4246 // on top of a WorkerThreadPrimaryRunnable.
4247 Unused << NS_WARN_IF(NS_HasPendingEvents(currentThread));
4249 #endif
4251 if (WorkerPrivate* parent = GetParent()) {
4252 RefPtr<WorkerFinishedRunnable> runnable =
4253 new WorkerFinishedRunnable(parent, this);
4254 if (!runnable->Dispatch(parent)) {
4255 NS_WARNING("Failed to dispatch runnable!");
4257 } else {
4258 if (ExtensionAPIAllowed()) {
4259 MOZ_ASSERT(IsServiceWorker());
4260 RefPtr<Runnable> extWorkerRunnable =
4261 extensions::CreateWorkerDestroyedRunnable(ServiceWorkerID(),
4262 GetBaseURI());
4263 // Dispatch as a low priority runnable.
4264 if (NS_FAILED(
4265 DispatchToMainThreadForMessaging(extWorkerRunnable.forget()))) {
4266 NS_WARNING(
4267 "Failed to dispatch runnable to notify extensions worker "
4268 "destroyed");
4272 // Note, this uses the lower priority DispatchToMainThreadForMessaging for
4273 // dispatching TopLevelWorkerFinishedRunnable to the main thread so that
4274 // other relevant runnables are guaranteed to run before it.
4275 RefPtr<TopLevelWorkerFinishedRunnable> runnable =
4276 new TopLevelWorkerFinishedRunnable(this);
4277 if (NS_FAILED(DispatchToMainThreadForMessaging(runnable.forget()))) {
4278 NS_WARNING("Failed to dispatch runnable!");
4281 // NOTE: Calling any WorkerPrivate methods (or accessing member data) after
4282 // this point is unsafe (the TopLevelWorkerFinishedRunnable just dispatched
4283 // may be able to call ClearSelfAndParentEventTargetRef on this
4284 // WorkerPrivate instance and by the time we get here the WorkerPrivate
4285 // instance destructor may have been already called).
4289 bool WorkerPrivate::CollectRuntimeStats(
4290 JS::RuntimeStats* aRtStats, bool aAnonymize) MOZ_NO_THREAD_SAFETY_ANALYSIS {
4291 // We don't have a lock to access mJSContext, but it's safe to access on this
4292 // thread.
4293 AssertIsOnWorkerThread();
4294 NS_ASSERTION(aRtStats, "Null RuntimeStats!");
4295 // We don't really own it, but it's safe to access on this thread
4296 NS_ASSERTION(mJSContext, "This must never be null!");
4298 return JS::CollectRuntimeStats(mJSContext, aRtStats, nullptr, aAnonymize);
4301 void WorkerPrivate::EnableMemoryReporter() {
4302 auto data = mWorkerThreadAccessible.Access();
4303 MOZ_ASSERT(!data->mMemoryReporter);
4305 // No need to lock here since the main thread can't race until we've
4306 // successfully registered the reporter.
4307 data->mMemoryReporter = new MemoryReporter(this);
4309 if (NS_FAILED(RegisterWeakAsyncMemoryReporter(data->mMemoryReporter))) {
4310 NS_WARNING("Failed to register memory reporter!");
4311 // No need to lock here since a failed registration means our memory
4312 // reporter can't start running. Just clean up.
4313 data->mMemoryReporter = nullptr;
4317 void WorkerPrivate::DisableMemoryReporter() {
4318 auto data = mWorkerThreadAccessible.Access();
4320 RefPtr<MemoryReporter> memoryReporter;
4322 // Mutex protectes MemoryReporter::mWorkerPrivate which is cleared by
4323 // MemoryReporter::Disable() below.
4324 MutexAutoLock lock(mMutex);
4326 // There is nothing to do here if the memory reporter was never successfully
4327 // registered.
4328 if (!data->mMemoryReporter) {
4329 return;
4332 // We don't need this set any longer. Swap it out so that we can unregister
4333 // below.
4334 data->mMemoryReporter.swap(memoryReporter);
4336 // Next disable the memory reporter so that the main thread stops trying to
4337 // signal us.
4338 memoryReporter->Disable();
4341 // Finally unregister the memory reporter.
4342 if (NS_FAILED(UnregisterWeakMemoryReporter(memoryReporter))) {
4343 NS_WARNING("Failed to unregister memory reporter!");
4347 void WorkerPrivate::WaitForWorkerEvents() {
4348 AUTO_PROFILER_LABEL("WorkerPrivate::WaitForWorkerEvents", IDLE);
4350 AssertIsOnWorkerThread();
4351 mMutex.AssertCurrentThreadOwns();
4353 // Wait for a worker event.
4354 mCondVar.Wait();
4357 WorkerPrivate::ProcessAllControlRunnablesResult
4358 WorkerPrivate::ProcessAllControlRunnablesLocked() {
4359 AssertIsOnWorkerThread();
4360 mMutex.AssertCurrentThreadOwns();
4362 AutoYieldJSThreadExecution yield;
4364 auto result = ProcessAllControlRunnablesResult::Nothing;
4366 for (;;) {
4367 WorkerRunnable* event;
4368 if (!mControlQueue.Pop(event)) {
4369 break;
4372 MutexAutoUnlock unlock(mMutex);
4375 MOZ_ASSERT(event);
4376 AUTO_PROFILE_FOLLOWING_RUNNABLE(event);
4377 if (NS_FAILED(static_cast<nsIRunnable*>(event)->Run())) {
4378 result = ProcessAllControlRunnablesResult::Abort;
4382 if (result == ProcessAllControlRunnablesResult::Nothing) {
4383 // We ran at least one thing.
4384 result = ProcessAllControlRunnablesResult::MayContinue;
4386 event->Release();
4389 return result;
4392 void WorkerPrivate::ShutdownModuleLoader() {
4393 AssertIsOnWorkerThread();
4395 WorkerGlobalScope* globalScope = GlobalScope();
4396 if (globalScope) {
4397 if (globalScope->GetModuleLoader(nullptr)) {
4398 globalScope->GetModuleLoader(nullptr)->Shutdown();
4401 WorkerDebuggerGlobalScope* debugGlobalScope = DebuggerGlobalScope();
4402 if (debugGlobalScope) {
4403 if (debugGlobalScope->GetModuleLoader(nullptr)) {
4404 debugGlobalScope->GetModuleLoader(nullptr)->Shutdown();
4409 void WorkerPrivate::ClearPreStartRunnables() {
4410 nsTArray<RefPtr<WorkerThreadRunnable>> prestart;
4412 MutexAutoLock lock(mMutex);
4413 mPreStartRunnables.SwapElements(prestart);
4415 for (uint32_t count = prestart.Length(), index = 0; index < count; index++) {
4416 LOG(WorkerLog(), ("WorkerPrivate::ClearPreStartRunnable [%p]", this));
4417 RefPtr<WorkerRunnable> runnable = std::move(prestart[index]);
4418 runnable->Cancel();
4422 void WorkerPrivate::ProcessSingleDebuggerRunnable() {
4423 WorkerRunnable* runnable = nullptr;
4425 // Move the timer out with the mutex held but only drop the ref
4426 // when the mutex is not held.
4427 nsCOMPtr<nsITimer> timer;
4429 MutexAutoLock lock(mMutex);
4431 mDebuggerQueue.Pop(runnable);
4433 mDebuggerInterruptTimer.swap(timer);
4435 timer = nullptr;
4438 MOZ_ASSERT(runnable);
4439 AUTO_PROFILE_FOLLOWING_RUNNABLE(runnable);
4440 static_cast<nsIRunnable*>(runnable)->Run();
4442 runnable->Release();
4444 CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
4445 ccjs->PerformDebuggerMicroTaskCheckpoint();
4448 void WorkerPrivate::ClearDebuggerEventQueue() {
4449 bool debuggerRunnablesPending = false;
4451 MutexAutoLock lock(mMutex);
4452 debuggerRunnablesPending = !mDebuggerQueue.IsEmpty();
4454 while (debuggerRunnablesPending) {
4455 WorkerRunnable* runnable = nullptr;
4457 MutexAutoLock lock(mMutex);
4458 mDebuggerQueue.Pop(runnable);
4459 debuggerRunnablesPending = !mDebuggerQueue.IsEmpty();
4461 // It should be ok to simply release the runnable, without running it.
4462 runnable->Release();
4464 // Move the timer out with the mutex held but only drop the ref
4465 // when the mutex is not held.
4466 nsCOMPtr<nsITimer> timer;
4468 MutexAutoLock lock(mMutex);
4469 mDebuggerInterruptTimer.swap(timer);
4471 timer = nullptr;
4475 bool WorkerPrivate::FreezeInternal() {
4476 auto data = mWorkerThreadAccessible.Access();
4477 NS_ASSERTION(!data->mFrozen, "Already frozen!");
4479 AutoYieldJSThreadExecution yield;
4481 // The worker can freeze even if it failed to run (and doesn't have a global).
4482 if (data->mScope) {
4483 data->mScope->MutableClientSourceRef().Freeze();
4486 data->mFrozen = true;
4488 for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
4489 data->mChildWorkers[index]->Freeze(nullptr);
4492 if (StaticPrefs::dom_workers_timeoutmanager() && XRE_IsContentProcess()) {
4493 auto* timeoutManager =
4494 data->mScope ? data->mScope->GetTimeoutManager() : nullptr;
4495 if (timeoutManager) {
4496 timeoutManager->Suspend();
4500 return true;
4503 bool WorkerPrivate::ThawInternal() {
4504 auto data = mWorkerThreadAccessible.Access();
4505 NS_ASSERTION(data->mFrozen, "Not yet frozen!");
4507 for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
4508 data->mChildWorkers[index]->Thaw(nullptr);
4511 data->mFrozen = false;
4513 // The worker can thaw even if it failed to run (and doesn't have a global).
4514 if (data->mScope) {
4515 data->mScope->MutableClientSourceRef().Thaw();
4518 if (StaticPrefs::dom_workers_timeoutmanager() && XRE_IsContentProcess()) {
4519 auto* timeoutManager =
4520 data->mScope ? data->mScope->GetTimeoutManager() : nullptr;
4521 if (timeoutManager) {
4522 timeoutManager->Resume();
4526 return true;
4529 bool WorkerPrivate::ChangeBackgroundStateInternal(bool aIsBackground) {
4530 AssertIsOnWorkerThread();
4531 mIsInBackground = aIsBackground;
4532 auto data = mWorkerThreadAccessible.Access();
4533 for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
4534 if (aIsBackground) {
4535 data->mChildWorkers[index]->SetIsRunningInBackground();
4536 } else {
4537 data->mChildWorkers[index]->SetIsRunningInForeground();
4540 return true;
4543 void WorkerPrivate::PropagateStorageAccessPermissionGrantedInternal() {
4544 auto data = mWorkerThreadAccessible.Access();
4546 mLoadInfo.mUseRegularPrincipal = true;
4547 mLoadInfo.mUsingStorageAccess = true;
4549 WorkerGlobalScope* globalScope = GlobalScope();
4550 if (globalScope) {
4551 globalScope->StorageAccessPermissionGranted();
4554 for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
4555 data->mChildWorkers[index]->PropagateStorageAccessPermissionGranted();
4559 void WorkerPrivate::TraverseTimeouts(nsCycleCollectionTraversalCallback& cb) {
4560 auto data = mWorkerThreadAccessible.Access();
4561 for (uint32_t i = 0; i < data->mTimeouts.Length(); ++i) {
4562 // TODO(erahm): No idea what's going on here.
4563 TimeoutInfo* tmp = data->mTimeouts[i].get();
4564 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHandler)
4568 void WorkerPrivate::UnlinkTimeouts() {
4569 auto data = mWorkerThreadAccessible.Access();
4570 data->mTimeouts.Clear();
4573 bool WorkerPrivate::AddChildWorker(WorkerPrivate& aChildWorker) {
4574 auto data = mWorkerThreadAccessible.Access();
4576 #ifdef DEBUG
4578 WorkerStatus currentStatus;
4580 MutexAutoLock lock(mMutex);
4581 currentStatus = mStatus;
4584 MOZ_ASSERT(currentStatus == Running);
4586 #endif
4588 NS_ASSERTION(!data->mChildWorkers.Contains(&aChildWorker),
4589 "Already know about this one!");
4590 data->mChildWorkers.AppendElement(&aChildWorker);
4592 if (data->mChildWorkers.Length() == 1) {
4593 UpdateCCFlag(CCFlag::IneligibleForChildWorker);
4596 return true;
4599 void WorkerPrivate::RemoveChildWorker(WorkerPrivate& aChildWorker) {
4600 auto data = mWorkerThreadAccessible.Access();
4602 NS_ASSERTION(data->mChildWorkers.Contains(&aChildWorker),
4603 "Didn't know about this one!");
4604 data->mChildWorkers.RemoveElement(&aChildWorker);
4606 if (data->mChildWorkers.IsEmpty()) {
4607 UpdateCCFlag(CCFlag::EligibleForChildWorker);
4611 bool WorkerPrivate::AddWorkerRef(WorkerRef* aWorkerRef,
4612 WorkerStatus aFailStatus) {
4613 MOZ_ASSERT(aWorkerRef);
4614 auto data = mWorkerThreadAccessible.Access();
4617 MutexAutoLock lock(mMutex);
4619 LOG(WorkerLog(),
4620 ("WorkerPrivate::AddWorkerRef [%p] mStatus: %u, aFailStatus: (%u)",
4621 this, static_cast<uint8_t>(mStatus),
4622 static_cast<uint8_t>(aFailStatus)));
4624 if (mStatus >= aFailStatus) {
4625 return false;
4628 // We shouldn't create strong references to workers before their main loop
4629 // begins running. Strong references must be disposed of on the worker
4630 // thread, so strong references from other threads use a control runnable
4631 // for that purpose. If the worker fails to reach the main loop stage then
4632 // no control runnables get run and it would be impossible to get rid of the
4633 // reference properly.
4634 MOZ_DIAGNOSTIC_ASSERT_IF(aWorkerRef->IsPreventingShutdown(),
4635 mStatus >= WorkerStatus::Running);
4638 MOZ_ASSERT(!data->mWorkerRefs.Contains(aWorkerRef),
4639 "Already know about this one!");
4641 if (aWorkerRef->IsPreventingShutdown()) {
4642 data->mNumWorkerRefsPreventingShutdownStart += 1;
4643 if (data->mNumWorkerRefsPreventingShutdownStart == 1) {
4644 UpdateCCFlag(CCFlag::IneligibleForWorkerRef);
4648 data->mWorkerRefs.AppendElement(aWorkerRef);
4649 return true;
4652 void WorkerPrivate::RemoveWorkerRef(WorkerRef* aWorkerRef) {
4653 MOZ_ASSERT(aWorkerRef);
4654 LOG(WorkerLog(),
4655 ("WorkerPrivate::RemoveWorkerRef [%p] aWorkerRef: %p", this, aWorkerRef));
4656 auto data = mWorkerThreadAccessible.Access();
4658 MOZ_ASSERT(data->mWorkerRefs.Contains(aWorkerRef),
4659 "Didn't know about this one!");
4660 data->mWorkerRefs.RemoveElement(aWorkerRef);
4662 if (aWorkerRef->IsPreventingShutdown()) {
4663 data->mNumWorkerRefsPreventingShutdownStart -= 1;
4664 if (!data->mNumWorkerRefsPreventingShutdownStart) {
4665 UpdateCCFlag(CCFlag::EligibleForWorkerRef);
4670 void WorkerPrivate::NotifyWorkerRefs(WorkerStatus aStatus) {
4671 auto data = mWorkerThreadAccessible.Access();
4673 NS_ASSERTION(aStatus > Closing, "Bad status!");
4675 LOG(WorkerLog(), ("WorkerPrivate::NotifyWorkerRefs [%p] aStatus: %u", this,
4676 static_cast<uint8_t>(aStatus)));
4678 for (auto* workerRef : data->mWorkerRefs.ForwardRange()) {
4679 LOG(WorkerLog(), ("WorkerPrivate::NotifyWorkerRefs [%p] WorkerRefs(%s %p)",
4680 this, workerRef->mName, workerRef));
4681 workerRef->Notify();
4684 AutoTArray<CheckedUnsafePtr<WorkerPrivate>, 10> children;
4685 children.AppendElements(data->mChildWorkers);
4687 for (uint32_t index = 0; index < children.Length(); index++) {
4688 if (!children[index]->Notify(aStatus)) {
4689 NS_WARNING("Failed to notify child worker!");
4694 nsresult WorkerPrivate::RegisterShutdownTask(nsITargetShutdownTask* aTask) {
4695 NS_ENSURE_ARG(aTask);
4697 MutexAutoLock lock(mMutex);
4699 // If we've already started running shutdown tasks, don't allow registering
4700 // new ones.
4701 if (mShutdownTasksRun) {
4702 return NS_ERROR_UNEXPECTED;
4705 MOZ_ASSERT(!mShutdownTasks.Contains(aTask));
4706 mShutdownTasks.AppendElement(aTask);
4707 return NS_OK;
4710 nsresult WorkerPrivate::UnregisterShutdownTask(nsITargetShutdownTask* aTask) {
4711 NS_ENSURE_ARG(aTask);
4713 MutexAutoLock lock(mMutex);
4715 // We've already started running shutdown tasks, so can't unregister them
4716 // anymore.
4717 if (mShutdownTasksRun) {
4718 return NS_ERROR_UNEXPECTED;
4721 return mShutdownTasks.RemoveElement(aTask) ? NS_OK : NS_ERROR_UNEXPECTED;
4724 void WorkerPrivate::RunShutdownTasks() {
4725 nsTArray<nsCOMPtr<nsITargetShutdownTask>> shutdownTasks;
4728 MutexAutoLock lock(mMutex);
4729 shutdownTasks = std::move(mShutdownTasks);
4730 mShutdownTasks.Clear();
4731 mShutdownTasksRun = true;
4734 for (auto& task : shutdownTasks) {
4735 task->TargetShutdown();
4737 mWorkerHybridEventTarget->ForgetWorkerPrivate(this);
4740 RefPtr<WorkerParentRef> WorkerPrivate::GetWorkerParentRef() const {
4741 RefPtr<WorkerParentRef> ref(mParentRef);
4742 return ref;
4745 void WorkerPrivate::AdjustNonblockingCCBackgroundActorCount(int32_t aCount) {
4746 AssertIsOnWorkerThread();
4747 auto data = mWorkerThreadAccessible.Access();
4748 LOGV(("WorkerPrivate::AdjustNonblockingCCBackgroundActors [%p] (%d/%u)", this,
4749 aCount, data->mNonblockingCCBackgroundActorCount));
4751 #ifdef DEBUG
4752 if (aCount < 0) {
4753 MOZ_ASSERT(data->mNonblockingCCBackgroundActorCount >=
4754 (uint32_t)abs(aCount));
4756 #endif
4758 data->mNonblockingCCBackgroundActorCount += aCount;
4761 void WorkerPrivate::UpdateCCFlag(const CCFlag aFlag) {
4762 AssertIsOnWorkerThread();
4764 auto data = mWorkerThreadAccessible.Access();
4766 #ifdef DEBUG
4767 switch (aFlag) {
4768 case CCFlag::EligibleForWorkerRef: {
4769 MOZ_ASSERT(!data->mNumWorkerRefsPreventingShutdownStart);
4770 break;
4772 case CCFlag::IneligibleForWorkerRef: {
4773 MOZ_ASSERT(data->mNumWorkerRefsPreventingShutdownStart);
4774 break;
4776 case CCFlag::EligibleForChildWorker: {
4777 MOZ_ASSERT(data->mChildWorkers.IsEmpty());
4778 break;
4780 case CCFlag::IneligibleForChildWorker: {
4781 MOZ_ASSERT(!data->mChildWorkers.IsEmpty());
4782 break;
4784 case CCFlag::EligibleForTimeout: {
4785 MOZ_ASSERT(data->mTimeouts.IsEmpty());
4786 break;
4788 case CCFlag::IneligibleForTimeout: {
4789 MOZ_ASSERT(!data->mTimeouts.IsEmpty());
4790 break;
4792 case CCFlag::CheckBackgroundActors: {
4793 break;
4796 #endif
4799 MutexAutoLock lock(mMutex);
4800 if (mStatus > Canceling) {
4801 mCCFlagSaysEligible = true;
4802 return;
4805 auto HasBackgroundActors = [nonblockingActorCount =
4806 data->mNonblockingCCBackgroundActorCount]() {
4807 RefPtr<PBackgroundChild> backgroundChild =
4808 BackgroundChild::GetForCurrentThread();
4809 MOZ_ASSERT(backgroundChild);
4810 auto totalCount = backgroundChild->AllManagedActorsCount();
4811 LOGV(("WorkerPrivate::UpdateCCFlag HasBackgroundActors: %s(%u/%u)",
4812 totalCount > nonblockingActorCount ? "true" : "false", totalCount,
4813 nonblockingActorCount));
4815 return totalCount > nonblockingActorCount;
4818 bool eligibleForCC = data->mChildWorkers.IsEmpty() &&
4819 data->mTimeouts.IsEmpty() &&
4820 !data->mNumWorkerRefsPreventingShutdownStart;
4822 // Only checking BackgroundActors when no strong WorkerRef, ChildWorker, and
4823 // Timeout since the checking is expensive.
4824 if (eligibleForCC) {
4825 eligibleForCC = !HasBackgroundActors();
4829 MutexAutoLock lock(mMutex);
4830 mCCFlagSaysEligible = eligibleForCC;
4834 bool WorkerPrivate::IsEligibleForCC() {
4835 LOGV(("WorkerPrivate::IsEligibleForCC [%p]", this));
4836 MutexAutoLock lock(mMutex);
4837 if (mStatus > Canceling) {
4838 return true;
4841 bool hasShutdownTasks = !mShutdownTasks.IsEmpty();
4842 bool hasPendingEvents = false;
4843 if (mThread) {
4844 hasPendingEvents =
4845 NS_SUCCEEDED(mThread->HasPendingEvents(&hasPendingEvents)) &&
4846 hasPendingEvents;
4849 LOGV(("mMainThreadEventTarget: %s",
4850 mMainThreadEventTarget->IsEmpty() ? "empty" : "non-empty"));
4851 LOGV(("mMainThreadEventTargetForMessaging: %s",
4852 mMainThreadEventTargetForMessaging->IsEmpty() ? "empty" : "non-empty"));
4853 LOGV(("mMainThreadDebuggerEventTarget: %s",
4854 mMainThreadDebuggeeEventTarget->IsEmpty() ? "empty" : "non-empty"));
4855 LOGV(("mCCFlagSaysEligible: %s", mCCFlagSaysEligible ? "true" : "false"));
4856 LOGV(("hasShutdownTasks: %s", hasShutdownTasks ? "true" : "false"));
4857 LOGV(("hasPendingEvents: %s", hasPendingEvents ? "true" : "false"));
4859 return mMainThreadEventTarget->IsEmpty() &&
4860 mMainThreadEventTargetForMessaging->IsEmpty() &&
4861 mMainThreadDebuggeeEventTarget->IsEmpty() && mCCFlagSaysEligible &&
4862 !hasShutdownTasks && !hasPendingEvents && mWorkerLoopIsIdle;
4865 void WorkerPrivate::CancelAllTimeouts() {
4866 auto data = mWorkerThreadAccessible.Access();
4868 if (StaticPrefs::dom_workers_timeoutmanager() && XRE_IsContentProcess()) {
4869 auto* timeoutManager =
4870 data->mScope ? data->mScope->GetTimeoutManager() : nullptr;
4871 if (timeoutManager) {
4872 timeoutManager->ClearAllTimeouts();
4874 return;
4877 LOG(TimeoutsLog(), ("Worker %p CancelAllTimeouts.\n", this));
4879 if (data->mTimerRunning) {
4880 NS_ASSERTION(data->mTimer && data->mTimerRunnable, "Huh?!");
4881 NS_ASSERTION(!data->mTimeouts.IsEmpty(), "Huh?!");
4883 if (NS_FAILED(data->mTimer->Cancel())) {
4884 NS_WARNING("Failed to cancel timer!");
4887 for (uint32_t index = 0; index < data->mTimeouts.Length(); index++) {
4888 data->mTimeouts[index]->mCanceled = true;
4891 // If mRunningExpiredTimeouts, then the fact that they are all canceled now
4892 // means that the currently executing RunExpiredTimeouts will deal with
4893 // them. Otherwise, we need to clean them up ourselves.
4894 if (!data->mRunningExpiredTimeouts) {
4895 data->mTimeouts.Clear();
4896 UpdateCCFlag(CCFlag::EligibleForTimeout);
4899 // Set mTimerRunning false even if mRunningExpiredTimeouts is true, so that
4900 // if we get reentered under this same RunExpiredTimeouts call we don't
4901 // assert above that !mTimeouts().IsEmpty(), because that's clearly false
4902 // now.
4903 data->mTimerRunning = false;
4905 #ifdef DEBUG
4906 else if (!data->mRunningExpiredTimeouts) {
4907 NS_ASSERTION(data->mTimeouts.IsEmpty(), "Huh?!");
4909 #endif
4911 data->mTimer = nullptr;
4912 data->mTimerRunnable = nullptr;
4915 already_AddRefed<nsISerialEventTarget> WorkerPrivate::CreateNewSyncLoop(
4916 WorkerStatus aFailStatus) {
4917 AssertIsOnWorkerThread();
4918 MOZ_ASSERT(
4919 aFailStatus >= Canceling,
4920 "Sync loops can be created when the worker is in Running/Closing state!");
4922 LOG(WorkerLog(), ("WorkerPrivate::CreateNewSyncLoop [%p] failstatus: %u",
4923 this, static_cast<uint8_t>(aFailStatus)));
4925 ThreadEventQueue* queue = nullptr;
4927 MutexAutoLock lock(mMutex);
4929 if (mStatus >= aFailStatus) {
4930 return nullptr;
4932 queue = static_cast<ThreadEventQueue*>(mThread->EventQueue());
4935 nsCOMPtr<nsISerialEventTarget> nestedEventTarget = queue->PushEventQueue();
4936 MOZ_ASSERT(nestedEventTarget);
4938 RefPtr<EventTarget> workerEventTarget =
4939 new EventTarget(this, nestedEventTarget);
4942 // Modifications must be protected by mMutex in DEBUG builds, see comment
4943 // about mSyncLoopStack in WorkerPrivate.h.
4944 #ifdef DEBUG
4945 MutexAutoLock lock(mMutex);
4946 #endif
4948 mSyncLoopStack.AppendElement(new SyncLoopInfo(workerEventTarget));
4951 return workerEventTarget.forget();
4954 nsresult WorkerPrivate::RunCurrentSyncLoop() {
4955 AssertIsOnWorkerThread();
4956 LOG(WorkerLog(), ("WorkerPrivate::RunCurrentSyncLoop [%p]", this));
4957 RefPtr<WorkerThread> thread;
4958 JSContext* cx = GetJSContext();
4959 MOZ_ASSERT(cx);
4960 // mThread is set before we enter, and is never changed during
4961 // RunCurrentSyncLoop.
4963 MutexAutoLock lock(mMutex);
4964 // Copy to local so we don't trigger mutex analysis lower down
4965 // mThread is set before we enter, and is never changed during
4966 // RunCurrentSyncLoop copy to local so we don't trigger mutex analysis
4967 thread = mThread;
4970 AutoPushEventLoopGlobal eventLoopGlobal(this, cx);
4972 // This should not change between now and the time we finish running this sync
4973 // loop.
4974 uint32_t currentLoopIndex = mSyncLoopStack.Length() - 1;
4976 SyncLoopInfo* loopInfo = mSyncLoopStack[currentLoopIndex].get();
4978 AutoYieldJSThreadExecution yield;
4980 MOZ_ASSERT(loopInfo);
4981 MOZ_ASSERT(!loopInfo->mHasRun);
4982 MOZ_ASSERT(!loopInfo->mCompleted);
4984 #ifdef DEBUG
4985 loopInfo->mHasRun = true;
4986 #endif
4989 while (!loopInfo->mCompleted) {
4990 bool normalRunnablesPending = false;
4992 // Don't block with the periodic GC timer running.
4993 if (!NS_HasPendingEvents(thread)) {
4994 SetGCTimerMode(IdleTimer);
4997 // Wait for something to do.
4999 MutexAutoLock lock(mMutex);
5001 for (;;) {
5002 while (mControlQueue.IsEmpty() && !normalRunnablesPending &&
5003 !(normalRunnablesPending = NS_HasPendingEvents(thread))) {
5004 WaitForWorkerEvents();
5007 auto result = ProcessAllControlRunnablesLocked();
5008 if (result != ProcessAllControlRunnablesResult::Nothing) {
5009 // The state of the world may have changed. Recheck it if we need to
5010 // continue.
5011 normalRunnablesPending =
5012 result == ProcessAllControlRunnablesResult::MayContinue &&
5013 NS_HasPendingEvents(thread);
5015 // NB: If we processed a NotifyRunnable, we might have run
5016 // non-control runnables, one of which may have shut down the
5017 // sync loop.
5018 if (loopInfo->mCompleted) {
5019 break;
5023 // If we *didn't* run any control runnables, this should be unchanged.
5024 MOZ_ASSERT(!loopInfo->mCompleted);
5026 if (normalRunnablesPending) {
5027 break;
5032 if (normalRunnablesPending) {
5033 // Make sure the periodic timer is running before we continue.
5034 SetGCTimerMode(PeriodicTimer);
5036 MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(thread, false));
5038 // Now *might* be a good time to GC. Let the JS engine make the
5039 // decision.
5040 if (GetCurrentEventLoopGlobal()) {
5041 // If GetCurrentEventLoopGlobal() is non-null, our JSContext is in a
5042 // Realm, so it's safe to try to GC.
5043 MOZ_ASSERT(JS::CurrentGlobalOrNull(cx));
5044 JS_MaybeGC(cx);
5050 // Make sure that the stack didn't change underneath us.
5051 MOZ_ASSERT(mSyncLoopStack[currentLoopIndex].get() == loopInfo);
5053 return DestroySyncLoop(currentLoopIndex);
5056 nsresult WorkerPrivate::DestroySyncLoop(uint32_t aLoopIndex) {
5057 MOZ_ASSERT(!mSyncLoopStack.IsEmpty());
5058 MOZ_ASSERT(mSyncLoopStack.Length() - 1 == aLoopIndex);
5060 LOG(WorkerLog(),
5061 ("WorkerPrivate::DestroySyncLoop [%p] aLoopIndex: %u", this, aLoopIndex));
5063 AutoYieldJSThreadExecution yield;
5065 // We're about to delete the loop, stash its event target and result.
5066 const auto& loopInfo = mSyncLoopStack[aLoopIndex];
5068 nsresult result = loopInfo->mResult;
5071 RefPtr<nsIEventTarget> nestedEventTarget(
5072 loopInfo->mEventTarget->GetNestedEventTarget());
5073 MOZ_ASSERT(nestedEventTarget);
5075 loopInfo->mEventTarget->Shutdown();
5078 MutexAutoLock lock(mMutex);
5079 static_cast<ThreadEventQueue*>(mThread->EventQueue())
5080 ->PopEventQueue(nestedEventTarget);
5084 // Are we making a 1 -> 0 transition here?
5085 if (mSyncLoopStack.Length() == 1) {
5086 if ((mPostSyncLoopOperations & eDispatchCancelingRunnable)) {
5087 LOG(WorkerLog(),
5088 ("WorkerPrivate::DestroySyncLoop [%p] Dispatching CancelingRunnables",
5089 this));
5090 DispatchCancelingRunnable();
5093 mPostSyncLoopOperations = 0;
5097 // Modifications must be protected by mMutex in DEBUG builds, see comment
5098 // about mSyncLoopStack in WorkerPrivate.h.
5099 #ifdef DEBUG
5100 MutexAutoLock lock(mMutex);
5101 #endif
5103 // This will delete |loopInfo|!
5104 mSyncLoopStack.RemoveElementAt(aLoopIndex);
5107 return result;
5110 void WorkerPrivate::DispatchCancelingRunnable() {
5111 // Here we use a normal runnable to know when the current JS chunk of code
5112 // is finished. We cannot use a WorkerRunnable because they are not
5113 // accepted any more by the worker, and we do not want to use a
5114 // WorkerControlRunnable because they are immediately executed.
5116 LOG(WorkerLog(), ("WorkerPrivate::DispatchCancelingRunnable [%p]", this));
5117 RefPtr<CancelingRunnable> r = new CancelingRunnable();
5119 MutexAutoLock lock(mMutex);
5120 mThread->nsThread::Dispatch(r.forget(), NS_DISPATCH_NORMAL);
5123 // At the same time, we want to be sure that we interrupt infinite loops.
5124 // The following runnable starts a timer that cancel the worker, from the
5125 // parent thread, after CANCELING_TIMEOUT millseconds.
5126 LOG(WorkerLog(), ("WorkerPrivate::DispatchCancelingRunnable [%p] Setup a "
5127 "timeout canceling",
5128 this));
5129 RefPtr<CancelingWithTimeoutOnParentRunnable> rr =
5130 new CancelingWithTimeoutOnParentRunnable(this);
5131 rr->Dispatch(this);
5134 void WorkerPrivate::ReportUseCounters() {
5135 AssertIsOnWorkerThread();
5137 if (mReportedUseCounters) {
5138 return;
5140 mReportedUseCounters = true;
5142 if (IsChromeWorker()) {
5143 return;
5146 const size_t kind = Kind();
5147 switch (kind) {
5148 case WorkerKindDedicated:
5149 glean::use_counter::dedicated_workers_destroyed.Add();
5150 break;
5151 case WorkerKindShared:
5152 glean::use_counter::shared_workers_destroyed.Add();
5153 break;
5154 case WorkerKindService:
5155 glean::use_counter::service_workers_destroyed.Add();
5156 break;
5157 default:
5158 MOZ_ASSERT(false, "Unknown worker kind");
5159 return;
5162 Maybe<nsCString> workerPathForLogging;
5163 const bool dumpCounters = StaticPrefs::dom_use_counters_dump_worker();
5164 if (dumpCounters) {
5165 nsAutoCString path(Domain());
5166 path.AppendLiteral("(");
5167 NS_ConvertUTF16toUTF8 script(ScriptURL());
5168 path.Append(script);
5169 path.AppendPrintf(", 0x%p)", this);
5170 workerPathForLogging.emplace(std::move(path));
5173 const size_t count = static_cast<size_t>(UseCounterWorker::Count);
5175 const auto workerKind = Kind();
5176 for (size_t c = 0; c < count; ++c) {
5177 if (!GetUseCounter(static_cast<UseCounterWorker>(c))) {
5178 continue;
5180 const char* metricName =
5181 IncrementWorkerUseCounter(static_cast<UseCounterWorker>(c), workerKind);
5182 if (dumpCounters) {
5183 printf_stderr("USE_COUNTER_WORKER: %s - %s\n", metricName,
5184 workerPathForLogging->get());
5189 void WorkerPrivate::StopSyncLoop(nsIEventTarget* aSyncLoopTarget,
5190 nsresult aResult) {
5191 AssertValidSyncLoop(aSyncLoopTarget);
5193 if (!MaybeStopSyncLoop(aSyncLoopTarget, aResult)) {
5194 // TODO: I wonder if we should really ever crash here given the assert.
5195 MOZ_CRASH("Unknown sync loop!");
5199 bool WorkerPrivate::MaybeStopSyncLoop(nsIEventTarget* aSyncLoopTarget,
5200 nsresult aResult) {
5201 AssertIsOnWorkerThread();
5203 for (uint32_t index = mSyncLoopStack.Length(); index > 0; index--) {
5204 const auto& loopInfo = mSyncLoopStack[index - 1];
5205 MOZ_ASSERT(loopInfo);
5206 MOZ_ASSERT(loopInfo->mEventTarget);
5208 if (loopInfo->mEventTarget == aSyncLoopTarget) {
5209 // Can't assert |loop->mHasRun| here because dispatch failures can cause
5210 // us to bail out early.
5211 MOZ_ASSERT(!loopInfo->mCompleted);
5213 loopInfo->mResult = aResult;
5214 loopInfo->mCompleted = true;
5216 loopInfo->mEventTarget->Disable();
5218 return true;
5221 MOZ_ASSERT(!SameCOMIdentity(loopInfo->mEventTarget, aSyncLoopTarget));
5224 return false;
5227 #ifdef DEBUG
5228 void WorkerPrivate::AssertValidSyncLoop(nsIEventTarget* aSyncLoopTarget) {
5229 MOZ_ASSERT(aSyncLoopTarget);
5231 EventTarget* workerTarget;
5232 nsresult rv = aSyncLoopTarget->QueryInterface(
5233 kDEBUGWorkerEventTargetIID, reinterpret_cast<void**>(&workerTarget));
5234 MOZ_ASSERT(NS_SUCCEEDED(rv));
5235 MOZ_ASSERT(workerTarget);
5237 bool valid = false;
5240 MutexAutoLock lock(mMutex);
5242 for (uint32_t index = 0; index < mSyncLoopStack.Length(); index++) {
5243 const auto& loopInfo = mSyncLoopStack[index];
5244 MOZ_ASSERT(loopInfo);
5245 MOZ_ASSERT(loopInfo->mEventTarget);
5247 if (loopInfo->mEventTarget == aSyncLoopTarget) {
5248 valid = true;
5249 break;
5252 MOZ_ASSERT(!SameCOMIdentity(loopInfo->mEventTarget, aSyncLoopTarget));
5256 MOZ_ASSERT(valid);
5258 #endif
5260 void WorkerPrivate::PostMessageToParent(
5261 JSContext* aCx, JS::Handle<JS::Value> aMessage,
5262 const Sequence<JSObject*>& aTransferable, ErrorResult& aRv) {
5263 LOG(WorkerLog(), ("WorkerPrivate::PostMessageToParent [%p]", this));
5264 AssertIsOnWorkerThread();
5265 MOZ_DIAGNOSTIC_ASSERT(IsDedicatedWorker());
5267 JS::Rooted<JS::Value> transferable(aCx, JS::UndefinedValue());
5269 aRv = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, aTransferable,
5270 &transferable);
5271 if (NS_WARN_IF(aRv.Failed())) {
5272 return;
5275 RefPtr<MessageEventToParentRunnable> runnable =
5276 new MessageEventToParentRunnable(this);
5278 JS::CloneDataPolicy clonePolicy;
5280 // Parent and dedicated workers are always part of the same cluster.
5281 clonePolicy.allowIntraClusterClonableSharedObjects();
5283 if (IsSharedMemoryAllowed()) {
5284 clonePolicy.allowSharedMemoryObjects();
5287 runnable->Write(aCx, aMessage, transferable, clonePolicy, aRv);
5289 if (NS_WARN_IF(aRv.Failed())) {
5290 return;
5293 if (!runnable->Dispatch(this)) {
5294 aRv = NS_ERROR_FAILURE;
5298 void WorkerPrivate::EnterDebuggerEventLoop() {
5299 auto data = mWorkerThreadAccessible.Access();
5301 JSContext* cx = GetJSContext();
5302 MOZ_ASSERT(cx);
5304 AutoPushEventLoopGlobal eventLoopGlobal(this, cx);
5305 AutoYieldJSThreadExecution yield;
5307 CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get();
5309 uint32_t currentEventLoopLevel = ++data->mDebuggerEventLoopLevel;
5311 while (currentEventLoopLevel <= data->mDebuggerEventLoopLevel) {
5312 bool debuggerRunnablesPending = false;
5315 MutexAutoLock lock(mMutex);
5317 debuggerRunnablesPending = !mDebuggerQueue.IsEmpty();
5320 // Don't block with the periodic GC timer running.
5321 if (!debuggerRunnablesPending) {
5322 SetGCTimerMode(IdleTimer);
5325 // Wait for something to do
5327 MutexAutoLock lock(mMutex);
5329 std::deque<RefPtr<MicroTaskRunnable>>& debuggerMtQueue =
5330 ccjscx->GetDebuggerMicroTaskQueue();
5331 while (mControlQueue.IsEmpty() &&
5332 !(debuggerRunnablesPending = !mDebuggerQueue.IsEmpty()) &&
5333 debuggerMtQueue.empty()) {
5334 WaitForWorkerEvents();
5337 ProcessAllControlRunnablesLocked();
5339 // XXXkhuey should we abort JS on the stack here if we got Abort above?
5341 ccjscx->PerformDebuggerMicroTaskCheckpoint();
5342 if (debuggerRunnablesPending) {
5343 // Start the periodic GC timer if it is not already running.
5344 SetGCTimerMode(PeriodicTimer);
5346 ProcessSingleDebuggerRunnable();
5348 // Now *might* be a good time to GC. Let the JS engine make the decision.
5349 if (GetCurrentEventLoopGlobal()) {
5350 // If GetCurrentEventLoopGlobal() is non-null, our JSContext is in a
5351 // Realm, so it's safe to try to GC.
5352 MOZ_ASSERT(JS::CurrentGlobalOrNull(cx));
5353 JS_MaybeGC(cx);
5359 void WorkerPrivate::LeaveDebuggerEventLoop() {
5360 auto data = mWorkerThreadAccessible.Access();
5362 // TODO: Why lock the mutex if we're accessing data accessible to one thread
5363 // only?
5364 MutexAutoLock lock(mMutex);
5366 if (data->mDebuggerEventLoopLevel > 0) {
5367 --data->mDebuggerEventLoopLevel;
5371 void WorkerPrivate::PostMessageToDebugger(const nsAString& aMessage) {
5372 mDebugger->PostMessageToDebugger(aMessage);
5375 void WorkerPrivate::SetDebuggerImmediate(dom::Function& aHandler,
5376 ErrorResult& aRv) {
5377 AssertIsOnWorkerThread();
5379 RefPtr<DebuggerImmediateRunnable> runnable =
5380 new DebuggerImmediateRunnable(this, aHandler);
5381 if (!runnable->Dispatch(this)) {
5382 aRv.Throw(NS_ERROR_FAILURE);
5386 void WorkerPrivate::ReportErrorToDebugger(const nsACString& aFilename,
5387 uint32_t aLineno,
5388 const nsAString& aMessage) {
5389 mDebugger->ReportErrorToDebugger(aFilename, aLineno, aMessage);
5392 bool WorkerPrivate::NotifyInternal(WorkerStatus aStatus) {
5393 auto data = mWorkerThreadAccessible.Access();
5395 // Yield execution while notifying out-of-module WorkerRefs and cancelling
5396 // runnables.
5397 AutoYieldJSThreadExecution yield;
5399 NS_ASSERTION(aStatus > Running && aStatus < Dead, "Bad status!");
5401 RefPtr<EventTarget> eventTarget;
5403 // Save the old status and set the new status.
5405 MutexAutoLock lock(mMutex);
5407 LOG(WorkerLog(),
5408 ("WorkerPrivate::NotifyInternal [%p] mStatus: %u, aStatus: %u", this,
5409 static_cast<uint8_t>(mStatus), static_cast<uint8_t>(aStatus)));
5411 if (mStatus >= aStatus) {
5412 return true;
5415 MOZ_ASSERT_IF(aStatus == Killing,
5416 mStatus == Canceling && mParentStatus == Canceling);
5418 mStatus = aStatus;
5420 // Mark parent status as closing immediately to avoid new events being
5421 // dispatched after we clear the queue below.
5422 if (aStatus == Closing) {
5423 Close();
5426 // Synchronize the mParentStatus with mStatus, such that event dispatching
5427 // will fail in proper after WorkerPrivate gets into Killing status.
5428 if (aStatus >= Killing) {
5429 mParentStatus = aStatus;
5433 // Status transistion to "Canceling"/"Killing", mark the scope as dying when
5434 // "Canceling," or shutdown the StorageManager when "Killing."
5435 if (aStatus >= Canceling) {
5436 if (data->mScope) {
5437 if (aStatus == Canceling) {
5438 data->mScope->NoteTerminating();
5439 } else {
5440 data->mScope->NoteShuttingDown();
5445 if (aStatus >= Closing) {
5446 CancelAllTimeouts();
5449 if (aStatus == Closing && GlobalScope()) {
5450 GlobalScope()->SetIsNotEligibleForMessaging();
5453 // Let all our holders know the new status.
5454 if (aStatus == Canceling) {
5455 NotifyWorkerRefs(aStatus);
5458 if (aStatus == Canceling && mRemoteWorkerNonLifeCycleOpController) {
5459 mRemoteWorkerNonLifeCycleOpController->TransistionStateToCanceled();
5462 if (aStatus == Killing && mRemoteWorkerNonLifeCycleOpController) {
5463 mRemoteWorkerNonLifeCycleOpController->TransistionStateToKilled();
5464 mRemoteWorkerNonLifeCycleOpController = nullptr;
5467 // If the worker script never ran, or failed to compile, we don't need to do
5468 // anything else.
5469 WorkerGlobalScope* global = GlobalScope();
5470 if (!global) {
5471 if (aStatus == Canceling) {
5472 MOZ_ASSERT(!data->mCancelBeforeWorkerScopeConstructed);
5473 data->mCancelBeforeWorkerScopeConstructed.Flip();
5475 return true;
5478 // Don't abort the script now, but we dispatch a runnable to do it when the
5479 // current JS frame is executed.
5480 if (aStatus == Closing) {
5481 if (!mSyncLoopStack.IsEmpty()) {
5482 LOG(WorkerLog(), ("WorkerPrivate::NotifyInternal [%p] request to "
5483 "dispatch canceling runnables...",
5484 this));
5485 mPostSyncLoopOperations |= eDispatchCancelingRunnable;
5486 } else {
5487 DispatchCancelingRunnable();
5489 return true;
5492 MOZ_ASSERT(aStatus == Canceling || aStatus == Killing);
5494 LOG(WorkerLog(), ("WorkerPrivate::NotifyInternal [%p] abort script", this));
5496 // Always abort the script.
5497 return false;
5500 void WorkerPrivate::ReportError(JSContext* aCx,
5501 JS::ConstUTF8CharsZ aToStringResult,
5502 JSErrorReport* aReport) {
5503 auto data = mWorkerThreadAccessible.Access();
5505 if (!MayContinueRunning() || data->mErrorHandlerRecursionCount == 2) {
5506 return;
5509 NS_ASSERTION(data->mErrorHandlerRecursionCount == 0 ||
5510 data->mErrorHandlerRecursionCount == 1,
5511 "Bad recursion logic!");
5513 UniquePtr<WorkerErrorReport> report = MakeUnique<WorkerErrorReport>();
5514 if (aReport) {
5515 report->AssignErrorReport(aReport);
5518 JS::ExceptionStack exnStack(aCx);
5519 if (JS_IsExceptionPending(aCx)) {
5520 if (!JS::StealPendingExceptionStack(aCx, &exnStack)) {
5521 JS_ClearPendingException(aCx);
5522 return;
5525 JS::Rooted<JSObject*> stack(aCx), stackGlobal(aCx);
5526 xpc::FindExceptionStackForConsoleReport(
5527 nullptr, exnStack.exception(), exnStack.stack(), &stack, &stackGlobal);
5529 if (stack) {
5530 JSAutoRealm ar(aCx, stackGlobal);
5531 report->SerializeWorkerStack(aCx, this, stack);
5533 } else {
5534 // ReportError is also used for reporting warnings,
5535 // so there won't be a pending exception.
5536 MOZ_ASSERT(aReport && aReport->isWarning());
5539 if (report->mMessage.IsEmpty() && aToStringResult) {
5540 nsDependentCString toStringResult(aToStringResult.c_str());
5541 if (!AppendUTF8toUTF16(toStringResult, report->mMessage,
5542 mozilla::fallible)) {
5543 // Try again, with only a 1 KB string. Do this infallibly this time.
5544 // If the user doesn't have 1 KB to spare we're done anyways.
5545 size_t index = std::min<size_t>(1024, toStringResult.Length());
5547 // Drop the last code point that may be cropped.
5548 index = RewindToPriorUTF8Codepoint(toStringResult.BeginReading(), index);
5550 nsDependentCString truncatedToStringResult(aToStringResult.c_str(),
5551 index);
5552 AppendUTF8toUTF16(truncatedToStringResult, report->mMessage);
5556 data->mErrorHandlerRecursionCount++;
5558 // Don't want to run the scope's error handler if this is a recursive error or
5559 // if we ran out of memory.
5560 bool fireAtScope = data->mErrorHandlerRecursionCount == 1 &&
5561 report->mErrorNumber != JSMSG_OUT_OF_MEMORY &&
5562 JS::CurrentGlobalOrNull(aCx);
5564 WorkerErrorReport::ReportError(aCx, this, fireAtScope, nullptr,
5565 std::move(report), 0, exnStack.exception());
5567 data->mErrorHandlerRecursionCount--;
5570 // static
5571 void WorkerPrivate::ReportErrorToConsole(
5572 uint32_t aErrorFlags, const nsCString& aCategory,
5573 nsContentUtils::PropertiesFile aFile, const nsCString& aMessageName,
5574 const nsTArray<nsString>& aParams,
5575 const mozilla::SourceLocation& aLocation) {
5576 WorkerPrivate* wp = nullptr;
5577 if (!NS_IsMainThread()) {
5578 wp = GetCurrentThreadWorkerPrivate();
5581 ReportErrorToConsoleRunnable::Report(wp, aErrorFlags, aCategory, aFile,
5582 aMessageName, aParams, aLocation);
5585 int32_t WorkerPrivate::SetTimeout(JSContext* aCx, TimeoutHandler* aHandler,
5586 int32_t aTimeout, bool aIsInterval,
5587 Timeout::Reason aReason, ErrorResult& aRv) {
5588 auto data = mWorkerThreadAccessible.Access();
5589 MOZ_ASSERT(aHandler);
5591 if (StaticPrefs::dom_workers_timeoutmanager() && XRE_IsContentProcess()) {
5592 WorkerGlobalScope* globalScope = GlobalScope();
5593 auto* timeoutManager = globalScope->GetTimeoutManager();
5594 int32_t timerId = -1;
5595 nsresult rv = timeoutManager->SetTimeout(aHandler, aTimeout, aIsInterval,
5596 aReason, &timerId);
5597 if (NS_FAILED(rv)) {
5598 aRv.Throw(NS_ERROR_FAILURE);
5600 return timerId;
5603 // Reasons that doesn't support cancellation will get -1 as their ids.
5604 int32_t timerId = -1;
5605 if (aReason == Timeout::Reason::eTimeoutOrInterval) {
5606 timerId = data->mNextTimeoutId;
5607 data->mNextTimeoutId += 1;
5610 WorkerStatus currentStatus;
5612 MutexAutoLock lock(mMutex);
5613 currentStatus = mStatus;
5616 // If the worker is trying to call setTimeout/setInterval and the parent
5617 // thread has initiated the close process then just silently fail.
5618 if (currentStatus >= Closing) {
5619 return timerId;
5622 auto newInfo = MakeUnique<TimeoutInfo>();
5623 newInfo->mReason = aReason;
5624 newInfo->mOnChromeWorker = mIsChromeWorker;
5625 newInfo->mIsInterval = aIsInterval;
5626 newInfo->mId = timerId;
5627 if (newInfo->mReason == Timeout::Reason::eTimeoutOrInterval ||
5628 newInfo->mReason == Timeout::Reason::eIdleCallbackTimeout) {
5629 newInfo->AccumulateNestingLevel(data->mCurrentTimerNestingLevel);
5632 if (MOZ_UNLIKELY(timerId == INT32_MAX)) {
5633 NS_WARNING("Timeout ids overflowed!");
5634 if (aReason == Timeout::Reason::eTimeoutOrInterval) {
5635 data->mNextTimeoutId = 1;
5639 newInfo->mHandler = aHandler;
5641 // See if any of the optional arguments were passed.
5642 aTimeout = std::max(0, aTimeout);
5643 newInfo->mInterval = TimeDuration::FromMilliseconds(aTimeout);
5644 newInfo->CalculateTargetTime();
5646 const auto& insertedInfo = data->mTimeouts.InsertElementSorted(
5647 std::move(newInfo), GetUniquePtrComparator(data->mTimeouts));
5649 LOG(TimeoutsLog(), ("Worker %p has new timeout: delay=%d interval=%s\n", this,
5650 aTimeout, aIsInterval ? "yes" : "no"));
5652 // If the timeout we just made is set to fire next then we need to update the
5653 // timer, unless we're currently running timeouts.
5654 if (insertedInfo == data->mTimeouts.Elements() &&
5655 !data->mRunningExpiredTimeouts) {
5656 if (!data->mTimer) {
5657 data->mTimer = NS_NewTimer(GlobalScope()->SerialEventTarget());
5658 if (!data->mTimer) {
5659 aRv.Throw(NS_ERROR_UNEXPECTED);
5660 return 0;
5663 data->mTimerRunnable = new RunExpiredTimoutsRunnable(this);
5666 if (!data->mTimerRunning) {
5667 UpdateCCFlag(CCFlag::IneligibleForTimeout);
5668 data->mTimerRunning = true;
5671 if (!RescheduleTimeoutTimer(aCx)) {
5672 aRv.Throw(NS_ERROR_FAILURE);
5673 return 0;
5677 return timerId;
5680 void WorkerPrivate::ClearTimeout(int32_t aId, Timeout::Reason aReason) {
5681 MOZ_ASSERT(aReason == Timeout::Reason::eTimeoutOrInterval,
5682 "This timeout reason doesn't support cancellation.");
5683 if (StaticPrefs::dom_workers_timeoutmanager() && XRE_IsContentProcess()) {
5684 WorkerGlobalScope* globalScope = GlobalScope();
5685 auto* timeoutManager = globalScope->GetTimeoutManager();
5686 timeoutManager->ClearTimeout(aId, aReason);
5687 return;
5690 auto data = mWorkerThreadAccessible.Access();
5692 if (!data->mTimeouts.IsEmpty()) {
5693 NS_ASSERTION(data->mTimerRunning, "Huh?!");
5695 for (uint32_t index = 0; index < data->mTimeouts.Length(); index++) {
5696 const auto& info = data->mTimeouts[index];
5697 if (info->mId == aId && info->mReason == aReason) {
5698 info->mCanceled = true;
5699 break;
5705 bool WorkerPrivate::RunExpiredTimeouts(JSContext* aCx) {
5706 auto data = mWorkerThreadAccessible.Access();
5708 // We may be called recursively (e.g. close() inside a timeout) or we could
5709 // have been canceled while this event was pending, bail out if there is
5710 // nothing to do.
5711 if (data->mRunningExpiredTimeouts || !data->mTimerRunning) {
5712 return true;
5715 NS_ASSERTION(data->mTimer && data->mTimerRunnable, "Must have a timer!");
5716 NS_ASSERTION(!data->mTimeouts.IsEmpty(), "Should have some work to do!");
5718 bool retval = true;
5720 auto comparator = GetUniquePtrComparator(data->mTimeouts);
5721 JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
5723 // We want to make sure to run *something*, even if the timer fired a little
5724 // early. Fudge the value of now to at least include the first timeout.
5725 const TimeStamp actual_now = TimeStamp::Now();
5726 const TimeStamp now = std::max(actual_now, data->mTimeouts[0]->mTargetTime);
5728 if (now != actual_now) {
5729 LOG(TimeoutsLog(), ("Worker %p fudged timeout by %f ms.\n", this,
5730 (now - actual_now).ToMilliseconds()));
5731 #ifdef DEBUG
5732 double microseconds = (now - actual_now).ToMicroseconds();
5733 uint32_t allowedEarlyFiringMicroseconds;
5734 data->mTimer->GetAllowedEarlyFiringMicroseconds(
5735 &allowedEarlyFiringMicroseconds);
5736 MOZ_ASSERT(microseconds < allowedEarlyFiringMicroseconds);
5737 #endif
5740 AutoTArray<TimeoutInfo*, 10> expiredTimeouts;
5741 for (uint32_t index = 0; index < data->mTimeouts.Length(); index++) {
5742 TimeoutInfo* info = data->mTimeouts[index].get();
5743 if (info->mTargetTime > now) {
5744 break;
5746 expiredTimeouts.AppendElement(info);
5749 // Guard against recursion.
5750 data->mRunningExpiredTimeouts = true;
5752 MOZ_DIAGNOSTIC_ASSERT(data->mCurrentTimerNestingLevel == 0);
5754 // Run expired timeouts.
5755 for (uint32_t index = 0; index < expiredTimeouts.Length(); index++) {
5756 TimeoutInfo*& info = expiredTimeouts[index];
5757 AutoRestore<uint32_t> nestingLevel(data->mCurrentTimerNestingLevel);
5759 if (info->mCanceled) {
5760 continue;
5763 // Set current timer nesting level to current running timer handler's
5764 // nesting level
5765 data->mCurrentTimerNestingLevel = info->mNestingLevel;
5767 LOG(TimeoutsLog(),
5768 ("Worker %p executing timeout with original delay %f ms.\n", this,
5769 info->mInterval.ToMilliseconds()));
5771 // Always check JS_IsExceptionPending if something fails, and if
5772 // JS_IsExceptionPending returns false (i.e. uncatchable exception) then
5773 // break out of the loop.
5775 RefPtr<TimeoutHandler> handler(info->mHandler);
5777 const char* reason;
5778 switch (info->mReason) {
5779 case Timeout::Reason::eTimeoutOrInterval:
5780 if (info->mIsInterval) {
5781 reason = "setInterval handler";
5782 } else {
5783 reason = "setTimeout handler";
5785 break;
5786 case Timeout::Reason::eDelayedWebTaskTimeout:
5787 reason = "delayedWebTask handler";
5788 break;
5789 default:
5790 MOZ_ASSERT(info->mReason == Timeout::Reason::eAbortSignalTimeout);
5791 reason = "AbortSignal Timeout";
5793 if (info->mReason == Timeout::Reason::eTimeoutOrInterval ||
5794 info->mReason == Timeout::Reason::eDelayedWebTaskTimeout) {
5795 RefPtr<WorkerGlobalScope> scope(this->GlobalScope());
5796 CallbackDebuggerNotificationGuard guard(
5797 scope, info->mIsInterval
5798 ? DebuggerNotificationType::SetIntervalCallback
5799 : DebuggerNotificationType::SetTimeoutCallback);
5801 if (!handler->Call(reason)) {
5802 retval = false;
5803 break;
5805 } else {
5806 MOZ_ASSERT(info->mReason == Timeout::Reason::eAbortSignalTimeout);
5807 MOZ_ALWAYS_TRUE(handler->Call(reason));
5810 NS_ASSERTION(data->mRunningExpiredTimeouts, "Someone changed this!");
5813 // No longer possible to be called recursively.
5814 data->mRunningExpiredTimeouts = false;
5816 // Now remove canceled and expired timeouts from the main list.
5817 // NB: The timeouts present in expiredTimeouts must have the same order
5818 // with respect to each other in mTimeouts. That is, mTimeouts is just
5819 // expiredTimeouts with extra elements inserted. There may be unexpired
5820 // timeouts that have been inserted between the expired timeouts if the
5821 // timeout event handler called setTimeout/setInterval.
5822 for (uint32_t index = 0, expiredTimeoutIndex = 0,
5823 expiredTimeoutLength = expiredTimeouts.Length();
5824 index < data->mTimeouts.Length();) {
5825 const auto& info = data->mTimeouts[index];
5826 if ((expiredTimeoutIndex < expiredTimeoutLength &&
5827 info == expiredTimeouts[expiredTimeoutIndex] &&
5828 ++expiredTimeoutIndex) ||
5829 info->mCanceled) {
5830 if (info->mIsInterval && !info->mCanceled) {
5831 // Reschedule intervals.
5832 // Reschedule a timeout, if needed, increase the nesting level.
5833 info->AccumulateNestingLevel(info->mNestingLevel);
5834 info->CalculateTargetTime();
5835 // Don't resort the list here, we'll do that at the end.
5836 ++index;
5837 } else {
5838 data->mTimeouts.RemoveElement(info);
5840 } else {
5841 // If info did not match the current entry in expiredTimeouts, it
5842 // shouldn't be there at all.
5843 NS_ASSERTION(!expiredTimeouts.Contains(info),
5844 "Our timeouts are out of order!");
5845 ++index;
5849 data->mTimeouts.Sort(comparator);
5851 // Either signal the parent that we're no longer using timeouts or reschedule
5852 // the timer.
5853 if (data->mTimeouts.IsEmpty()) {
5854 UpdateCCFlag(CCFlag::EligibleForTimeout);
5855 data->mTimerRunning = false;
5856 } else if (retval && !RescheduleTimeoutTimer(aCx)) {
5857 retval = false;
5860 return retval;
5863 bool WorkerPrivate::RescheduleTimeoutTimer(JSContext* aCx) {
5864 auto data = mWorkerThreadAccessible.Access();
5865 MOZ_ASSERT(!data->mRunningExpiredTimeouts);
5866 NS_ASSERTION(!data->mTimeouts.IsEmpty(), "Should have some timeouts!");
5867 NS_ASSERTION(data->mTimer && data->mTimerRunnable, "Should have a timer!");
5869 // NB: This is important! The timer may have already fired, e.g. if a timeout
5870 // callback itself calls setTimeout for a short duration and then takes longer
5871 // than that to finish executing. If that has happened, it's very important
5872 // that we don't execute the event that is now pending in our event queue, or
5873 // our code in RunExpiredTimeouts to "fudge" the timeout value will unleash an
5874 // early timeout when we execute the event we're about to queue.
5875 data->mTimer->Cancel();
5877 double delta =
5878 (data->mTimeouts[0]->mTargetTime - TimeStamp::Now()).ToMilliseconds();
5879 uint32_t delay = delta > 0 ? static_cast<uint32_t>(std::ceil(
5880 std::min(delta, double(UINT32_MAX))))
5881 : 0;
5883 LOG(TimeoutsLog(),
5884 ("Worker %p scheduled timer for %d ms, %zu pending timeouts\n", this,
5885 delay, data->mTimeouts.Length()));
5887 nsresult rv = data->mTimer->InitWithCallback(data->mTimerRunnable, delay,
5888 nsITimer::TYPE_ONE_SHOT);
5889 if (NS_FAILED(rv)) {
5890 JS_ReportErrorASCII(aCx, "Failed to start timer!");
5891 return false;
5894 return true;
5897 void WorkerPrivate::StartCancelingTimer() {
5898 AssertIsOnParentThread();
5900 // return if mCancelingTimer has already existed.
5901 if (mCancelingTimer) {
5902 return;
5905 auto errorCleanup = MakeScopeExit([&] { mCancelingTimer = nullptr; });
5907 if (WorkerPrivate* parent = GetParent()) {
5908 mCancelingTimer = NS_NewTimer(parent->ControlEventTarget());
5909 } else {
5910 mCancelingTimer = NS_NewTimer();
5913 if (NS_WARN_IF(!mCancelingTimer)) {
5914 return;
5917 // This is not needed if we are already in an advanced shutdown state.
5919 MutexAutoLock lock(mMutex);
5920 if (ParentStatus() >= Canceling) {
5921 return;
5925 uint32_t cancelingTimeoutMillis =
5926 StaticPrefs::dom_worker_canceling_timeoutMilliseconds();
5928 RefPtr<CancelingTimerCallback> callback = new CancelingTimerCallback(this);
5929 nsresult rv = mCancelingTimer->InitWithCallback(
5930 callback, cancelingTimeoutMillis, nsITimer::TYPE_ONE_SHOT);
5931 if (NS_WARN_IF(NS_FAILED(rv))) {
5932 return;
5935 errorCleanup.release();
5938 void WorkerPrivate::UpdateContextOptionsInternal(
5939 JSContext* aCx, const JS::ContextOptions& aContextOptions) {
5940 auto data = mWorkerThreadAccessible.Access();
5942 JS::ContextOptionsRef(aCx) = aContextOptions;
5944 for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
5945 data->mChildWorkers[index]->UpdateContextOptions(aContextOptions);
5949 void WorkerPrivate::UpdateLanguagesInternal(
5950 const nsTArray<nsString>& aLanguages) {
5951 WorkerGlobalScope* globalScope = GlobalScope();
5952 RefPtr<WorkerNavigator> nav = globalScope->GetExistingNavigator();
5953 if (nav) {
5954 nav->SetLanguages(aLanguages);
5957 auto data = mWorkerThreadAccessible.Access();
5958 for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
5959 data->mChildWorkers[index]->UpdateLanguages(aLanguages);
5962 RefPtr<Event> event = NS_NewDOMEvent(globalScope, nullptr, nullptr);
5964 event->InitEvent(u"languagechange"_ns, false, false);
5965 event->SetTrusted(true);
5967 globalScope->DispatchEvent(*event);
5970 void WorkerPrivate::UpdateJSWorkerMemoryParameterInternal(
5971 JSContext* aCx, JSGCParamKey aKey, Maybe<uint32_t> aValue) {
5972 auto data = mWorkerThreadAccessible.Access();
5974 if (aValue) {
5975 JS_SetGCParameter(aCx, aKey, *aValue);
5976 } else {
5977 JS_ResetGCParameter(aCx, aKey);
5980 for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
5981 data->mChildWorkers[index]->UpdateJSWorkerMemoryParameter(aKey, aValue);
5985 #ifdef JS_GC_ZEAL
5986 void WorkerPrivate::UpdateGCZealInternal(JSContext* aCx, uint8_t aGCZeal,
5987 uint32_t aFrequency) {
5988 auto data = mWorkerThreadAccessible.Access();
5990 JS::SetGCZeal(aCx, aGCZeal, aFrequency);
5992 for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
5993 data->mChildWorkers[index]->UpdateGCZeal(aGCZeal, aFrequency);
5996 #endif
5998 void WorkerPrivate::SetLowMemoryStateInternal(JSContext* aCx, bool aState) {
5999 auto data = mWorkerThreadAccessible.Access();
6001 JS::SetLowMemoryState(aCx, aState);
6003 for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
6004 data->mChildWorkers[index]->SetLowMemoryState(aState);
6008 void WorkerPrivate::SetCCCollectedAnything(bool collectedAnything) {
6009 mWorkerThreadAccessible.Access()->mCCCollectedAnything = collectedAnything;
6012 uint32_t WorkerPrivate::GetCurrentTimerNestingLevel() const {
6013 auto data = mWorkerThreadAccessible.Access();
6015 auto* timeoutManager =
6016 data->mScope ? data->mScope->GetTimeoutManager() : nullptr;
6017 if (timeoutManager) {
6018 return timeoutManager->GetNestingLevel();
6021 return data->mCurrentTimerNestingLevel;
6024 bool WorkerPrivate::isLastCCCollectedAnything() {
6025 return mWorkerThreadAccessible.Access()->mCCCollectedAnything;
6028 void WorkerPrivate::GarbageCollectInternal(JSContext* aCx, bool aShrinking,
6029 bool aCollectChildren) {
6030 // Perform GC followed by CC (the CC is triggered by
6031 // WorkerJSRuntime::CustomGCCallback at the end of the collection).
6033 auto data = mWorkerThreadAccessible.Access();
6035 if (!GlobalScope()) {
6036 // We haven't compiled anything yet. Just bail out.
6037 return;
6040 if (aShrinking || aCollectChildren) {
6041 JS::PrepareForFullGC(aCx);
6043 if (aShrinking && mSyncLoopStack.IsEmpty()) {
6044 JS::NonIncrementalGC(aCx, JS::GCOptions::Shrink,
6045 JS::GCReason::DOM_WORKER);
6047 // Check whether the CC collected anything and if so GC again. This is
6048 // necessary to collect all garbage.
6049 if (data->mCCCollectedAnything) {
6050 JS::NonIncrementalGC(aCx, JS::GCOptions::Normal,
6051 JS::GCReason::DOM_WORKER);
6054 if (!aCollectChildren) {
6055 LOG(WorkerLog(), ("Worker %p collected idle garbage\n", this));
6057 } else {
6058 JS::NonIncrementalGC(aCx, JS::GCOptions::Normal,
6059 JS::GCReason::DOM_WORKER);
6060 LOG(WorkerLog(), ("Worker %p collected garbage\n", this));
6062 } else {
6063 JS_MaybeGC(aCx);
6064 LOG(WorkerLog(), ("Worker %p collected periodic garbage\n", this));
6067 if (aCollectChildren) {
6068 for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
6069 data->mChildWorkers[index]->GarbageCollect(aShrinking);
6074 void WorkerPrivate::CycleCollectInternal(bool aCollectChildren) {
6075 auto data = mWorkerThreadAccessible.Access();
6077 nsCycleCollector_collect(CCReason::WORKER, nullptr);
6079 if (aCollectChildren) {
6080 for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
6081 data->mChildWorkers[index]->CycleCollect();
6086 void WorkerPrivate::MemoryPressureInternal() {
6087 auto data = mWorkerThreadAccessible.Access();
6089 if (data->mScope) {
6090 RefPtr<Console> console = data->mScope->GetConsoleIfExists();
6091 if (console) {
6092 console->ClearStorage();
6095 RefPtr<Performance> performance = data->mScope->GetPerformanceIfExists();
6096 if (performance) {
6097 performance->MemoryPressure();
6100 data->mScope->RemoveReportRecords();
6103 if (data->mDebuggerScope) {
6104 RefPtr<Console> console = data->mDebuggerScope->GetConsoleIfExists();
6105 if (console) {
6106 console->ClearStorage();
6110 for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
6111 data->mChildWorkers[index]->MemoryPressure();
6115 void WorkerPrivate::SetThread(WorkerThread* aThread) {
6116 if (aThread) {
6117 #ifdef DEBUG
6119 bool isOnCurrentThread;
6120 MOZ_ASSERT(NS_SUCCEEDED(aThread->IsOnCurrentThread(&isOnCurrentThread)));
6121 MOZ_ASSERT(!isOnCurrentThread);
6123 #endif
6125 MOZ_ASSERT(!mPRThread);
6126 mPRThread = PRThreadFromThread(aThread);
6127 MOZ_ASSERT(mPRThread);
6129 mWorkerThreadAccessible.Transfer(mPRThread);
6130 } else {
6131 MOZ_ASSERT(mPRThread);
6135 void WorkerPrivate::SetWorkerPrivateInWorkerThread(
6136 WorkerThread* const aThread) {
6137 LOG(WorkerLog(),
6138 ("WorkerPrivate::SetWorkerPrivateInWorkerThread [%p]", this));
6139 MutexAutoLock lock(mMutex);
6141 MOZ_ASSERT(!mThread);
6142 MOZ_ASSERT(mStatus == Pending);
6144 mThread = aThread;
6145 mThread->SetWorker(WorkerThreadFriendKey{}, this);
6147 if (!mPreStartRunnables.IsEmpty()) {
6148 for (uint32_t index = 0; index < mPreStartRunnables.Length(); index++) {
6149 MOZ_ALWAYS_SUCCEEDS(mThread->DispatchAnyThread(
6150 WorkerThreadFriendKey{}, mPreStartRunnables[index]));
6152 // Don't clear mPreStartRunnables here, it will be cleared in the beginning
6153 // of WorkerPrivate::DoRunLoop() or when in WorkerPrivate::RunLoopNeverRan()
6157 void WorkerPrivate::ResetWorkerPrivateInWorkerThread() {
6158 LOG(WorkerLog(),
6159 ("WorkerPrivate::ResetWorkerPrivateInWorkerThread [%p]", this));
6160 RefPtr<WorkerThread> doomedThread;
6162 // Release the mutex before doomedThread.
6163 MutexAutoLock lock(mMutex);
6164 MOZ_ASSERT(mStatus == Dead);
6166 MOZ_ASSERT(mThread);
6168 mThread->ClearEventQueueAndWorker(WorkerThreadFriendKey{});
6169 mThread.swap(doomedThread);
6172 void WorkerPrivate::BeginCTypesCall() {
6173 AssertIsOnWorkerThread();
6174 auto data = mWorkerThreadAccessible.Access();
6176 // Don't try to GC while we're blocked in a ctypes call.
6177 SetGCTimerMode(NoTimer);
6179 data->mYieldJSThreadExecution.EmplaceBack();
6182 void WorkerPrivate::EndCTypesCall() {
6183 AssertIsOnWorkerThread();
6184 auto data = mWorkerThreadAccessible.Access();
6186 data->mYieldJSThreadExecution.RemoveLastElement();
6188 // Make sure the periodic timer is running before we start running JS again.
6189 SetGCTimerMode(PeriodicTimer);
6192 void WorkerPrivate::BeginCTypesCallback() {
6193 AssertIsOnWorkerThread();
6195 // Make sure the periodic timer is running before we start running JS again.
6196 SetGCTimerMode(PeriodicTimer);
6198 // Re-requesting execution is not needed since the JSRuntime code calling
6199 // this will do an AutoEntryScript.
6202 void WorkerPrivate::EndCTypesCallback() {
6203 AssertIsOnWorkerThread();
6205 // Don't try to GC while we're blocked in a ctypes call.
6206 SetGCTimerMode(NoTimer);
6209 bool WorkerPrivate::ConnectMessagePort(JSContext* aCx,
6210 UniqueMessagePortId& aIdentifier) {
6211 AssertIsOnWorkerThread();
6213 WorkerGlobalScope* globalScope = GlobalScope();
6215 JS::Rooted<JSObject*> jsGlobal(aCx, globalScope->GetWrapper());
6216 MOZ_ASSERT(jsGlobal);
6218 // This UniqueMessagePortId is used to create a new port, still connected
6219 // with the other one, but in the worker thread.
6220 ErrorResult rv;
6221 RefPtr<MessagePort> port = MessagePort::Create(globalScope, aIdentifier, rv);
6222 if (NS_WARN_IF(rv.Failed())) {
6223 rv.SuppressException();
6224 return false;
6227 GlobalObject globalObject(aCx, jsGlobal);
6228 if (globalObject.Failed()) {
6229 return false;
6232 RootedDictionary<MessageEventInit> init(aCx);
6233 init.mData = JS_GetEmptyStringValue(aCx);
6234 init.mBubbles = false;
6235 init.mCancelable = false;
6236 init.mSource.SetValue().SetAsMessagePort() = port;
6237 if (!init.mPorts.AppendElement(port.forget(), fallible)) {
6238 return false;
6241 RefPtr<MessageEvent> event =
6242 MessageEvent::Constructor(globalObject, u"connect"_ns, init);
6244 event->SetTrusted(true);
6246 globalScope->DispatchEvent(*event);
6248 return true;
6251 WorkerGlobalScope* WorkerPrivate::GetOrCreateGlobalScope(JSContext* aCx) {
6252 auto data = mWorkerThreadAccessible.Access();
6254 if (data->mScope) {
6255 return data->mScope;
6258 if (IsSharedWorker()) {
6259 data->mScope =
6260 new SharedWorkerGlobalScope(this, CreateClientSource(), WorkerName());
6261 } else if (IsServiceWorker()) {
6262 data->mScope = new ServiceWorkerGlobalScope(
6263 this, CreateClientSource(), GetServiceWorkerRegistrationDescriptor());
6264 } else {
6265 data->mScope = new DedicatedWorkerGlobalScope(this, CreateClientSource(),
6266 WorkerName());
6269 JS::Rooted<JSObject*> global(aCx);
6270 NS_ENSURE_TRUE(data->mScope->WrapGlobalObject(aCx, &global), nullptr);
6272 JSAutoRealm ar(aCx, global);
6274 if (!RegisterBindings(aCx, global)) {
6275 data->mScope = nullptr;
6276 return nullptr;
6279 // Worker has already in "Canceling", let the WorkerGlobalScope start dying.
6280 if (data->mCancelBeforeWorkerScopeConstructed) {
6281 data->mScope->NoteTerminating();
6282 data->mScope->DisconnectGlobalTeardownObservers();
6285 JS_FireOnNewGlobalObject(aCx, global);
6287 return data->mScope;
6290 WorkerDebuggerGlobalScope* WorkerPrivate::CreateDebuggerGlobalScope(
6291 JSContext* aCx) {
6292 auto data = mWorkerThreadAccessible.Access();
6293 MOZ_ASSERT(!data->mDebuggerScope);
6295 // The debugger global gets a dummy client, not the "real" client used by the
6296 // debugee worker.
6297 auto clientSource = ClientManager::CreateSource(
6298 GetClientType(), HybridEventTarget(), NullPrincipalInfo());
6300 data->mDebuggerScope =
6301 new WorkerDebuggerGlobalScope(this, std::move(clientSource));
6303 JS::Rooted<JSObject*> global(aCx);
6304 NS_ENSURE_TRUE(data->mDebuggerScope->WrapGlobalObject(aCx, &global), nullptr);
6306 JSAutoRealm ar(aCx, global);
6308 if (!RegisterDebuggerBindings(aCx, global)) {
6309 data->mDebuggerScope = nullptr;
6310 return nullptr;
6313 JS_FireOnNewGlobalObject(aCx, global);
6315 return data->mDebuggerScope;
6318 bool WorkerPrivate::IsOnWorkerThread() const {
6319 // We can't use mThread because it must be protected by mMutex and sometimes
6320 // this method is called when mMutex is already locked. This method should
6321 // always work.
6322 MOZ_ASSERT(mPRThread,
6323 "AssertIsOnWorkerThread() called before a thread was assigned!");
6325 return mPRThread == PR_GetCurrentThread();
6328 #ifdef DEBUG
6329 void WorkerPrivate::AssertIsOnWorkerThread() const {
6330 MOZ_ASSERT(IsOnWorkerThread());
6332 #endif // DEBUG
6334 void WorkerPrivate::DumpCrashInformation(nsACString& aString) {
6335 auto data = mWorkerThreadAccessible.Access();
6337 aString.Append("IsChromeWorker(");
6338 if (IsChromeWorker()) {
6339 aString.Append(NS_ConvertUTF16toUTF8(ScriptURL()));
6340 } else {
6341 aString.Append("false");
6343 aString.Append(")");
6344 for (const auto* workerRef : data->mWorkerRefs.NonObservingRange()) {
6345 if (workerRef->IsPreventingShutdown()) {
6346 aString.Append("|");
6347 aString.Append(workerRef->Name());
6348 const nsCString status = GET_WORKERREF_DEBUG_STATUS(workerRef);
6349 if (!status.IsEmpty()) {
6350 aString.Append("[");
6351 aString.Append(status);
6352 aString.Append("]");
6358 PerformanceStorage* WorkerPrivate::GetPerformanceStorage() {
6359 MOZ_ASSERT(mPerformanceStorage);
6360 return mPerformanceStorage;
6363 bool WorkerPrivate::ShouldResistFingerprinting(RFPTarget aTarget) const {
6364 return mLoadInfo.mShouldResistFingerprinting &&
6365 nsRFPService::IsRFPEnabledFor(
6366 mLoadInfo.mOriginAttributes.IsPrivateBrowsing(), aTarget,
6367 mLoadInfo.mOverriddenFingerprintingSettings);
6370 void WorkerPrivate::SetRemoteWorkerController(RemoteWorkerChild* aController) {
6371 AssertIsOnMainThread();
6372 MOZ_ASSERT(aController);
6373 MOZ_ASSERT(!mRemoteWorkerController);
6375 mRemoteWorkerController = aController;
6378 RemoteWorkerChild* WorkerPrivate::GetRemoteWorkerController() {
6379 AssertIsOnMainThread();
6380 MOZ_ASSERT(mRemoteWorkerController);
6381 return mRemoteWorkerController;
6384 RefPtr<GenericPromise> WorkerPrivate::SetServiceWorkerSkipWaitingFlag() {
6385 AssertIsOnWorkerThread();
6386 MOZ_ASSERT(IsServiceWorker());
6388 RefPtr<RemoteWorkerChild> rwc = mRemoteWorkerController;
6390 if (!rwc) {
6391 return GenericPromise::CreateAndReject(NS_ERROR_DOM_ABORT_ERR, __func__);
6394 RefPtr<GenericPromise> promise =
6395 rwc->MaybeSendSetServiceWorkerSkipWaitingFlag();
6397 return promise;
6400 const nsAString& WorkerPrivate::Id() {
6401 AssertIsOnMainThread();
6403 if (mId.IsEmpty()) {
6404 mId = ComputeWorkerPrivateId();
6407 MOZ_ASSERT(!mId.IsEmpty());
6409 return mId;
6412 bool WorkerPrivate::IsSharedMemoryAllowed() const {
6413 if (StaticPrefs::
6414 dom_postMessage_sharedArrayBuffer_bypassCOOP_COEP_insecure_enabled()) {
6415 return true;
6418 // Allow privileged addons to access shared memory.
6419 if (mIsPrivilegedAddonGlobal) {
6420 return true;
6423 return CrossOriginIsolated();
6426 bool WorkerPrivate::CrossOriginIsolated() const {
6427 if (!StaticPrefs::
6428 dom_postMessage_sharedArrayBuffer_withCOOP_COEP_AtStartup()) {
6429 return false;
6432 return mAgentClusterOpenerPolicy ==
6433 nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP;
6436 nsILoadInfo::CrossOriginEmbedderPolicy WorkerPrivate::GetEmbedderPolicy()
6437 const {
6438 if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
6439 return nsILoadInfo::EMBEDDER_POLICY_NULL;
6442 return mEmbedderPolicy.valueOr(nsILoadInfo::EMBEDDER_POLICY_NULL);
6445 Result<Ok, nsresult> WorkerPrivate::SetEmbedderPolicy(
6446 nsILoadInfo::CrossOriginEmbedderPolicy aPolicy) {
6447 MOZ_ASSERT(NS_IsMainThread());
6448 MOZ_ASSERT(mEmbedderPolicy.isNothing());
6450 if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
6451 return Ok();
6454 // https://html.spec.whatwg.org/multipage/browsers.html#check-a-global-object's-embedder-policy
6455 // If ownerPolicy's value is not compatible with cross-origin isolation or
6456 // policy's value is compatible with cross-origin isolation, then return true.
6457 EnsureOwnerEmbedderPolicy();
6458 nsILoadInfo::CrossOriginEmbedderPolicy ownerPolicy =
6459 mOwnerEmbedderPolicy.valueOr(nsILoadInfo::EMBEDDER_POLICY_NULL);
6460 if (nsContentSecurityManager::IsCompatibleWithCrossOriginIsolation(
6461 ownerPolicy) &&
6462 !nsContentSecurityManager::IsCompatibleWithCrossOriginIsolation(
6463 aPolicy)) {
6464 return Err(NS_ERROR_BLOCKED_BY_POLICY);
6467 mEmbedderPolicy.emplace(aPolicy);
6469 return Ok();
6472 void WorkerPrivate::InheritOwnerEmbedderPolicyOrNull(nsIRequest* aRequest) {
6473 MOZ_ASSERT(NS_IsMainThread());
6474 MOZ_ASSERT(aRequest);
6476 EnsureOwnerEmbedderPolicy();
6478 if (mOwnerEmbedderPolicy.isSome()) {
6479 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
6480 MOZ_ASSERT(channel);
6482 nsCOMPtr<nsIURI> scriptURI;
6483 MOZ_ALWAYS_SUCCEEDS(channel->GetURI(getter_AddRefs(scriptURI)));
6485 bool isLocalScriptURI = false;
6486 MOZ_ALWAYS_SUCCEEDS(NS_URIChainHasFlags(
6487 scriptURI, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
6488 &isLocalScriptURI));
6490 MOZ_RELEASE_ASSERT(isLocalScriptURI);
6493 mEmbedderPolicy.emplace(
6494 mOwnerEmbedderPolicy.valueOr(nsILoadInfo::EMBEDDER_POLICY_NULL));
6497 bool WorkerPrivate::MatchEmbedderPolicy(
6498 nsILoadInfo::CrossOriginEmbedderPolicy aPolicy) const {
6499 MOZ_ASSERT(NS_IsMainThread());
6501 if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
6502 return true;
6505 return mEmbedderPolicy.value() == aPolicy;
6508 nsILoadInfo::CrossOriginEmbedderPolicy WorkerPrivate::GetOwnerEmbedderPolicy()
6509 const {
6510 if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
6511 return nsILoadInfo::EMBEDDER_POLICY_NULL;
6514 return mOwnerEmbedderPolicy.valueOr(nsILoadInfo::EMBEDDER_POLICY_NULL);
6517 void WorkerPrivate::EnsureOwnerEmbedderPolicy() {
6518 MOZ_ASSERT(NS_IsMainThread());
6519 MOZ_ASSERT(mOwnerEmbedderPolicy.isNothing());
6521 if (GetParent()) {
6522 mOwnerEmbedderPolicy.emplace(GetParent()->GetEmbedderPolicy());
6523 } else if (GetWindow() && GetWindow()->GetWindowContext()) {
6524 mOwnerEmbedderPolicy.emplace(
6525 GetWindow()->GetWindowContext()->GetEmbedderPolicy());
6529 nsIPrincipal* WorkerPrivate::GetEffectiveStoragePrincipal() const {
6530 AssertIsOnWorkerThread();
6532 if (mLoadInfo.mUseRegularPrincipal) {
6533 return mLoadInfo.mPrincipal;
6536 return mLoadInfo.mPartitionedPrincipal;
6539 const mozilla::ipc::PrincipalInfo&
6540 WorkerPrivate::GetEffectiveStoragePrincipalInfo() const {
6541 AssertIsOnWorkerThread();
6543 if (mLoadInfo.mUseRegularPrincipal) {
6544 return *mLoadInfo.mPrincipalInfo;
6547 return *mLoadInfo.mPartitionedPrincipalInfo;
6550 NS_IMPL_ADDREF(WorkerPrivate::EventTarget)
6551 NS_IMPL_RELEASE(WorkerPrivate::EventTarget)
6553 NS_INTERFACE_MAP_BEGIN(WorkerPrivate::EventTarget)
6554 NS_INTERFACE_MAP_ENTRY(nsISerialEventTarget)
6555 NS_INTERFACE_MAP_ENTRY(nsIEventTarget)
6556 NS_INTERFACE_MAP_ENTRY(nsISupports)
6557 #ifdef DEBUG
6558 // kDEBUGWorkerEventTargetIID is special in that it does not AddRef its
6559 // result.
6560 if (aIID.Equals(kDEBUGWorkerEventTargetIID)) {
6561 *aInstancePtr = this;
6562 return NS_OK;
6563 } else
6564 #endif
6565 NS_INTERFACE_MAP_END
6567 NS_IMETHODIMP
6568 WorkerPrivate::EventTarget::DispatchFromScript(nsIRunnable* aRunnable,
6569 uint32_t aFlags) {
6570 nsCOMPtr<nsIRunnable> event(aRunnable);
6571 return Dispatch(event.forget(), aFlags);
6574 NS_IMETHODIMP
6575 WorkerPrivate::EventTarget::Dispatch(already_AddRefed<nsIRunnable> aRunnable,
6576 uint32_t aFlags) {
6577 // May be called on any thread!
6578 nsCOMPtr<nsIRunnable> event(aRunnable);
6580 // Workers only support asynchronous dispatch for now.
6581 if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL)) {
6582 return NS_ERROR_UNEXPECTED;
6585 RefPtr<WorkerRunnable> workerRunnable;
6587 MutexAutoLock lock(mMutex);
6589 if (mDisabled) {
6590 NS_WARNING(
6591 "A runnable was posted to a worker that is already shutting "
6592 "down!");
6593 return NS_ERROR_UNEXPECTED;
6596 MOZ_ASSERT(mWorkerPrivate);
6597 MOZ_ASSERT(mNestedEventTarget);
6599 if (event) {
6600 workerRunnable = mWorkerPrivate->MaybeWrapAsWorkerRunnable(event.forget());
6603 nsresult rv =
6604 mWorkerPrivate->Dispatch(workerRunnable.forget(), mNestedEventTarget);
6605 if (NS_WARN_IF(NS_FAILED(rv))) {
6606 return rv;
6609 return NS_OK;
6612 NS_IMETHODIMP
6613 WorkerPrivate::EventTarget::DelayedDispatch(already_AddRefed<nsIRunnable>,
6614 uint32_t)
6617 return NS_ERROR_NOT_IMPLEMENTED;
6620 NS_IMETHODIMP
6621 WorkerPrivate::EventTarget::RegisterShutdownTask(nsITargetShutdownTask* aTask) {
6622 return NS_ERROR_NOT_IMPLEMENTED;
6625 NS_IMETHODIMP
6626 WorkerPrivate::EventTarget::UnregisterShutdownTask(
6627 nsITargetShutdownTask* aTask) {
6628 return NS_ERROR_NOT_IMPLEMENTED;
6631 NS_IMETHODIMP
6632 WorkerPrivate::EventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread) {
6633 // May be called on any thread!
6635 MOZ_ASSERT(aIsOnCurrentThread);
6637 MutexAutoLock lock(mMutex);
6639 if (mShutdown) {
6640 NS_WARNING(
6641 "A worker's event target was used after the worker has shutdown!");
6642 return NS_ERROR_UNEXPECTED;
6645 MOZ_ASSERT(mNestedEventTarget);
6647 *aIsOnCurrentThread = mNestedEventTarget->IsOnCurrentThread();
6648 return NS_OK;
6651 NS_IMETHODIMP_(bool)
6652 WorkerPrivate::EventTarget::IsOnCurrentThreadInfallible() {
6653 // May be called on any thread!
6655 MutexAutoLock lock(mMutex);
6657 if (mShutdown) {
6658 NS_WARNING(
6659 "A worker's event target was used after the worker has shutdown!");
6660 return false;
6663 MOZ_ASSERT(mNestedEventTarget);
6665 return mNestedEventTarget->IsOnCurrentThread();
6668 WorkerPrivate::AutoPushEventLoopGlobal::AutoPushEventLoopGlobal(
6669 WorkerPrivate* aWorkerPrivate, JSContext* aCx) {
6670 auto data = aWorkerPrivate->mWorkerThreadAccessible.Access();
6671 mOldEventLoopGlobal = std::move(data->mCurrentEventLoopGlobal);
6672 if (JSObject* global = JS::CurrentGlobalOrNull(aCx)) {
6673 data->mCurrentEventLoopGlobal = xpc::NativeGlobal(global);
6675 #ifdef DEBUG
6676 mNewEventLoopGlobal = data->mCurrentEventLoopGlobal;
6677 #endif
6680 WorkerPrivate::AutoPushEventLoopGlobal::~AutoPushEventLoopGlobal() {
6681 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
6682 // We are popping out the event loop global, WorkerPrivate is supposed to be
6683 // alive and in a valid status(Running or Canceling)
6684 MOZ_ASSERT(workerPrivate);
6685 auto data = workerPrivate->mWorkerThreadAccessible.Access();
6686 #ifdef DEBUG
6687 // Saved event loop global should be matched.
6688 MOZ_ASSERT(data->mCurrentEventLoopGlobal == mNewEventLoopGlobal);
6689 mNewEventLoopGlobal = nullptr;
6690 #endif
6691 data->mCurrentEventLoopGlobal = std::move(mOldEventLoopGlobal);
6694 // -----------------------------------------------------------------------------
6695 // AutoSyncLoopHolder
6697 AutoSyncLoopHolder::AutoSyncLoopHolder(WorkerPrivate* aWorkerPrivate,
6698 WorkerStatus aFailStatus,
6699 const char* const aName)
6700 : mTarget(aWorkerPrivate->CreateNewSyncLoop(aFailStatus)),
6701 mIndex(aWorkerPrivate->mSyncLoopStack.Length() - 1) {
6702 aWorkerPrivate->AssertIsOnWorkerThread();
6703 LOGV(
6704 ("AutoSyncLoopHolder::AutoSyncLoopHolder [%p] creator: %s", this, aName));
6705 if (aFailStatus < Canceling) {
6706 mWorkerRef = StrongWorkerRef::Create(aWorkerPrivate, aName, [aName]() {
6707 // Do nothing with the shutdown callback here since we need to wait for
6708 // the underlying SyncLoop to complete by itself.
6709 LOGV(
6710 ("AutoSyncLoopHolder::AutoSyncLoopHolder Worker starts to shutdown "
6711 "with a AutoSyncLoopHolder(%s).",
6712 aName));
6714 } else {
6715 LOGV(
6716 ("AutoSyncLoopHolder::AutoSyncLoopHolder [%p] Create "
6717 "AutoSyncLoopHolder(%s) while Worker is shutting down",
6718 this, aName));
6719 mWorkerRef = StrongWorkerRef::CreateForcibly(aWorkerPrivate, aName);
6721 // mWorkerRef can be nullptr here.
6724 AutoSyncLoopHolder::~AutoSyncLoopHolder() {
6725 if (mWorkerRef && mTarget) {
6726 mWorkerRef->Private()->AssertIsOnWorkerThread();
6727 mWorkerRef->Private()->StopSyncLoop(mTarget, NS_ERROR_FAILURE);
6728 mWorkerRef->Private()->DestroySyncLoop(mIndex);
6732 nsresult AutoSyncLoopHolder::Run() {
6733 if (mWorkerRef) {
6734 WorkerPrivate* workerPrivate = mWorkerRef->Private();
6735 MOZ_ASSERT(workerPrivate);
6737 workerPrivate->AssertIsOnWorkerThread();
6739 nsresult rv = workerPrivate->RunCurrentSyncLoop();
6741 // The sync loop is done, sync loop has already destroyed in the end of
6742 // WorkerPrivate::RunCurrentSyncLoop(). So, release mWorkerRef here to
6743 // avoid destroying sync loop again in the ~AutoSyncLoopHolder();
6744 mWorkerRef = nullptr;
6746 return rv;
6748 return NS_OK;
6751 nsISerialEventTarget* AutoSyncLoopHolder::GetSerialEventTarget() const {
6752 // This can be null if CreateNewSyncLoop() fails.
6753 return mTarget;
6756 // -----------------------------------------------------------------------------
6757 // WorkerParentRef
6758 WorkerParentRef::WorkerParentRef(RefPtr<WorkerPrivate>& aWorkerPrivate)
6759 : mWorkerPrivate(aWorkerPrivate) {
6760 LOGV(("WorkerParentRef::WorkerParentRef [%p] aWorkerPrivate %p", this,
6761 aWorkerPrivate.get()));
6762 MOZ_ASSERT(mWorkerPrivate);
6763 mWorkerPrivate->AssertIsOnParentThread();
6766 const RefPtr<WorkerPrivate>& WorkerParentRef::Private() const {
6767 if (mWorkerPrivate) {
6768 mWorkerPrivate->AssertIsOnParentThread();
6770 return mWorkerPrivate;
6773 void WorkerParentRef::DropWorkerPrivate() {
6774 LOGV(("WorkerParentRef::DropWorkerPrivate [%p]", this));
6775 if (mWorkerPrivate) {
6776 mWorkerPrivate->AssertIsOnParentThread();
6777 mWorkerPrivate = nullptr;
6781 WorkerParentRef::~WorkerParentRef() = default;
6783 } // namespace dom
6784 } // namespace mozilla