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 "nsJSEnvironment.h"
12 #include "mozilla/dom/AtomList.h"
13 #include "mozilla/dom/WorkletGlobalScope.h"
14 #include "mozilla/dom/WorkletPrincipals.h"
15 #include "mozilla/ipc/BackgroundChild.h"
16 #include "mozilla/Attributes.h"
17 #include "mozilla/CycleCollectedJSRuntime.h"
18 #include "mozilla/EventQueue.h"
19 #include "mozilla/ThreadEventQueue.h"
20 #include "js/Exception.h"
27 // The size of the worklet runtime heaps in bytes.
28 #define WORKLET_DEFAULT_RUNTIME_HEAPSIZE 32 * 1024 * 1024
30 // The C stack size. We use the same stack size on all platforms for
32 const uint32_t kWorkletStackSize
= 256 * sizeof(size_t) * 1024;
34 // Half the size of the actual C stack, to be safe.
35 #define WORKLET_CONTEXT_NATIVE_STACK_LIMIT 128 * sizeof(size_t) * 1024
39 bool PreserveWrapper(JSContext
* aCx
, JS::HandleObject aObj
) {
42 MOZ_ASSERT(mozilla::dom::IsDOMObject(aObj
));
43 return mozilla::dom::TryPreserveWrapper(aObj
);
46 JSObject
* Wrap(JSContext
* aCx
, JS::HandleObject aExisting
,
47 JS::HandleObject aObj
) {
49 js::Wrapper::Renew(aExisting
, aObj
,
50 &js::OpaqueCrossCompartmentWrapper::singleton
);
53 return js::Wrapper::New(aCx
, aObj
,
54 &js::OpaqueCrossCompartmentWrapper::singleton
);
57 const JSWrapObjectCallbacks WrapObjectCallbacks
= {
64 // This classes control CC in the worklet thread.
66 class WorkletJSRuntime final
: public mozilla::CycleCollectedJSRuntime
{
68 explicit WorkletJSRuntime(JSContext
* aCx
) : CycleCollectedJSRuntime(aCx
) {}
70 ~WorkletJSRuntime() override
= default;
72 virtual void PrepareForForgetSkippable() override
{}
74 virtual void BeginCycleCollectionCallback() override
{}
76 virtual void EndCycleCollectionCallback(
77 CycleCollectorResults
& aResults
) override
{}
79 virtual void DispatchDeferredDeletion(bool aContinuation
,
80 bool aPurge
) override
{
81 MOZ_ASSERT(!aContinuation
);
82 nsCycleCollector_doDeferredDeletion();
85 virtual void CustomGCCallback(JSGCStatus aStatus
) override
{
86 // nsCycleCollector_collect() requires a cycle collector but
87 // ~WorkletJSContext calls nsCycleCollector_shutdown() and the base class
88 // destructor will trigger a final GC. The nsCycleCollector_collect()
89 // call can be skipped in this GC as ~CycleCollectedJSContext removes the
90 // context from |this|.
91 if (aStatus
== JSGC_END
&& GetContext()) {
92 nsCycleCollector_collect(nullptr);
97 class WorkletJSContext final
: public CycleCollectedJSContext
{
100 MOZ_ASSERT(!NS_IsMainThread());
102 nsCycleCollector_startup();
105 // MOZ_CAN_RUN_SCRIPT_BOUNDARY because otherwise we have to annotate the
106 // SpiderMonkey JS::JobQueue's destructor as MOZ_CAN_RUN_SCRIPT, which is a
108 MOZ_CAN_RUN_SCRIPT_BOUNDARY
~WorkletJSContext() override
{
109 MOZ_ASSERT(!NS_IsMainThread());
111 JSContext
* cx
= MaybeContext();
113 return; // Initialize() must have failed
116 nsCycleCollector_shutdown();
119 WorkletJSContext
* GetAsWorkletJSContext() override
{ return this; }
121 CycleCollectedJSRuntime
* CreateRuntime(JSContext
* aCx
) override
{
122 return new WorkletJSRuntime(aCx
);
125 nsresult
Initialize(JSRuntime
* aParentRuntime
) {
126 MOZ_ASSERT(!NS_IsMainThread());
128 nsresult rv
= CycleCollectedJSContext::Initialize(
129 aParentRuntime
, WORKLET_DEFAULT_RUNTIME_HEAPSIZE
);
130 if (NS_WARN_IF(NS_FAILED(rv
))) {
134 JSContext
* cx
= Context();
136 js::SetPreserveWrapperCallbacks(cx
, PreserveWrapper
, HasReleasedWrapper
);
137 JS_InitDestroyPrincipalsCallback(cx
, WorkletPrincipals::Destroy
);
138 JS_SetWrapObjectCallbacks(cx
, &WrapObjectCallbacks
);
139 JS_SetFutexCanWait(cx
);
144 void DispatchToMicroTask(
145 already_AddRefed
<MicroTaskRunnable
> aRunnable
) override
{
146 RefPtr
<MicroTaskRunnable
> runnable(aRunnable
);
148 MOZ_ASSERT(!NS_IsMainThread());
149 MOZ_ASSERT(runnable
);
151 JSContext
* cx
= Context();
155 JS::Rooted
<JSObject
*> global(cx
, JS::CurrentGlobalOrNull(cx
));
159 JS::JobQueueMayNotBeEmpty(cx
);
160 GetMicroTaskQueue().push(std::move(runnable
));
163 bool IsSystemCaller() const override
{
164 // Currently no support for special system worklet privileges.
168 void ReportError(JSErrorReport
* aReport
,
169 JS::ConstUTF8CharsZ aToStringResult
) override
;
171 uint64_t GetCurrentWorkletWindowID() {
172 JSObject
* global
= JS::CurrentGlobalOrNull(Context());
173 if (NS_WARN_IF(!global
)) {
176 nsIGlobalObject
* nativeGlobal
= xpc::NativeGlobal(global
);
177 nsCOMPtr
<WorkletGlobalScope
> workletGlobal
=
178 do_QueryInterface(nativeGlobal
);
179 if (NS_WARN_IF(!workletGlobal
)) {
182 return workletGlobal
->Impl()->LoadInfo().InnerWindowID();
186 void WorkletJSContext::ReportError(JSErrorReport
* aReport
,
187 JS::ConstUTF8CharsZ aToStringResult
) {
188 RefPtr
<xpc::ErrorReport
> xpcReport
= new xpc::ErrorReport();
189 xpcReport
->Init(aReport
, aToStringResult
.c_str(), IsSystemCaller(),
190 GetCurrentWorkletWindowID());
191 RefPtr
<AsyncErrorReporter
> reporter
= new AsyncErrorReporter(xpcReport
);
193 JSContext
* cx
= Context();
194 if (JS_IsExceptionPending(cx
)) {
195 JS::ExceptionStack
exnStack(cx
);
196 if (JS::StealPendingExceptionStack(cx
, &exnStack
)) {
197 JS::Rooted
<JSObject
*> stack(cx
);
198 JS::Rooted
<JSObject
*> stackGlobal(cx
);
199 xpc::FindExceptionStackForConsoleReport(nullptr, exnStack
.exception(),
200 exnStack
.stack(), &stack
,
203 reporter
->SerializeStack(cx
, stack
);
208 NS_DispatchToMainThread(reporter
);
211 // This is the first runnable to be dispatched. It calls the RunEventLoop() so
212 // basically everything happens into this runnable. The reason behind this
213 // approach is that, when the Worklet is terminated, it must not have any JS in
214 // stack, but, because we have CC, nsIThread creates an AutoNoJSAPI object by
215 // default. Using this runnable, CC exists only into it.
216 class WorkletThread::PrimaryRunnable final
: public Runnable
{
218 explicit PrimaryRunnable(WorkletThread
* aWorkletThread
)
219 : Runnable("WorkletThread::PrimaryRunnable"),
220 mWorkletThread(aWorkletThread
) {
221 MOZ_ASSERT(aWorkletThread
);
222 MOZ_ASSERT(NS_IsMainThread());
227 mWorkletThread
->RunEventLoop();
232 RefPtr
<WorkletThread
> mWorkletThread
;
235 // This is the last runnable to be dispatched. It calls the TerminateInternal()
236 class WorkletThread::TerminateRunnable final
: public Runnable
{
238 explicit TerminateRunnable(WorkletThread
* aWorkletThread
)
239 : Runnable("WorkletThread::TerminateRunnable"),
240 mWorkletThread(aWorkletThread
) {
241 MOZ_ASSERT(aWorkletThread
);
242 MOZ_ASSERT(NS_IsMainThread());
247 mWorkletThread
->TerminateInternal();
252 RefPtr
<WorkletThread
> mWorkletThread
;
255 WorkletThread::WorkletThread(WorkletImpl
* aWorkletImpl
)
257 MakeNotNull
<ThreadEventQueue
*>(MakeUnique
<mozilla::EventQueue
>()),
258 nsThread::NOT_MAIN_THREAD
, kWorkletStackSize
),
259 mWorkletImpl(aWorkletImpl
),
261 mIsTerminating(false) {
262 MOZ_ASSERT(NS_IsMainThread());
263 nsContentUtils::RegisterShutdownObserver(this);
266 WorkletThread::~WorkletThread() = default;
269 already_AddRefed
<WorkletThread
> WorkletThread::Create(
270 WorkletImpl
* aWorkletImpl
) {
271 RefPtr
<WorkletThread
> thread
= new WorkletThread(aWorkletImpl
);
272 if (NS_WARN_IF(NS_FAILED(thread
->Init("DOM Worklet"_ns
)))) {
276 RefPtr
<PrimaryRunnable
> runnable
= new PrimaryRunnable(thread
);
277 if (NS_WARN_IF(NS_FAILED(thread
->DispatchRunnable(runnable
.forget())))) {
281 return thread
.forget();
284 nsresult
WorkletThread::DispatchRunnable(
285 already_AddRefed
<nsIRunnable
> aRunnable
) {
286 nsCOMPtr
<nsIRunnable
> runnable(aRunnable
);
287 return nsThread::Dispatch(runnable
.forget(), NS_DISPATCH_NORMAL
);
291 WorkletThread::DispatchFromScript(nsIRunnable
* aRunnable
, uint32_t aFlags
) {
292 nsCOMPtr
<nsIRunnable
> runnable(aRunnable
);
293 return Dispatch(runnable
.forget(), aFlags
);
297 WorkletThread::Dispatch(already_AddRefed
<nsIRunnable
> aRunnable
,
299 nsCOMPtr
<nsIRunnable
> runnable(aRunnable
);
301 // Worklet only supports asynchronous dispatch.
302 if (NS_WARN_IF(aFlags
!= NS_DISPATCH_NORMAL
)) {
303 return NS_ERROR_UNEXPECTED
;
306 return nsThread::Dispatch(runnable
.forget(), NS_DISPATCH_NORMAL
);
310 WorkletThread::DelayedDispatch(already_AddRefed
<nsIRunnable
>, uint32_t aFlags
) {
311 return NS_ERROR_NOT_IMPLEMENTED
;
314 static bool DispatchToEventLoop(void* aClosure
,
315 JS::Dispatchable
* aDispatchable
) {
316 // This callback may execute either on the worklet thread or a random
317 // JS-internal helper thread.
319 // See comment at JS::InitDispatchToEventLoop() below for how we know the
320 // WorkletThread is alive.
321 WorkletThread
* workletThread
= reinterpret_cast<WorkletThread
*>(aClosure
);
323 nsresult rv
= workletThread
->DispatchRunnable(NS_NewRunnableFunction(
324 "WorkletThread::DispatchToEventLoop", [aDispatchable
]() {
325 CycleCollectedJSContext
* ccjscx
= CycleCollectedJSContext::Get();
330 WorkletJSContext
* wjc
= ccjscx
->GetAsWorkletJSContext();
335 aDispatchable
->run(wjc
->Context(), JS::Dispatchable::NotShuttingDown
);
338 return NS_SUCCEEDED(rv
);
341 void WorkletThread::EnsureCycleCollectedJSContext(JSRuntime
* aParentRuntime
) {
342 CycleCollectedJSContext
* ccjscx
= CycleCollectedJSContext::Get();
344 MOZ_ASSERT(ccjscx
->GetAsWorkletJSContext());
348 WorkletJSContext
* context
= new WorkletJSContext();
349 nsresult rv
= context
->Initialize(aParentRuntime
);
350 if (NS_WARN_IF(NS_FAILED(rv
))) {
351 // TODO: error propagation
355 JS_SetGCParameter(context
->Context(), JSGC_MAX_BYTES
, uint32_t(-1));
357 // FIXME: JS_SetDefaultLocale
359 // FIXME: JS_SetSecurityCallbacks
360 // FIXME: JS::SetAsyncTaskCallbacks
361 // FIXME: JS::SetCTypesActivityCallback
362 // FIXME: JS_SetGCZeal
364 // A WorkletThread lives strictly longer than its JSRuntime so we can safely
365 // store a raw pointer as the callback's closure argument on the JSRuntime.
366 JS::InitDispatchToEventLoop(context
->Context(), DispatchToEventLoop
,
369 JS_SetNativeStackQuota(context
->Context(),
370 WORKLET_CONTEXT_NATIVE_STACK_LIMIT
);
372 if (!JS::InitSelfHostedCode(context
->Context())) {
373 // TODO: error propagation
378 void WorkletThread::RunEventLoop() {
379 MOZ_ASSERT(!NS_IsMainThread());
381 PR_SetCurrentThreadName("worklet");
384 MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(this, /* wait: */ true));
387 DeleteCycleCollectedJSContext();
390 void WorkletThread::Terminate() {
391 MOZ_ASSERT(NS_IsMainThread());
393 if (mIsTerminating
) {
394 // nsThread::Dispatch() would leak the runnable if the event queue is no
395 // longer accepting runnables.
399 mIsTerminating
= true;
401 nsContentUtils::UnregisterShutdownObserver(this);
403 RefPtr
<TerminateRunnable
> runnable
= new TerminateRunnable(this);
404 DispatchRunnable(runnable
.forget());
407 void WorkletThread::TerminateInternal() {
408 MOZ_ASSERT(!CycleCollectedJSContext::Get() || IsOnWorkletThread());
412 nsCOMPtr
<nsIRunnable
> runnable
= NewRunnableMethod(
413 "WorkletThread::Shutdown", this, &WorkletThread::Shutdown
);
414 NS_DispatchToMainThread(runnable
);
418 void WorkletThread::DeleteCycleCollectedJSContext() {
419 CycleCollectedJSContext
* ccjscx
= CycleCollectedJSContext::Get();
424 // Release any MessagePort kept alive by its ipc actor.
425 mozilla::ipc::BackgroundChild::CloseForCurrentThread();
427 WorkletJSContext
* workletjscx
= ccjscx
->GetAsWorkletJSContext();
428 MOZ_ASSERT(workletjscx
);
433 bool WorkletThread::IsOnWorkletThread() {
434 CycleCollectedJSContext
* ccjscx
= CycleCollectedJSContext::Get();
435 return ccjscx
&& ccjscx
->GetAsWorkletJSContext();
439 void WorkletThread::AssertIsOnWorkletThread() {
440 MOZ_ASSERT(IsOnWorkletThread());
445 WorkletThread::Observe(nsISupports
* aSubject
, const char* aTopic
,
447 MOZ_ASSERT(strcmp(aTopic
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
) == 0);
449 // The WorkletImpl will terminate the worklet thread after sending a message
450 // to release worklet thread objects.
451 mWorkletImpl
->NotifyWorkletFinished();
455 NS_IMPL_ISUPPORTS_INHERITED(WorkletThread
, nsThread
, nsIObserver
)
458 } // namespace mozilla