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"
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"
82 #include "nsIMemoryReporter.h"
83 #include "nsIPermissionManager.h"
84 #include "nsIProtocolHandler.h"
85 #include "nsIScriptError.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"
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
; }
144 #define LOG(log, _args) MOZ_LOG(log, LogLevel::Debug, _args);
145 #define LOGV(args) MOZ_LOG(sWorkerPrivateLog, LogLevel::Verbose, args);
153 using namespace workerinternals
;
155 MOZ_DEFINE_MALLOC_SIZE_OF(JsWorkerMallocSizeOf
)
161 const nsIID kDEBUGWorkerEventTargetIID
= {
165 {0xba, 0x87, 0x3b, 0x3b, 0x5b, 0x1d, 0x5, 0xfb}};
170 class UniquePtrComparator
{
171 using A
= UniquePtr
<T
>;
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
;
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
;
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
)
208 ~ExternalRunnableWrapper() = default;
210 virtual bool PreDispatch(WorkerPrivate
* aWorkerPrivate
) override
{
211 // Silence bad assertions.
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;
225 if (!JS_IsExceptionPending(aCx
)) {
233 nsresult
Cancel() override
{
234 nsCOMPtr
<nsIDiscardableRunnable
> doomed
=
235 do_QueryInterface(mWrappedRunnable
);
239 mWrappedRunnable
= nullptr;
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
);
251 aName
.AppendLiteral("?");
253 aName
.AppendLiteral(")");
259 struct WindowAction
{
260 nsPIDOMWindowInner
* mWindow
;
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
;
275 WorkerFinishedRunnable(WorkerPrivate
* aWorkerPrivate
,
276 WorkerPrivate
* aFinishedWorker
)
277 : WorkerControlRunnable("WorkerFinishedRunnable"),
278 mFinishedWorker(aFinishedWorker
) {
279 aFinishedWorker
->IncreaseWorkerFinishedRunnableCount();
283 virtual bool PreDispatch(WorkerPrivate
* aWorkerPrivate
) override
{
284 // Silence bad assertions.
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();
316 class TopLevelWorkerFinishedRunnable final
: public Runnable
{
317 WorkerPrivate
* mFinishedWorker
;
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
)
330 ~TopLevelWorkerFinishedRunnable() = default;
334 AssertIsOnMainThread();
336 mFinishedWorker
->DecreaseTopLevelWorkerFinishedRunnableCount();
338 RuntimeService
* runtime
= RuntimeService::GetService();
341 mFinishedWorker
->DisableDebugger();
343 runtime
->UnregisterWorker(*mFinishedWorker
);
345 if (!mFinishedWorker
->ProxyReleaseMainThreadObjects()) {
346 NS_WARNING("Failed to dispatch, going to leak!");
349 mFinishedWorker
->ClearSelfAndParentEventTargetRef();
354 class CompileScriptRunnable final
: public WorkerDebuggeeRunnable
{
356 const mozilla::Encoding
* mDocumentEncoding
;
357 UniquePtr
<SerializedStackHolder
> mOriginStack
;
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()) {}
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
)) {
384 if (NS_WARN_IF(!aWorkerPrivate
->EnsureCSPEventListener())) {
389 workerinternals::LoadMainScript(aWorkerPrivate
, std::move(mOriginStack
),
390 mScriptURL
, WorkerScript
, rv
,
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()))) {
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();
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(
423 rv
.SuppressException();
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
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."
453 aWorkerPrivate
->SetWorkerScriptExecutedSuccessfully();
457 void PostRun(JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
,
458 bool aRunResult
) override
{
460 aWorkerPrivate
->CloseInternal();
462 WorkerThreadRunnable::PostRun(aCx
, aWorkerPrivate
, aRunResult
);
466 class NotifyRunnable final
: public WorkerControlRunnable
{
467 WorkerStatus mStatus
;
470 NotifyRunnable(WorkerPrivate
* aWorkerPrivate
, WorkerStatus aStatus
)
471 : WorkerControlRunnable("NotifyRunnable"), mStatus(aStatus
) {
472 MOZ_ASSERT(aStatus
== Closing
|| aStatus
== Canceling
||
477 virtual bool PreDispatch(WorkerPrivate
* aWorkerPrivate
) override
{
478 aWorkerPrivate
->AssertIsOnParentThread();
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
{
498 explicit FreezeRunnable(WorkerPrivate
* aWorkerPrivate
)
499 : WorkerControlRunnable("FreezeRunnable") {}
502 virtual bool WorkerRun(JSContext
* aCx
,
503 WorkerPrivate
* aWorkerPrivate
) override
{
504 return aWorkerPrivate
->FreezeInternal();
508 class ThawRunnable final
: public WorkerControlRunnable
{
510 explicit ThawRunnable(WorkerPrivate
* aWorkerPrivate
)
511 : WorkerControlRunnable("ThawRunnable") {}
514 virtual bool WorkerRun(JSContext
* aCx
,
515 WorkerPrivate
* aWorkerPrivate
) override
{
516 return aWorkerPrivate
->ThawInternal();
520 class ChangeBackgroundStateRunnable final
: public WorkerControlRunnable
{
522 ChangeBackgroundStateRunnable() = delete;
523 explicit ChangeBackgroundStateRunnable(WorkerPrivate
* aWorkerPrivate
) =
525 ChangeBackgroundStateRunnable(WorkerPrivate
* aWorkerPrivate
,
527 : WorkerControlRunnable("ChangeBackgroundStateRunnable"),
528 mIsBackground(aIsBackground
) {}
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
{
541 explicit PropagateStorageAccessPermissionGrantedRunnable(
542 WorkerPrivate
* aWorkerPrivate
)
543 : WorkerControlRunnable(
544 "PropagateStorageAccessPermissionGrantedRunnable") {}
547 bool WorkerRun(JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
) override
{
548 aWorkerPrivate
->PropagateStorageAccessPermissionGrantedInternal();
553 class ReportErrorToConsoleRunnable final
: public WorkerParentThreadRunnable
{
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();
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
,
574 runnable
->Dispatch(aWorkerPrivate
);
578 // Log a warning to the console.
579 nsContentUtils::ReportToConsole(aErrorFlags
, aCategory
, nullptr, aFile
,
580 aMessageName
.get(), aParams
, aLocation
);
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
),
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
,
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
{
626 NS_DECL_ISUPPORTS_INHERITED
628 explicit RunExpiredTimoutsRunnable(WorkerPrivate
* aWorkerPrivate
)
629 : WorkerThreadRunnable("RunExpiredTimoutsRunnable") {}
632 ~RunExpiredTimoutsRunnable() = default;
634 virtual bool PreDispatch(WorkerPrivate
* aWorkerPrivate
) override
{
635 // Silence bad assertions.
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
);
653 Notify(nsITimer
* aTimer
) override
{ return Run(); }
656 NS_IMPL_ISUPPORTS_INHERITED(RunExpiredTimoutsRunnable
, WorkerThreadRunnable
,
659 class DebuggerImmediateRunnable final
: public WorkerThreadRunnable
{
660 RefPtr
<dom::Function
> mHandler
;
663 explicit DebuggerImmediateRunnable(WorkerPrivate
* aWorkerPrivate
,
664 dom::Function
& aHandler
)
665 : WorkerThreadRunnable("DebuggerImmediateRunnable"),
666 mHandler(&aHandler
) {}
669 virtual bool IsDebuggerRunnable() const override
{ return true; }
671 virtual bool PreDispatch(WorkerPrivate
* aWorkerPrivate
) override
{
672 // Silence bad assertions.
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
);
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
;
726 UpdateContextOptionsRunnable(WorkerPrivate
* aWorkerPrivate
,
727 const JS::ContextOptions
& aContextOptions
)
728 : WorkerControlRunnable("UpdateContextOptionsRunnable"),
729 mContextOptions(aContextOptions
) {}
732 virtual bool WorkerRun(JSContext
* aCx
,
733 WorkerPrivate
* aWorkerPrivate
) override
{
734 aWorkerPrivate
->UpdateContextOptionsInternal(aCx
, mContextOptions
);
739 class UpdateLanguagesRunnable final
: public WorkerThreadRunnable
{
740 nsTArray
<nsString
> mLanguages
;
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
);
755 class UpdateJSWorkerMemoryParameterRunnable final
756 : public WorkerControlRunnable
{
757 Maybe
<uint32_t> mValue
;
761 UpdateJSWorkerMemoryParameterRunnable(WorkerPrivate
* aWorkerPrivate
,
763 Maybe
<uint32_t> aValue
)
764 : WorkerControlRunnable("UpdateJSWorkerMemoryParameterRunnable"),
769 virtual bool WorkerRun(JSContext
* aCx
,
770 WorkerPrivate
* aWorkerPrivate
) override
{
771 aWorkerPrivate
->UpdateJSWorkerMemoryParameterInternal(aCx
, mKey
, mValue
);
777 class UpdateGCZealRunnable final
: public WorkerControlRunnable
{
782 UpdateGCZealRunnable(WorkerPrivate
* aWorkerPrivate
, uint8_t aGCZeal
,
784 : WorkerControlRunnable("UpdateGCZealRunnable"),
786 mFrequency(aFrequency
) {}
789 virtual bool WorkerRun(JSContext
* aCx
,
790 WorkerPrivate
* aWorkerPrivate
) override
{
791 aWorkerPrivate
->UpdateGCZealInternal(aCx
, mGCZeal
, mFrequency
);
797 class SetLowMemoryStateRunnable final
: public WorkerControlRunnable
{
801 SetLowMemoryStateRunnable(WorkerPrivate
* aWorkerPrivate
, bool aState
)
802 : WorkerControlRunnable("SetLowMemoryStateRunnable"), mState(aState
) {}
805 virtual bool WorkerRun(JSContext
* aCx
,
806 WorkerPrivate
* aWorkerPrivate
) override
{
807 aWorkerPrivate
->SetLowMemoryStateInternal(aCx
, mState
);
812 class GarbageCollectRunnable final
: public WorkerControlRunnable
{
814 bool mCollectChildren
;
817 GarbageCollectRunnable(WorkerPrivate
* aWorkerPrivate
, bool aShrinking
,
818 bool aCollectChildren
)
819 : WorkerControlRunnable("GarbageCollectRunnable"),
820 mShrinking(aShrinking
),
821 mCollectChildren(aCollectChildren
) {}
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..
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
);
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();
848 class CycleCollectRunnable final
: public WorkerControlRunnable
{
849 bool mCollectChildren
;
852 CycleCollectRunnable(WorkerPrivate
* aWorkerPrivate
, bool aCollectChildren
)
853 : WorkerControlRunnable("CycleCollectRunnable"),
854 mCollectChildren(aCollectChildren
) {}
856 bool WorkerRun(JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
) override
{
857 aWorkerPrivate
->CycleCollectInternal(mCollectChildren
);
862 class OfflineStatusChangeRunnable final
: public WorkerThreadRunnable
{
864 OfflineStatusChangeRunnable(WorkerPrivate
* aWorkerPrivate
, bool aIsOffline
)
865 : WorkerThreadRunnable("OfflineStatusChangeRunnable"),
866 mIsOffline(aIsOffline
) {}
868 bool WorkerRun(JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
) override
{
869 aWorkerPrivate
->OfflineStatusChangeEventInternal(mIsOffline
);
877 class MemoryPressureRunnable final
: public WorkerControlRunnable
{
879 explicit MemoryPressureRunnable(WorkerPrivate
* aWorkerPrivate
)
880 : WorkerControlRunnable("MemoryPressureRunnable") {}
882 bool WorkerRun(JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
) override
{
883 aWorkerPrivate
->MemoryPressureInternal();
889 static bool StartsWithExplicit(nsACString
& s
) {
890 return StringBeginsWith(s
, "explicit/"_ns
);
894 PRThread
* PRThreadFromThread(nsIThread
* aThread
) {
898 MOZ_ALWAYS_SUCCEEDS(aThread
->GetPRThread(&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
{
910 explicit CancelingOnParentRunnable(WorkerPrivate
* aWorkerPrivate
)
911 : WorkerParentDebuggeeRunnable("CancelingOnParentRunnable") {}
913 bool WorkerRun(JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
) override
{
914 aWorkerPrivate
->Cancel();
919 // A runnable to cancel the worker from the parent process.
920 class CancelingWithTimeoutOnParentRunnable final
921 : public WorkerParentControlRunnable
{
923 explicit CancelingWithTimeoutOnParentRunnable(WorkerPrivate
* aWorkerPrivate
)
924 : WorkerParentControlRunnable("CancelingWithTimeoutOnParentRunnable") {}
926 bool WorkerRun(JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
) override
{
927 aWorkerPrivate
->AssertIsOnParentThread();
928 aWorkerPrivate
->StartCancelingTimer();
933 class CancelingTimerCallback final
: public nsITimerCallback
{
937 explicit CancelingTimerCallback(WorkerPrivate
* aWorkerPrivate
)
938 : mWorkerPrivate(aWorkerPrivate
) {}
941 Notify(nsITimer
* aTimer
) override
{
942 mWorkerPrivate
->AssertIsOnParentThread();
943 mWorkerPrivate
->Cancel();
948 ~CancelingTimerCallback() = default;
950 // Raw pointer here is OK because the timer is canceled during the shutdown
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
{
960 CancelingRunnable() : Runnable("CancelingRunnable") {}
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
);
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
);
995 EventTarget(WorkerPrivate
* aWorkerPrivate
, nsIEventTarget
* aNestedEventTarget
)
996 : mMutex("WorkerPrivate::EventTarget::mMutex"),
997 mWorkerPrivate(aWorkerPrivate
),
998 mNestedEventTarget(aNestedEventTarget
),
1001 MOZ_ASSERT(aWorkerPrivate
);
1002 MOZ_ASSERT(aNestedEventTarget
);
1007 MutexAutoLock
lock(mMutex
);
1009 // Note, Disable() can be called more than once safely.
1015 nsCOMPtr
<nsIEventTarget
> nestedEventTarget
;
1017 MutexAutoLock
lock(mMutex
);
1019 mWorkerPrivate
= nullptr;
1020 mNestedEventTarget
.swap(nestedEventTarget
);
1021 MOZ_ASSERT(mDisabled
);
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
1042 ~EventTarget() = default;
1045 struct WorkerPrivate::TimeoutInfo
{
1049 mReason(Timeout::Reason::eTimeoutOrInterval
),
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;
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() &&
1080 target
= TimeDuration::Max(
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
;
1091 uint32_t mNestingLevel
;
1092 Timeout::Reason mReason
;
1095 bool mOnChromeWorker
;
1098 class WorkerJSContextStats final
: public JS::RuntimeStats
{
1099 const nsCString mRtPath
;
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
;
1165 WorkerPrivate
* mWorkerPrivate
;
1168 explicit MemoryReporter(WorkerPrivate
* aWorkerPrivate
)
1169 : mMutex(aWorkerPrivate
->mMutex
), mWorkerPrivate(aWorkerPrivate
) {
1170 aWorkerPrivate
->AssertIsOnWorkerThread();
1174 CollectReports(nsIHandleReportCallback
* aHandleReport
, nsISupports
* aData
,
1175 bool aAnonymize
) override
;
1178 class FinishCollectRunnable
;
1180 class CollectReportsRunnable final
: public MainThreadWorkerControlRunnable
{
1181 RefPtr
<FinishCollectRunnable
> mFinishCollectRunnable
;
1182 const bool mAnonymize
;
1185 CollectReportsRunnable(WorkerPrivate
* aWorkerPrivate
,
1186 nsIHandleReportCallback
* aHandleReport
,
1187 nsISupports
* aHandlerData
, bool aAnonymize
,
1188 const nsACString
& aPath
);
1191 bool WorkerRun(JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
) override
;
1193 ~CollectReportsRunnable() {
1194 if (NS_IsMainThread()) {
1195 mFinishCollectRunnable
->Run();
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
;
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;
1235 ~FinishCollectRunnable() {
1236 // mHandleReport and mHandlerData are released on the main thread.
1237 AssertIsOnMainThread();
1241 ~MemoryReporter() = default;
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
)
1255 WorkerPrivate::MemoryReporter::CollectReports(
1256 nsIHandleReportCallback
* aHandleReport
, nsISupports
* aData
,
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");
1270 manager
->EndReport();
1276 path
.AppendLiteral("explicit/workers/workers(");
1277 if (aAnonymize
&& !mWorkerPrivate
->Domain().IsEmpty()) {
1278 path
.AppendLiteral("<anonymized-domain>)/worker(<anonymized-url>");
1280 nsAutoCString
escapedDomain(mWorkerPrivate
->Domain());
1281 if (escapedDomain
.IsEmpty()) {
1282 escapedDomain
+= "chrome";
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
,
1298 if (!runnable
->Dispatch(mWorkerPrivate
)) {
1299 return NS_ERROR_UNEXPECTED
;
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;
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
));
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
),
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
;
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
,
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();
1385 WorkerPrivate::SyncLoopInfo::SyncLoopInfo(EventTarget
* aEventTarget
)
1386 : mEventTarget(aEventTarget
),
1387 mResult(NS_ERROR_FAILURE
),
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
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
{
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();
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
))) {
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
);
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
);
1473 basePrin
->GetURI(getter_AddRefs(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
;
1500 addonPolicy
= basePrin
->AddonPolicy();
1503 // For extension workers there aren't any csp header values,
1504 // instead it will inherit the Extension CSP.
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
))) {
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()) {
1573 ReferrerPolicy policy
=
1574 ReferrerInfo::ReferrerPolicyFromHeaderString(headerValue
);
1575 if (policy
== ReferrerPolicy::_empty
) {
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,
1631 WorkerPrivate
* parent
= GetParent();
1632 // Dispatch to parent worker
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,
1658 MOZ_ASSERT_IF(aSyncLoopTarget
, mThread
);
1660 // Dispatch normal worker runnable
1661 if (mStatus
== Dead
|| (!aSyncLoopTarget
&& ParentStatus() > Canceling
)) {
1663 "A runnable was posted to a worker that is already shutting "
1665 return NS_ERROR_UNEXPECTED
;
1668 if (runnable
->IsDebuggeeRunnable() && !mDebuggerReady
) {
1669 MOZ_RELEASE_ASSERT(!aSyncLoopTarget
);
1670 mDelayedDebuggeeRunnables
.AppendElement(runnable
);
1675 if (ParentStatus() == Pending
|| mStatus
== Pending
) {
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
);
1687 "Using a worker event target after the thread has already"
1689 return NS_ERROR_UNEXPECTED
;
1693 if (aSyncLoopTarget
) {
1695 ("WorkerPrivate::DispatchLockHeld [%p] runnable %p dispatch to a "
1697 this, runnable
.get(), aSyncLoopTarget
));
1698 rv
= aSyncLoopTarget
->Dispatch(runnable
.forget(), NS_DISPATCH_NORMAL
);
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
) {
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
1719 ("WorkerPrivate::DispatchLockHeld [%p] runnable %p dispatch to the "
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));
1734 void WorkerPrivate::EnableDebugger() {
1735 AssertIsOnParentThread();
1737 if (NS_FAILED(RegisterWorkerDebugger(this))) {
1738 NS_WARNING("Failed to register worker debugger!");
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
);
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
) {
1832 "A debugger runnable was posted to a worker that is already "
1834 return NS_ERROR_UNEXPECTED
;
1837 // Transfer ownership to the debugger queue.
1838 mDebuggerQueue
.Push(runnable
.forget().take());
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,
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
;
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
);
1896 MutexAutoLock
lock(mMutex
);
1898 if (mParentStatus
>= aStatus
) {
1902 pending
= mParentStatus
== Pending
;
1903 mParentStatus
= aStatus
;
1906 if (mCancellationCallback
) {
1907 mCancellationCallback(!pending
);
1908 mCancellationCallback
= nullptr;
1911 mParentRef
->DropWorkerPrivate();
1916 // Fake a thread here just so that our assertions don't go off for no
1918 nsIThread
* currentThread
= NS_GetCurrentThread();
1919 MOZ_ASSERT(currentThread
);
1921 MOZ_ASSERT(!mPRThread
);
1922 mPRThread
= PRThreadFromThread(currentThread
);
1923 MOZ_ASSERT(mPRThread
);
1927 // Worker never got a chance to run, go ahead and delete it.
1928 ScheduleDeletion(WorkerPrivate::WorkerNeverRan
);
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.
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.
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
));
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.
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
);
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() &&
2076 void WorkerPrivate::PropagateStorageAccessPermissionGranted() {
2077 AssertIsOnParentThread();
2080 MutexAutoLock
lock(mMutex
);
2082 if (mParentStatus
>= Canceling
) {
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
) {
2099 hasNotifiedStorageKeyUsed
= true;
2101 // Notify about storage access on the main thread.
2102 RefPtr
<StrongWorkerRef
> strongRef
=
2103 StrongWorkerRef::Create(this, "WorkerPrivate::NotifyStorageKeyUsed");
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());
2113 window
->MaybeNotifyStorageKeyUsed();
2118 bool WorkerPrivate::Close() {
2119 mMutex
.AssertCurrentThreadOwns();
2120 if (mParentStatus
< Closing
) {
2121 mParentStatus
= Closing
;
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;
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
);
2184 RefPtr
<UpdateJSWorkerMemoryParameterRunnable
> runnable
=
2185 new UpdateJSWorkerMemoryParameterRunnable(this, aKey
, aValue
);
2186 if (!runnable
->Dispatch(this)) {
2187 NS_WARNING("Failed to update memory parameter!");
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!");
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
) {
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();
2266 nav
->SetOnLine(data
->mOnLine
);
2271 eventType
.AssignLiteral("offline");
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
) {
2303 return InvokeAsync(ControlEventTarget(), __func__
, []() {
2304 WorkerPrivate
* wp
= GetCurrentThreadWorkerPrivate();
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();
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(':');
2362 mLocationInfo
.mProtocol
.Truncate();
2366 if (NS_SUCCEEDED(aBaseURI
->GetPort(&port
)) && port
!= -1) {
2367 mLocationInfo
.mPort
.AppendInt(port
);
2369 nsAutoCString
host(mLocationInfo
.mHostname
);
2371 host
.Append(mLocationInfo
.mPort
);
2373 mLocationInfo
.mHost
.Assign(host
);
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();
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");
2420 RefPtr
<ThreadSafeWorkerRef
> ref
= new ThreadSafeWorkerRef(strongRef
);
2421 DispatchToMainThread(NS_NewRunnableFunction(
2422 "WorkerPrivate::UpdateIsOnContentBlockingAllowList",
2423 [ref
= std::move(ref
), aOnContentBlockingAllowList
] {
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 "
2438 "CookieJarSettings to perform the *nested check*, but we can "
2440 "on the FPI/dFPI partition key check. The WorkerPrivate's "
2441 "ShouldResistFingerprinting function for the ServiceWorker "
2443 "on this boolean and will also consider an explicit RFPTarget.",
2444 RFPTarget::IsAlwaysEnabledForPrecompute
) &&
2445 !nsContentUtils::ETPSaysShouldNotResistFingerprinting(
2446 workerCookieJarSettings
, false);
2449 ->mLoadInfo
.mCookieJarSettingsArgs
.shouldResistFingerprinting() =
2450 shouldResistFingerprinting
;
2451 ref
->Private()->mLoadInfo
.mShouldResistFingerprinting
=
2452 shouldResistFingerprinting
;
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
2464 locale = nsRFPService::GetSpoofedJSLocale();
2467 MutexAutoLock lock(mMutex);
2468 mJSSettings.chromeRealmOptions.creationOptions().setForceUTC(aEnabled);
2469 mJSSettings.chromeRealmOptions.creationOptions().setAlwaysUseFdlibm(aEnabled);
2471 mJSSettings.chromeRealmOptions.creationOptions().setLocaleCopyZ(
2475 mJSSettings.contentRealmOptions.creationOptions().setForceUTC(aEnabled);
2476 mJSSettings.contentRealmOptions.creationOptions().setAlwaysUseFdlibm(
2479 mJSSettings.contentRealmOptions.creationOptions().setLocaleCopyZ(
2485 bool WorkerPrivate::IsOnParentThread() const {
2487 return GetParent()->IsOnWorkerThread();
2489 return NS_IsMainThread();
2494 void WorkerPrivate::AssertIsOnParentThread() const {
2496 GetParent()->AssertIsOnWorkerThread();
2498 AssertIsOnMainThread();
2502 void WorkerPrivate::AssertInnerWindowIsCorrect() const {
2503 AssertIsOnParentThread();
2505 // Only care about top level workers from windows.
2506 if (mParent
|| !mLoadInfo
.mWindow
) {
2510 AssertIsOnMainThread();
2512 nsPIDOMWindowOuter
* outer
= mLoadInfo
.mWindow
->GetOuterWindow();
2513 NS_ASSERTION(outer
&& outer
->GetCurrentInnerWindow() == mLoadInfo
.mWindow
,
2514 "Inner window no longer correct!");
2519 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
2520 bool WorkerPrivate::PrincipalIsValid() const {
2521 return mLoadInfo
.PrincipalIsValid();
2525 WorkerPrivate::WorkerThreadAccessible::WorkerThreadAccessible(
2526 WorkerPrivate
* const aParent
)
2527 : mNumWorkerRefsPreventingShutdownStart(0),
2528 mDebuggerEventLoopLevel(0),
2529 mNonblockingCCBackgroundActorCount(0),
2530 mErrorHandlerRecursionCount(0),
2532 mCurrentTimerNestingLevel(0),
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) {}
2545 bool IsNewWorkerSecureContext(const WorkerPrivate
* const aParent
,
2546 const WorkerKind aWorkerKind
,
2547 const WorkerLoadInfo
& aLoadInfo
) {
2549 return aParent
->IsSecureContext();
2552 // Our secure context state depends on the kind of worker we have.
2554 if (aLoadInfo
.mPrincipal
&& aLoadInfo
.mPrincipal
->IsSystemPrincipal()) {
2558 if (aWorkerKind
== WorkerKindService
) {
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");
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
>&&
2586 : mMutex("WorkerPrivate Mutex"),
2587 mCondVar(mMutex
, "WorkerPrivate CondVar"),
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
)),
2598 mJSContext(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
),
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),
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());
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
) {
2650 if (aParent
->IsRunningInBackground()) {
2651 mIsInBackground
= true;
2654 mIsPrivilegedAddonGlobal
= aParent
->mIsPrivilegedAddonGlobal
;
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
) {
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;
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
2740 mMainThreadEventTargetForMessaging
=
2741 aParent
->mMainThreadEventTargetForMessaging
;
2742 mMainThreadEventTarget
= aParent
->mMainThreadEventTarget
;
2743 mMainThreadDebuggeeEventTarget
= aParent
->mMainThreadDebuggeeEventTarget
;
2747 MOZ_ASSERT(NS_IsMainThread());
2748 target
= GetWindow()
2749 ? GetWindow()->GetBrowsingContextGroup()->GetWorkerEventQueue()
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
;
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(); });
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
};
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
>&&
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
;
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
);
2864 AssertIsOnMainThread();
2867 Maybe
<WorkerLoadInfo
> stackLoadInfo
;
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
);
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
2886 RuntimeService
* runtimeService
;
2889 runtimeService
= RuntimeService::GetOrCreateService();
2890 if (!runtimeService
) {
2891 aRv
.Throw(NS_ERROR_FAILURE
);
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
);
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
);
2925 worker
->mDefaultLocale
= std::move(defaultLocale
);
2927 if (!runtimeService
->RegisterWorker(*worker
)) {
2928 aRv
.Throw(NS_ERROR_UNEXPECTED
);
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()
2955 RefPtr
<CompileScriptRunnable
> compiler
= new CompileScriptRunnable(
2956 worker
, std::move(stack
), aScriptURL
, aDocumentEncoding
);
2957 if (!compiler
->Dispatch(worker
)) {
2958 aRv
.Throw(NS_ERROR_UNEXPECTED
);
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
) {
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());
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
;
3028 MOZ_ASSERT_IF(NS_IsMainThread(),
3029 aCx
== nsContentUtils::GetCurrentJSContext());
3032 AssertIsOnMainThread();
3035 WorkerLoadInfo loadInfo
;
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
));
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();
3098 AssertIsOnMainThread();
3100 // Make sure that the IndexedDatabaseManager is set up
3101 IndexedDatabaseManager
* idm
= IndexedDatabaseManager::GetOrCreate();
3103 Unused
<< NS_WARN_IF(NS_FAILED(idm
->EnsureLocale()));
3105 NS_WARNING("Failed to get IndexedDatabaseManager!");
3108 nsIScriptSecurityManager
* ssm
= nsContentUtils::GetSecurityManager();
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
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
;
3138 // Only use the current inner window, and only use it if the caller can
3140 if (nsPIDOMWindowOuter
* outerWindow
= globalWindow
->GetOuterWindow()) {
3141 loadInfo
.mWindow
= outerWindow
->GetCurrentInnerWindow();
3145 OriginTrials::FromWindow(nsGlobalWindowInner::Cast(loadInfo
.mWindow
));
3147 BrowsingContext
* browsingContext
= globalWindow
->GetBrowsingContext();
3149 // TODO: fix this for SharedWorkers with multiple documents (bug
3151 loadInfo
.mServiceWorkersTestingInWindow
=
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
);
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
;
3192 tmpDoc
= tmpDoc
->GetInProcessParentDocument();
3193 } while (tmpDoc
&& tmpDoc
->GetSandboxFlags() & SANDBOXED_ORIGIN
);
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
);
3201 // No unsandboxed ancestor, use our GUID.
3202 rv
= loadInfo
.mLoadingPrincipal
->GetBaseDomain(loadInfo
.mDomain
);
3203 NS_ENSURE_SUCCESS(rv
, rv
);
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
),
3216 nsCOMPtr
<nsIPermissionManager
> permMgr
=
3217 do_GetService(NS_PERMISSIONMANAGER_CONTRACTID
, &rv
);
3218 NS_ENSURE_SUCCESS(rv
, rv
);
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
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
;
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
3291 rv
= NS_NewURI(getter_AddRefs(loadInfo
.mBaseURI
), fileName
.get());
3293 if (NS_FAILED(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
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
);
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
3385 // - use remote tabs = false
3386 nsCOMPtr
<nsILoadGroup
> loadGroup
= do_CreateInstance(NS_LOADGROUP_CONTRACTID
);
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
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
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
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();
3452 MOZ_ASSERT(debugScope
->mWorkerPrivate
== this);
3454 RefPtr
<WorkerGlobalScope
> scope
= data
->mScope
.forget();
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
);
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
);
3474 MOZ_ASSERT(mStatus
== Pending
);
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.
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
);
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
;
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;
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;
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.
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();
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
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
);
3629 MutexAutoLock
lock(mMutex
);
3630 currentStatus
= mStatus
;
3632 MOZ_ASSERT(currentStatus
== Killing
);
3634 currentStatus
= Killing
;
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();
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
);
3658 mJSContext
= nullptr;
3659 mDebuggerInterruptTimer
.swap(timer
);
3663 // After mStatus is set to Dead there can be no more
3664 // WorkerControlRunnables so no need to lock here.
3665 if (!mControlQueue
.IsEmpty()) {
3667 ("WorkerPrivate::DoRunLoop [%p] dropping control runnables in "
3670 WorkerRunnable
* runnable
= nullptr;
3671 while (mControlQueue
.Pop(runnable
)) {
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.
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.
3705 // Now *might* be a good time to GC. Let the JS engine make the
3707 JSAutoRealm
ar(aCx
, globalScope
->GetGlobalJSObject());
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
3719 JSAutoRealm
ar(aCx
, GlobalScope()->GetGlobalJSObject());
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
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!");
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();
3771 return ccjs
->RecursionDepth();
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
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
3796 void WorkerPrivate::AfterProcessNextEvent() {
3797 AssertIsOnWorkerThread();
3798 MOZ_ASSERT(GetEffectiveEventLoopRecursionDepth());
3801 nsISerialEventTarget
* WorkerPrivate::MainThreadEventTargetForMessaging() {
3802 return mMainThreadEventTargetForMessaging
;
3805 nsresult
WorkerPrivate::DispatchToMainThreadForMessaging(nsIRunnable
* aRunnable
,
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
),
3817 nsISerialEventTarget
* WorkerPrivate::MainThreadEventTarget() {
3818 return mMainThreadEventTarget
;
3821 nsresult
WorkerPrivate::DispatchToMainThread(nsIRunnable
* aRunnable
,
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(),
3840 nsISerialEventTarget
* WorkerPrivate::ControlEventTarget() {
3841 return mWorkerControlEventTarget
;
3844 nsISerialEventTarget
* WorkerPrivate::HybridEventTarget() {
3845 return mWorkerHybridEventTarget
;
3848 ClientType
WorkerPrivate::GetClientType() const {
3850 case WorkerKindDedicated
:
3851 return ClientType::Worker
;
3852 case WorkerKindShared
:
3853 return ClientType::Sharedworker
;
3854 case WorkerKindService
:
3855 return ClientType::Serviceworker
;
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
);
3870 clientSource
= ClientManager::CreateSource(
3871 GetClientType(), mWorkerHybridEventTarget
,
3872 StoragePrincipalHelper::ShouldUsePartitionPrincipalForServiceWorker(
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
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
)) {
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
) {
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.
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;
4023 WorkerStatus status
;
4025 MutexAutoLock
lock(mMutex
);
4029 if (status
>= Killing
) {
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
) {
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.
4049 // Cancel the periodic timer now, since the event loop is (in the common
4051 MOZ_ALWAYS_SUCCEEDS(data
->mPeriodicGCTimer
->Cancel());
4052 data
->mPeriodicGCTimerRunning
= false;
4054 if (data
->mIdleGCTimerRunning
) {
4059 MOZ_ASSERT(aMode
== PeriodicTimer
|| aMode
== IdleTimer
);
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));
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
) {
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;
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
);
4141 mayFreeze
= mStatus
<= Running
;
4144 if (mStatus
>= Canceling
) {
4145 mayContinue
= false;
4149 if (!mayContinue
|| !mayFreeze
) {
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()) {
4166 WaitForWorkerEvents();
4171 // We want only uncatchable exceptions here.
4172 NS_ASSERTION(!JS_IsExceptionPending(aCx
),
4173 "Should not have an exception set here!");
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();
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;
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();
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
));
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!");
4258 if (ExtensionAPIAllowed()) {
4259 MOZ_ASSERT(IsServiceWorker());
4260 RefPtr
<Runnable
> extWorkerRunnable
=
4261 extensions::CreateWorkerDestroyedRunnable(ServiceWorkerID(),
4263 // Dispatch as a low priority runnable.
4265 DispatchToMainThreadForMessaging(extWorkerRunnable
.forget()))) {
4267 "Failed to dispatch runnable to notify extensions worker "
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
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
4328 if (!data
->mMemoryReporter
) {
4332 // We don't need this set any longer. Swap it out so that we can unregister
4334 data
->mMemoryReporter
.swap(memoryReporter
);
4336 // Next disable the memory reporter so that the main thread stops trying to
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.
4357 WorkerPrivate::ProcessAllControlRunnablesResult
4358 WorkerPrivate::ProcessAllControlRunnablesLocked() {
4359 AssertIsOnWorkerThread();
4360 mMutex
.AssertCurrentThreadOwns();
4362 AutoYieldJSThreadExecution yield
;
4364 auto result
= ProcessAllControlRunnablesResult::Nothing
;
4367 WorkerRunnable
* event
;
4368 if (!mControlQueue
.Pop(event
)) {
4372 MutexAutoUnlock
unlock(mMutex
);
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
;
4392 void WorkerPrivate::ShutdownModuleLoader() {
4393 AssertIsOnWorkerThread();
4395 WorkerGlobalScope
* globalScope
= 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
]);
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
);
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
);
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).
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();
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).
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();
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();
4537 data
->mChildWorkers
[index
]->SetIsRunningInForeground();
4543 void WorkerPrivate::PropagateStorageAccessPermissionGrantedInternal() {
4544 auto data
= mWorkerThreadAccessible
.Access();
4546 mLoadInfo
.mUseRegularPrincipal
= true;
4547 mLoadInfo
.mUsingStorageAccess
= true;
4549 WorkerGlobalScope
* globalScope
= 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();
4578 WorkerStatus currentStatus
;
4580 MutexAutoLock
lock(mMutex
);
4581 currentStatus
= mStatus
;
4584 MOZ_ASSERT(currentStatus
== Running
);
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
);
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
);
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
) {
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
);
4652 void WorkerPrivate::RemoveWorkerRef(WorkerRef
* aWorkerRef
) {
4653 MOZ_ASSERT(aWorkerRef
);
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
4701 if (mShutdownTasksRun
) {
4702 return NS_ERROR_UNEXPECTED
;
4705 MOZ_ASSERT(!mShutdownTasks
.Contains(aTask
));
4706 mShutdownTasks
.AppendElement(aTask
);
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
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
);
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
));
4753 MOZ_ASSERT(data
->mNonblockingCCBackgroundActorCount
>=
4754 (uint32_t)abs(aCount
));
4758 data
->mNonblockingCCBackgroundActorCount
+= aCount
;
4761 void WorkerPrivate::UpdateCCFlag(const CCFlag aFlag
) {
4762 AssertIsOnWorkerThread();
4764 auto data
= mWorkerThreadAccessible
.Access();
4768 case CCFlag::EligibleForWorkerRef
: {
4769 MOZ_ASSERT(!data
->mNumWorkerRefsPreventingShutdownStart
);
4772 case CCFlag::IneligibleForWorkerRef
: {
4773 MOZ_ASSERT(data
->mNumWorkerRefsPreventingShutdownStart
);
4776 case CCFlag::EligibleForChildWorker
: {
4777 MOZ_ASSERT(data
->mChildWorkers
.IsEmpty());
4780 case CCFlag::IneligibleForChildWorker
: {
4781 MOZ_ASSERT(!data
->mChildWorkers
.IsEmpty());
4784 case CCFlag::EligibleForTimeout
: {
4785 MOZ_ASSERT(data
->mTimeouts
.IsEmpty());
4788 case CCFlag::IneligibleForTimeout
: {
4789 MOZ_ASSERT(!data
->mTimeouts
.IsEmpty());
4792 case CCFlag::CheckBackgroundActors
: {
4799 MutexAutoLock
lock(mMutex
);
4800 if (mStatus
> Canceling
) {
4801 mCCFlagSaysEligible
= true;
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
) {
4841 bool hasShutdownTasks
= !mShutdownTasks
.IsEmpty();
4842 bool hasPendingEvents
= false;
4845 NS_SUCCEEDED(mThread
->HasPendingEvents(&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();
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
4903 data
->mTimerRunning
= false;
4906 else if (!data
->mRunningExpiredTimeouts
) {
4907 NS_ASSERTION(data
->mTimeouts
.IsEmpty(), "Huh?!");
4911 data
->mTimer
= nullptr;
4912 data
->mTimerRunnable
= nullptr;
4915 already_AddRefed
<nsISerialEventTarget
> WorkerPrivate::CreateNewSyncLoop(
4916 WorkerStatus aFailStatus
) {
4917 AssertIsOnWorkerThread();
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
) {
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.
4945 MutexAutoLock
lock(mMutex
);
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();
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
4970 AutoPushEventLoopGlobal
eventLoopGlobal(this, cx
);
4972 // This should not change between now and the time we finish running this sync
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
);
4985 loopInfo
->mHasRun
= true;
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
);
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
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
5018 if (loopInfo
->mCompleted
) {
5023 // If we *didn't* run any control runnables, this should be unchanged.
5024 MOZ_ASSERT(!loopInfo
->mCompleted
);
5026 if (normalRunnablesPending
) {
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
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
));
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
);
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
)) {
5088 ("WorkerPrivate::DestroySyncLoop [%p] Dispatching CancelingRunnables",
5090 DispatchCancelingRunnable();
5093 mPostSyncLoopOperations
= 0;
5097 // Modifications must be protected by mMutex in DEBUG builds, see comment
5098 // about mSyncLoopStack in WorkerPrivate.h.
5100 MutexAutoLock
lock(mMutex
);
5103 // This will delete |loopInfo|!
5104 mSyncLoopStack
.RemoveElementAt(aLoopIndex
);
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",
5129 RefPtr
<CancelingWithTimeoutOnParentRunnable
> rr
=
5130 new CancelingWithTimeoutOnParentRunnable(this);
5134 void WorkerPrivate::ReportUseCounters() {
5135 AssertIsOnWorkerThread();
5137 if (mReportedUseCounters
) {
5140 mReportedUseCounters
= true;
5142 if (IsChromeWorker()) {
5146 const size_t kind
= Kind();
5148 case WorkerKindDedicated
:
5149 glean::use_counter::dedicated_workers_destroyed
.Add();
5151 case WorkerKindShared
:
5152 glean::use_counter::shared_workers_destroyed
.Add();
5154 case WorkerKindService
:
5155 glean::use_counter::service_workers_destroyed
.Add();
5158 MOZ_ASSERT(false, "Unknown worker kind");
5162 Maybe
<nsCString
> workerPathForLogging
;
5163 const bool dumpCounters
= StaticPrefs::dom_use_counters_dump_worker();
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
))) {
5180 const char* metricName
=
5181 IncrementWorkerUseCounter(static_cast<UseCounterWorker
>(c
), workerKind
);
5183 printf_stderr("USE_COUNTER_WORKER: %s - %s\n", metricName
,
5184 workerPathForLogging
->get());
5189 void WorkerPrivate::StopSyncLoop(nsIEventTarget
* aSyncLoopTarget
,
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
,
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();
5221 MOZ_ASSERT(!SameCOMIdentity(loopInfo
->mEventTarget
, aSyncLoopTarget
));
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
);
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
) {
5252 MOZ_ASSERT(!SameCOMIdentity(loopInfo
->mEventTarget
, aSyncLoopTarget
));
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
,
5271 if (NS_WARN_IF(aRv
.Failed())) {
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())) {
5293 if (!runnable
->Dispatch(this)) {
5294 aRv
= NS_ERROR_FAILURE
;
5298 void WorkerPrivate::EnterDebuggerEventLoop() {
5299 auto data
= mWorkerThreadAccessible
.Access();
5301 JSContext
* cx
= GetJSContext();
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
));
5359 void WorkerPrivate::LeaveDebuggerEventLoop() {
5360 auto data
= mWorkerThreadAccessible
.Access();
5362 // TODO: Why lock the mutex if we're accessing data accessible to one thread
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
,
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
,
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
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
);
5408 ("WorkerPrivate::NotifyInternal [%p] mStatus: %u, aStatus: %u", this,
5409 static_cast<uint8_t>(mStatus
), static_cast<uint8_t>(aStatus
)));
5411 if (mStatus
>= aStatus
) {
5415 MOZ_ASSERT_IF(aStatus
== Killing
,
5416 mStatus
== Canceling
&& mParentStatus
== Canceling
);
5420 // Mark parent status as closing immediately to avoid new events being
5421 // dispatched after we clear the queue below.
5422 if (aStatus
== Closing
) {
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
) {
5437 if (aStatus
== Canceling
) {
5438 data
->mScope
->NoteTerminating();
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
5469 WorkerGlobalScope
* global
= GlobalScope();
5471 if (aStatus
== Canceling
) {
5472 MOZ_ASSERT(!data
->mCancelBeforeWorkerScopeConstructed
);
5473 data
->mCancelBeforeWorkerScopeConstructed
.Flip();
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...",
5485 mPostSyncLoopOperations
|= eDispatchCancelingRunnable
;
5487 DispatchCancelingRunnable();
5492 MOZ_ASSERT(aStatus
== Canceling
|| aStatus
== Killing
);
5494 LOG(WorkerLog(), ("WorkerPrivate::NotifyInternal [%p] abort script", this));
5496 // Always abort the script.
5500 void WorkerPrivate::ReportError(JSContext
* aCx
,
5501 JS::ConstUTF8CharsZ aToStringResult
,
5502 JSErrorReport
* aReport
) {
5503 auto data
= mWorkerThreadAccessible
.Access();
5505 if (!MayContinueRunning() || data
->mErrorHandlerRecursionCount
== 2) {
5509 NS_ASSERTION(data
->mErrorHandlerRecursionCount
== 0 ||
5510 data
->mErrorHandlerRecursionCount
== 1,
5511 "Bad recursion logic!");
5513 UniquePtr
<WorkerErrorReport
> report
= MakeUnique
<WorkerErrorReport
>();
5515 report
->AssignErrorReport(aReport
);
5518 JS::ExceptionStack
exnStack(aCx
);
5519 if (JS_IsExceptionPending(aCx
)) {
5520 if (!JS::StealPendingExceptionStack(aCx
, &exnStack
)) {
5521 JS_ClearPendingException(aCx
);
5525 JS::Rooted
<JSObject
*> stack(aCx
), stackGlobal(aCx
);
5526 xpc::FindExceptionStackForConsoleReport(
5527 nullptr, exnStack
.exception(), exnStack
.stack(), &stack
, &stackGlobal
);
5530 JSAutoRealm
ar(aCx
, stackGlobal
);
5531 report
->SerializeWorkerStack(aCx
, this, stack
);
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(),
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
--;
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
,
5597 if (NS_FAILED(rv
)) {
5598 aRv
.Throw(NS_ERROR_FAILURE
);
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
) {
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
);
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
);
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
);
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;
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
5711 if (data
->mRunningExpiredTimeouts
|| !data
->mTimerRunning
) {
5715 NS_ASSERTION(data
->mTimer
&& data
->mTimerRunnable
, "Must have a timer!");
5716 NS_ASSERTION(!data
->mTimeouts
.IsEmpty(), "Should have some work to do!");
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()));
5732 double microseconds
= (now
- actual_now
).ToMicroseconds();
5733 uint32_t allowedEarlyFiringMicroseconds
;
5734 data
->mTimer
->GetAllowedEarlyFiringMicroseconds(
5735 &allowedEarlyFiringMicroseconds
);
5736 MOZ_ASSERT(microseconds
< allowedEarlyFiringMicroseconds
);
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
) {
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
) {
5763 // Set current timer nesting level to current running timer handler's
5765 data
->mCurrentTimerNestingLevel
= info
->mNestingLevel
;
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
);
5778 switch (info
->mReason
) {
5779 case Timeout::Reason::eTimeoutOrInterval
:
5780 if (info
->mIsInterval
) {
5781 reason
= "setInterval handler";
5783 reason
= "setTimeout handler";
5786 case Timeout::Reason::eDelayedWebTaskTimeout
:
5787 reason
= "delayedWebTask handler";
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
)) {
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
) ||
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.
5838 data
->mTimeouts
.RemoveElement(info
);
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!");
5849 data
->mTimeouts
.Sort(comparator
);
5851 // Either signal the parent that we're no longer using timeouts or reschedule
5853 if (data
->mTimeouts
.IsEmpty()) {
5854 UpdateCCFlag(CCFlag::EligibleForTimeout
);
5855 data
->mTimerRunning
= false;
5856 } else if (retval
&& !RescheduleTimeoutTimer(aCx
)) {
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();
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
))))
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!");
5897 void WorkerPrivate::StartCancelingTimer() {
5898 AssertIsOnParentThread();
5900 // return if mCancelingTimer has already existed.
5901 if (mCancelingTimer
) {
5905 auto errorCleanup
= MakeScopeExit([&] { mCancelingTimer
= nullptr; });
5907 if (WorkerPrivate
* parent
= GetParent()) {
5908 mCancelingTimer
= NS_NewTimer(parent
->ControlEventTarget());
5910 mCancelingTimer
= NS_NewTimer();
5913 if (NS_WARN_IF(!mCancelingTimer
)) {
5917 // This is not needed if we are already in an advanced shutdown state.
5919 MutexAutoLock
lock(mMutex
);
5920 if (ParentStatus() >= Canceling
) {
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
))) {
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();
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();
5975 JS_SetGCParameter(aCx
, aKey
, *aValue
);
5977 JS_ResetGCParameter(aCx
, aKey
);
5980 for (uint32_t index
= 0; index
< data
->mChildWorkers
.Length(); index
++) {
5981 data
->mChildWorkers
[index
]->UpdateJSWorkerMemoryParameter(aKey
, aValue
);
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
);
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.
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));
6058 JS::NonIncrementalGC(aCx
, JS::GCOptions::Normal
,
6059 JS::GCReason::DOM_WORKER
);
6060 LOG(WorkerLog(), ("Worker %p collected garbage\n", this));
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();
6090 RefPtr
<Console
> console
= data
->mScope
->GetConsoleIfExists();
6092 console
->ClearStorage();
6095 RefPtr
<Performance
> performance
= data
->mScope
->GetPerformanceIfExists();
6097 performance
->MemoryPressure();
6100 data
->mScope
->RemoveReportRecords();
6103 if (data
->mDebuggerScope
) {
6104 RefPtr
<Console
> console
= data
->mDebuggerScope
->GetConsoleIfExists();
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
) {
6119 bool isOnCurrentThread
;
6120 MOZ_ASSERT(NS_SUCCEEDED(aThread
->IsOnCurrentThread(&isOnCurrentThread
)));
6121 MOZ_ASSERT(!isOnCurrentThread
);
6125 MOZ_ASSERT(!mPRThread
);
6126 mPRThread
= PRThreadFromThread(aThread
);
6127 MOZ_ASSERT(mPRThread
);
6129 mWorkerThreadAccessible
.Transfer(mPRThread
);
6131 MOZ_ASSERT(mPRThread
);
6135 void WorkerPrivate::SetWorkerPrivateInWorkerThread(
6136 WorkerThread
* const aThread
) {
6138 ("WorkerPrivate::SetWorkerPrivateInWorkerThread [%p]", this));
6139 MutexAutoLock
lock(mMutex
);
6141 MOZ_ASSERT(!mThread
);
6142 MOZ_ASSERT(mStatus
== Pending
);
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() {
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.
6221 RefPtr
<MessagePort
> port
= MessagePort::Create(globalScope
, aIdentifier
, rv
);
6222 if (NS_WARN_IF(rv
.Failed())) {
6223 rv
.SuppressException();
6227 GlobalObject
globalObject(aCx
, jsGlobal
);
6228 if (globalObject
.Failed()) {
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
)) {
6241 RefPtr
<MessageEvent
> event
=
6242 MessageEvent::Constructor(globalObject
, u
"connect"_ns
, init
);
6244 event
->SetTrusted(true);
6246 globalScope
->DispatchEvent(*event
);
6251 WorkerGlobalScope
* WorkerPrivate::GetOrCreateGlobalScope(JSContext
* aCx
) {
6252 auto data
= mWorkerThreadAccessible
.Access();
6255 return data
->mScope
;
6258 if (IsSharedWorker()) {
6260 new SharedWorkerGlobalScope(this, CreateClientSource(), WorkerName());
6261 } else if (IsServiceWorker()) {
6262 data
->mScope
= new ServiceWorkerGlobalScope(
6263 this, CreateClientSource(), GetServiceWorkerRegistrationDescriptor());
6265 data
->mScope
= new DedicatedWorkerGlobalScope(this, CreateClientSource(),
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;
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(
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
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;
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
6322 MOZ_ASSERT(mPRThread
,
6323 "AssertIsOnWorkerThread() called before a thread was assigned!");
6325 return mPRThread
== PR_GetCurrentThread();
6329 void WorkerPrivate::AssertIsOnWorkerThread() const {
6330 MOZ_ASSERT(IsOnWorkerThread());
6334 void WorkerPrivate::DumpCrashInformation(nsACString
& aString
) {
6335 auto data
= mWorkerThreadAccessible
.Access();
6337 aString
.Append("IsChromeWorker(");
6338 if (IsChromeWorker()) {
6339 aString
.Append(NS_ConvertUTF16toUTF8(ScriptURL()));
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
;
6391 return GenericPromise::CreateAndReject(NS_ERROR_DOM_ABORT_ERR
, __func__
);
6394 RefPtr
<GenericPromise
> promise
=
6395 rwc
->MaybeSendSetServiceWorkerSkipWaitingFlag();
6400 const nsAString
& WorkerPrivate::Id() {
6401 AssertIsOnMainThread();
6403 if (mId
.IsEmpty()) {
6404 mId
= ComputeWorkerPrivateId();
6407 MOZ_ASSERT(!mId
.IsEmpty());
6412 bool WorkerPrivate::IsSharedMemoryAllowed() const {
6414 dom_postMessage_sharedArrayBuffer_bypassCOOP_COEP_insecure_enabled()) {
6418 // Allow privileged addons to access shared memory.
6419 if (mIsPrivilegedAddonGlobal
) {
6423 return CrossOriginIsolated();
6426 bool WorkerPrivate::CrossOriginIsolated() const {
6428 dom_postMessage_sharedArrayBuffer_withCOOP_COEP_AtStartup()) {
6432 return mAgentClusterOpenerPolicy
==
6433 nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP
;
6436 nsILoadInfo::CrossOriginEmbedderPolicy
WorkerPrivate::GetEmbedderPolicy()
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()) {
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(
6462 !nsContentSecurityManager::IsCompatibleWithCrossOriginIsolation(
6464 return Err(NS_ERROR_BLOCKED_BY_POLICY
);
6467 mEmbedderPolicy
.emplace(aPolicy
);
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()) {
6505 return mEmbedderPolicy
.value() == aPolicy
;
6508 nsILoadInfo::CrossOriginEmbedderPolicy
WorkerPrivate::GetOwnerEmbedderPolicy()
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());
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
)
6558 // kDEBUGWorkerEventTargetIID is special in that it does not AddRef its
6560 if (aIID
.Equals(kDEBUGWorkerEventTargetIID
)) {
6561 *aInstancePtr
= this;
6565 NS_INTERFACE_MAP_END
6568 WorkerPrivate::EventTarget::DispatchFromScript(nsIRunnable
* aRunnable
,
6570 nsCOMPtr
<nsIRunnable
> event(aRunnable
);
6571 return Dispatch(event
.forget(), aFlags
);
6575 WorkerPrivate::EventTarget::Dispatch(already_AddRefed
<nsIRunnable
> aRunnable
,
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
);
6591 "A runnable was posted to a worker that is already shutting "
6593 return NS_ERROR_UNEXPECTED
;
6596 MOZ_ASSERT(mWorkerPrivate
);
6597 MOZ_ASSERT(mNestedEventTarget
);
6600 workerRunnable
= mWorkerPrivate
->MaybeWrapAsWorkerRunnable(event
.forget());
6604 mWorkerPrivate
->Dispatch(workerRunnable
.forget(), mNestedEventTarget
);
6605 if (NS_WARN_IF(NS_FAILED(rv
))) {
6613 WorkerPrivate::EventTarget::DelayedDispatch(already_AddRefed
<nsIRunnable
>,
6617 return NS_ERROR_NOT_IMPLEMENTED
;
6621 WorkerPrivate::EventTarget::RegisterShutdownTask(nsITargetShutdownTask
* aTask
) {
6622 return NS_ERROR_NOT_IMPLEMENTED
;
6626 WorkerPrivate::EventTarget::UnregisterShutdownTask(
6627 nsITargetShutdownTask
* aTask
) {
6628 return NS_ERROR_NOT_IMPLEMENTED
;
6632 WorkerPrivate::EventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread
) {
6633 // May be called on any thread!
6635 MOZ_ASSERT(aIsOnCurrentThread
);
6637 MutexAutoLock
lock(mMutex
);
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();
6651 NS_IMETHODIMP_(bool)
6652 WorkerPrivate::EventTarget::IsOnCurrentThreadInfallible() {
6653 // May be called on any thread!
6655 MutexAutoLock
lock(mMutex
);
6659 "A worker's event target was used after the worker has shutdown!");
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
);
6676 mNewEventLoopGlobal
= data
->mCurrentEventLoopGlobal
;
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();
6687 // Saved event loop global should be matched.
6688 MOZ_ASSERT(data
->mCurrentEventLoopGlobal
== mNewEventLoopGlobal
);
6689 mNewEventLoopGlobal
= nullptr;
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();
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.
6710 ("AutoSyncLoopHolder::AutoSyncLoopHolder Worker starts to shutdown "
6711 "with a AutoSyncLoopHolder(%s).",
6716 ("AutoSyncLoopHolder::AutoSyncLoopHolder [%p] Create "
6717 "AutoSyncLoopHolder(%s) while Worker is shutting down",
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() {
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;
6751 nsISerialEventTarget
* AutoSyncLoopHolder::GetSerialEventTarget() const {
6752 // This can be null if CreateNewSyncLoop() fails.
6756 // -----------------------------------------------------------------------------
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;
6784 } // namespace mozilla