Bug 1639230: Remove vendor prefix for printing with webdriver r=jgraham,webdriver...
[gecko.git] / dom / worklet / WorkletThread.cpp
blob9511ac4fd390e465eea1ea762a2929b988881510
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"
8 #include "prthread.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"
18 namespace mozilla {
19 namespace dom {
21 namespace {
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
27 // consistency.
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
33 // Helper functions
35 bool PreserveWrapper(JSContext* aCx, JS::HandleObject aObj) {
36 MOZ_ASSERT(aCx);
37 MOZ_ASSERT(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) {
44 if (aExisting) {
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 = {
54 Wrap,
55 nullptr,
58 } // namespace
60 // This classes control CC in the worklet thread.
62 class WorkletJSRuntime final : public mozilla::CycleCollectedJSRuntime {
63 public:
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 {
94 public:
95 WorkletJSContext() {
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
103 // bit of a pain.
104 MOZ_CAN_RUN_SCRIPT_BOUNDARY ~WorkletJSContext() override {
105 MOZ_ASSERT(!NS_IsMainThread());
107 JSContext* cx = MaybeContext();
108 if (!cx) {
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))) {
127 return 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);
137 return NS_OK;
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();
148 MOZ_ASSERT(cx);
150 #ifdef DEBUG
151 JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
152 MOZ_ASSERT(global);
153 #endif
155 JS::JobQueueMayNotBeEmpty(cx);
156 GetMicroTaskQueue().push(std::move(runnable));
159 bool IsSystemCaller() const override {
160 // Currently no support for special system worklet privileges.
161 return false;
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)) {
170 return 0;
172 nsIGlobalObject* nativeGlobal = xpc::NativeGlobal(global);
173 nsCOMPtr<WorkletGlobalScope> workletGlobal =
174 do_QueryInterface(nativeGlobal);
175 if (NS_WARN_IF(!workletGlobal)) {
176 return 0;
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,
197 &stackGlobal);
198 if (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 {
213 public:
214 explicit PrimaryRunnable(WorkletThread* aWorkletThread)
215 : Runnable("WorkletThread::PrimaryRunnable"),
216 mWorkletThread(aWorkletThread) {
217 MOZ_ASSERT(aWorkletThread);
218 MOZ_ASSERT(NS_IsMainThread());
221 NS_IMETHOD
222 Run() override {
223 mWorkletThread->RunEventLoop();
224 return NS_OK;
227 private:
228 RefPtr<WorkletThread> mWorkletThread;
231 // This is the last runnable to be dispatched. It calls the TerminateInternal()
232 class WorkletThread::TerminateRunnable final : public Runnable {
233 public:
234 explicit TerminateRunnable(WorkletThread* aWorkletThread)
235 : Runnable("WorkletThread::TerminateRunnable"),
236 mWorkletThread(aWorkletThread) {
237 MOZ_ASSERT(aWorkletThread);
238 MOZ_ASSERT(NS_IsMainThread());
241 NS_IMETHOD
242 Run() override {
243 mWorkletThread->TerminateInternal();
244 return NS_OK;
247 private:
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),
256 mExitLoop(false),
257 mIsTerminating(false) {
258 MOZ_ASSERT(NS_IsMainThread());
259 nsContentUtils::RegisterShutdownObserver(this);
262 WorkletThread::~WorkletThread() = default;
264 // static
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"))))) {
269 return nullptr;
272 RefPtr<PrimaryRunnable> runnable = new PrimaryRunnable(thread);
273 if (NS_WARN_IF(NS_FAILED(thread->DispatchRunnable(runnable.forget())))) {
274 return nullptr;
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);
286 NS_IMETHODIMP
287 WorkletThread::DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags) {
288 nsCOMPtr<nsIRunnable> runnable(aRunnable);
289 return Dispatch(runnable.forget(), aFlags);
292 NS_IMETHODIMP
293 WorkletThread::Dispatch(already_AddRefed<nsIRunnable> aRunnable,
294 uint32_t aFlags) {
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);
305 NS_IMETHODIMP
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();
322 if (!ccjscx) {
323 return;
326 WorkletJSContext* wjc = ccjscx->GetAsWorkletJSContext();
327 if (!wjc) {
328 return;
331 aDispatchable->run(wjc->Context(), JS::Dispatchable::NotShuttingDown);
332 }));
334 return NS_SUCCEEDED(rv);
337 void WorkletThread::EnsureCycleCollectedJSContext(JSRuntime* aParentRuntime) {
338 CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get();
339 if (ccjscx) {
340 MOZ_ASSERT(ccjscx->GetAsWorkletJSContext());
341 return;
344 WorkletJSContext* context = new WorkletJSContext();
345 nsresult rv = context->Initialize(aParentRuntime);
346 if (NS_WARN_IF(NS_FAILED(rv))) {
347 // TODO: error propagation
348 return;
351 JS_SetGCParameter(context->Context(), JSGC_MAX_BYTES, uint32_t(-1));
353 // FIXME: JS_SetDefaultLocale
354 // FIXME: JSSettings
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,
363 (void*)this);
365 JS_SetNativeStackQuota(context->Context(),
366 WORKLET_CONTEXT_NATIVE_STACK_LIMIT);
368 if (!JS::InitSelfHostedCode(context->Context())) {
369 // TODO: error propagation
370 return;
374 void WorkletThread::RunEventLoop() {
375 MOZ_ASSERT(!NS_IsMainThread());
377 PR_SetCurrentThreadName("worklet");
379 while (!mExitLoop) {
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.
392 return;
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());
406 mExitLoop = true;
408 nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod(
409 "WorkletThread::Shutdown", this, &WorkletThread::Shutdown);
410 NS_DispatchToMainThread(runnable);
413 /* static */
414 void WorkletThread::DeleteCycleCollectedJSContext() {
415 CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get();
416 if (!ccjscx) {
417 return;
420 // Release any MessagePort kept alive by its ipc actor.
421 mozilla::ipc::BackgroundChild::CloseForCurrentThread();
423 WorkletJSContext* workletjscx = ccjscx->GetAsWorkletJSContext();
424 MOZ_ASSERT(workletjscx);
425 delete workletjscx;
428 /* static */
429 bool WorkletThread::IsOnWorkletThread() {
430 CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get();
431 return ccjscx && ccjscx->GetAsWorkletJSContext();
434 /* static */
435 void WorkletThread::AssertIsOnWorkletThread() {
436 MOZ_ASSERT(IsOnWorkletThread());
439 // nsIObserver
440 NS_IMETHODIMP
441 WorkletThread::Observe(nsISupports* aSubject, const char* aTopic,
442 const char16_t*) {
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();
448 return NS_OK;
451 NS_IMPL_ISUPPORTS_INHERITED(WorkletThread, nsThread, nsIObserver)
453 } // namespace dom
454 } // namespace mozilla