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