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 "WorkletThread.h"
9 #include "nsContentUtils.h"
10 #include "nsCycleCollector.h"
11 #include "mozilla/dom/AtomList.h"
12 #include "mozilla/ipc/BackgroundChild.h"
13 #include "mozilla/Attributes.h"
14 #include "mozilla/EventQueue.h"
15 #include "mozilla/ThreadEventQueue.h"
16 #include "js/Exception.h"
23 // The size of the worklet runtime heaps in bytes.
24 #define WORKLET_DEFAULT_RUNTIME_HEAPSIZE 32 * 1024 * 1024
26 // The C stack size. We use the same stack size on all platforms for
28 const uint32_t kWorkletStackSize
= 256 * sizeof(size_t) * 1024;
30 // Half the size of the actual C stack, to be safe.
31 #define WORKLET_CONTEXT_NATIVE_STACK_LIMIT 128 * sizeof(size_t) * 1024
35 bool PreserveWrapper(JSContext
* aCx
, JS::HandleObject aObj
) {
38 MOZ_ASSERT(mozilla::dom::IsDOMObject(aObj
));
39 return mozilla::dom::TryPreserveWrapper(aObj
);
42 JSObject
* Wrap(JSContext
* aCx
, JS::HandleObject aExisting
,
43 JS::HandleObject aObj
) {
45 js::Wrapper::Renew(aExisting
, aObj
,
46 &js::OpaqueCrossCompartmentWrapper::singleton
);
49 return js::Wrapper::New(aCx
, aObj
,
50 &js::OpaqueCrossCompartmentWrapper::singleton
);
53 const JSWrapObjectCallbacks WrapObjectCallbacks
= {
60 // This classes control CC in the worklet thread.
62 class WorkletJSRuntime final
: public mozilla::CycleCollectedJSRuntime
{
64 explicit WorkletJSRuntime(JSContext
* aCx
) : CycleCollectedJSRuntime(aCx
) {}
66 ~WorkletJSRuntime() override
= default;
68 virtual void PrepareForForgetSkippable() override
{}
70 virtual void BeginCycleCollectionCallback() override
{}
72 virtual void EndCycleCollectionCallback(
73 CycleCollectorResults
& aResults
) override
{}
75 virtual void DispatchDeferredDeletion(bool aContinuation
,
76 bool aPurge
) override
{
77 MOZ_ASSERT(!aContinuation
);
78 nsCycleCollector_doDeferredDeletion();
81 virtual void CustomGCCallback(JSGCStatus aStatus
) override
{
82 // nsCycleCollector_collect() requires a cycle collector but
83 // ~WorkletJSContext calls nsCycleCollector_shutdown() and the base class
84 // destructor will trigger a final GC. The nsCycleCollector_collect()
85 // call can be skipped in this GC as ~CycleCollectedJSContext removes the
86 // context from |this|.
87 if (aStatus
== JSGC_END
&& GetContext()) {
88 nsCycleCollector_collect(nullptr);
93 class WorkletJSContext final
: public CycleCollectedJSContext
{
96 MOZ_ASSERT(!NS_IsMainThread());
98 nsCycleCollector_startup();
101 // MOZ_CAN_RUN_SCRIPT_BOUNDARY because otherwise we have to annotate the
102 // SpiderMonkey JS::JobQueue's destructor as MOZ_CAN_RUN_SCRIPT, which is a
104 MOZ_CAN_RUN_SCRIPT_BOUNDARY
~WorkletJSContext() override
{
105 MOZ_ASSERT(!NS_IsMainThread());
107 JSContext
* cx
= MaybeContext();
109 return; // Initialize() must have failed
112 nsCycleCollector_shutdown();
115 WorkletJSContext
* GetAsWorkletJSContext() override
{ return this; }
117 CycleCollectedJSRuntime
* CreateRuntime(JSContext
* aCx
) override
{
118 return new WorkletJSRuntime(aCx
);
121 nsresult
Initialize(JSRuntime
* aParentRuntime
) {
122 MOZ_ASSERT(!NS_IsMainThread());
124 nsresult rv
= CycleCollectedJSContext::Initialize(
125 aParentRuntime
, WORKLET_DEFAULT_RUNTIME_HEAPSIZE
);
126 if (NS_WARN_IF(NS_FAILED(rv
))) {
130 JSContext
* cx
= Context();
132 js::SetPreserveWrapperCallback(cx
, PreserveWrapper
);
133 JS_InitDestroyPrincipalsCallback(cx
, WorkletPrincipals::Destroy
);
134 JS_SetWrapObjectCallbacks(cx
, &WrapObjectCallbacks
);
135 JS_SetFutexCanWait(cx
);
140 void DispatchToMicroTask(
141 already_AddRefed
<MicroTaskRunnable
> aRunnable
) override
{
142 RefPtr
<MicroTaskRunnable
> runnable(aRunnable
);
144 MOZ_ASSERT(!NS_IsMainThread());
145 MOZ_ASSERT(runnable
);
147 JSContext
* cx
= Context();
151 JS::Rooted
<JSObject
*> global(cx
, JS::CurrentGlobalOrNull(cx
));
155 JS::JobQueueMayNotBeEmpty(cx
);
156 GetMicroTaskQueue().push(std::move(runnable
));
159 bool IsSystemCaller() const override
{
160 // Currently no support for special system worklet privileges.
164 void ReportError(JSErrorReport
* aReport
,
165 JS::ConstUTF8CharsZ aToStringResult
) override
;
167 uint64_t GetCurrentWorkletWindowID() {
168 JSObject
* global
= JS::CurrentGlobalOrNull(Context());
169 if (NS_WARN_IF(!global
)) {
172 nsIGlobalObject
* nativeGlobal
= xpc::NativeGlobal(global
);
173 nsCOMPtr
<WorkletGlobalScope
> workletGlobal
=
174 do_QueryInterface(nativeGlobal
);
175 if (NS_WARN_IF(!workletGlobal
)) {
178 return workletGlobal
->Impl()->LoadInfo().InnerWindowID();
182 void WorkletJSContext::ReportError(JSErrorReport
* aReport
,
183 JS::ConstUTF8CharsZ aToStringResult
) {
184 RefPtr
<xpc::ErrorReport
> xpcReport
= new xpc::ErrorReport();
185 xpcReport
->Init(aReport
, aToStringResult
.c_str(), IsSystemCaller(),
186 GetCurrentWorkletWindowID());
187 RefPtr
<AsyncErrorReporter
> reporter
= new AsyncErrorReporter(xpcReport
);
189 JSContext
* cx
= Context();
190 if (JS_IsExceptionPending(cx
)) {
191 JS::ExceptionStack
exnStack(cx
);
192 if (JS::StealPendingExceptionStack(cx
, &exnStack
)) {
193 JS::Rooted
<JSObject
*> stack(cx
);
194 JS::Rooted
<JSObject
*> stackGlobal(cx
);
195 xpc::FindExceptionStackForConsoleReport(nullptr, exnStack
.exception(),
196 exnStack
.stack(), &stack
,
199 reporter
->SerializeStack(cx
, stack
);
204 NS_DispatchToMainThread(reporter
);
207 // This is the first runnable to be dispatched. It calls the RunEventLoop() so
208 // basically everything happens into this runnable. The reason behind this
209 // approach is that, when the Worklet is terminated, it must not have any JS in
210 // stack, but, because we have CC, nsIThread creates an AutoNoJSAPI object by
211 // default. Using this runnable, CC exists only into it.
212 class WorkletThread::PrimaryRunnable final
: public Runnable
{
214 explicit PrimaryRunnable(WorkletThread
* aWorkletThread
)
215 : Runnable("WorkletThread::PrimaryRunnable"),
216 mWorkletThread(aWorkletThread
) {
217 MOZ_ASSERT(aWorkletThread
);
218 MOZ_ASSERT(NS_IsMainThread());
223 mWorkletThread
->RunEventLoop();
228 RefPtr
<WorkletThread
> mWorkletThread
;
231 // This is the last runnable to be dispatched. It calls the TerminateInternal()
232 class WorkletThread::TerminateRunnable final
: public Runnable
{
234 explicit TerminateRunnable(WorkletThread
* aWorkletThread
)
235 : Runnable("WorkletThread::TerminateRunnable"),
236 mWorkletThread(aWorkletThread
) {
237 MOZ_ASSERT(aWorkletThread
);
238 MOZ_ASSERT(NS_IsMainThread());
243 mWorkletThread
->TerminateInternal();
248 RefPtr
<WorkletThread
> mWorkletThread
;
251 WorkletThread::WorkletThread(WorkletImpl
* aWorkletImpl
)
252 : nsThread(MakeNotNull
<ThreadEventQueue
<mozilla::EventQueue
>*>(
253 MakeUnique
<mozilla::EventQueue
>()),
254 nsThread::NOT_MAIN_THREAD
, kWorkletStackSize
),
255 mWorkletImpl(aWorkletImpl
),
257 mIsTerminating(false) {
258 MOZ_ASSERT(NS_IsMainThread());
259 nsContentUtils::RegisterShutdownObserver(this);
262 WorkletThread::~WorkletThread() = default;
265 already_AddRefed
<WorkletThread
> WorkletThread::Create(
266 WorkletImpl
* aWorkletImpl
) {
267 RefPtr
<WorkletThread
> thread
= new WorkletThread(aWorkletImpl
);
268 if (NS_WARN_IF(NS_FAILED(thread
->Init(NS_LITERAL_CSTRING("DOM Worklet"))))) {
272 RefPtr
<PrimaryRunnable
> runnable
= new PrimaryRunnable(thread
);
273 if (NS_WARN_IF(NS_FAILED(thread
->DispatchRunnable(runnable
.forget())))) {
277 return thread
.forget();
280 nsresult
WorkletThread::DispatchRunnable(
281 already_AddRefed
<nsIRunnable
> aRunnable
) {
282 nsCOMPtr
<nsIRunnable
> runnable(aRunnable
);
283 return nsThread::Dispatch(runnable
.forget(), NS_DISPATCH_NORMAL
);
287 WorkletThread::DispatchFromScript(nsIRunnable
* aRunnable
, uint32_t aFlags
) {
288 nsCOMPtr
<nsIRunnable
> runnable(aRunnable
);
289 return Dispatch(runnable
.forget(), aFlags
);
293 WorkletThread::Dispatch(already_AddRefed
<nsIRunnable
> aRunnable
,
295 nsCOMPtr
<nsIRunnable
> runnable(aRunnable
);
297 // Worklet only supports asynchronous dispatch.
298 if (NS_WARN_IF(aFlags
!= NS_DISPATCH_NORMAL
)) {
299 return NS_ERROR_UNEXPECTED
;
302 return nsThread::Dispatch(runnable
.forget(), NS_DISPATCH_NORMAL
);
306 WorkletThread::DelayedDispatch(already_AddRefed
<nsIRunnable
>, uint32_t aFlags
) {
307 return NS_ERROR_NOT_IMPLEMENTED
;
310 static bool DispatchToEventLoop(void* aClosure
,
311 JS::Dispatchable
* aDispatchable
) {
312 // This callback may execute either on the worklet thread or a random
313 // JS-internal helper thread.
315 // See comment at JS::InitDispatchToEventLoop() below for how we know the
316 // WorkletThread is alive.
317 WorkletThread
* workletThread
= reinterpret_cast<WorkletThread
*>(aClosure
);
319 nsresult rv
= workletThread
->DispatchRunnable(NS_NewRunnableFunction(
320 "WorkletThread::DispatchToEventLoop", [aDispatchable
]() {
321 CycleCollectedJSContext
* ccjscx
= CycleCollectedJSContext::Get();
326 WorkletJSContext
* wjc
= ccjscx
->GetAsWorkletJSContext();
331 aDispatchable
->run(wjc
->Context(), JS::Dispatchable::NotShuttingDown
);
334 return NS_SUCCEEDED(rv
);
337 void WorkletThread::EnsureCycleCollectedJSContext(JSRuntime
* aParentRuntime
) {
338 CycleCollectedJSContext
* ccjscx
= CycleCollectedJSContext::Get();
340 MOZ_ASSERT(ccjscx
->GetAsWorkletJSContext());
344 WorkletJSContext
* context
= new WorkletJSContext();
345 nsresult rv
= context
->Initialize(aParentRuntime
);
346 if (NS_WARN_IF(NS_FAILED(rv
))) {
347 // TODO: error propagation
351 JS_SetGCParameter(context
->Context(), JSGC_MAX_BYTES
, uint32_t(-1));
353 // FIXME: JS_SetDefaultLocale
355 // FIXME: JS_SetSecurityCallbacks
356 // FIXME: JS::SetAsyncTaskCallbacks
357 // FIXME: JS::SetCTypesActivityCallback
358 // FIXME: JS_SetGCZeal
360 // A WorkletThread lives strictly longer than its JSRuntime so we can safely
361 // store a raw pointer as the callback's closure argument on the JSRuntime.
362 JS::InitDispatchToEventLoop(context
->Context(), DispatchToEventLoop
,
365 JS_SetNativeStackQuota(context
->Context(),
366 WORKLET_CONTEXT_NATIVE_STACK_LIMIT
);
368 if (!JS::InitSelfHostedCode(context
->Context())) {
369 // TODO: error propagation
374 void WorkletThread::RunEventLoop() {
375 MOZ_ASSERT(!NS_IsMainThread());
377 PR_SetCurrentThreadName("worklet");
380 MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(this, /* wait: */ true));
383 DeleteCycleCollectedJSContext();
386 void WorkletThread::Terminate() {
387 MOZ_ASSERT(NS_IsMainThread());
389 if (mIsTerminating
) {
390 // nsThread::Dispatch() would leak the runnable if the event queue is no
391 // longer accepting runnables.
395 mIsTerminating
= true;
397 nsContentUtils::UnregisterShutdownObserver(this);
399 RefPtr
<TerminateRunnable
> runnable
= new TerminateRunnable(this);
400 DispatchRunnable(runnable
.forget());
403 void WorkletThread::TerminateInternal() {
404 MOZ_ASSERT(!CycleCollectedJSContext::Get() || IsOnWorkletThread());
408 nsCOMPtr
<nsIRunnable
> runnable
= NewRunnableMethod(
409 "WorkletThread::Shutdown", this, &WorkletThread::Shutdown
);
410 NS_DispatchToMainThread(runnable
);
414 void WorkletThread::DeleteCycleCollectedJSContext() {
415 CycleCollectedJSContext
* ccjscx
= CycleCollectedJSContext::Get();
420 // Release any MessagePort kept alive by its ipc actor.
421 mozilla::ipc::BackgroundChild::CloseForCurrentThread();
423 WorkletJSContext
* workletjscx
= ccjscx
->GetAsWorkletJSContext();
424 MOZ_ASSERT(workletjscx
);
429 bool WorkletThread::IsOnWorkletThread() {
430 CycleCollectedJSContext
* ccjscx
= CycleCollectedJSContext::Get();
431 return ccjscx
&& ccjscx
->GetAsWorkletJSContext();
435 void WorkletThread::AssertIsOnWorkletThread() {
436 MOZ_ASSERT(IsOnWorkletThread());
441 WorkletThread::Observe(nsISupports
* aSubject
, const char* aTopic
,
443 MOZ_ASSERT(strcmp(aTopic
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
) == 0);
445 // The WorkletImpl will terminate the worklet thread after sending a message
446 // to release worklet thread objects.
447 mWorkletImpl
->NotifyWorkletFinished();
451 NS_IMPL_ISUPPORTS_INHERITED(WorkletThread
, nsThread
, nsIObserver
)
454 } // namespace mozilla