Bug 1698238 return default dictionary from GetUserMediaRequest#getConstraints() if...
[gecko.git] / dom / worklet / WorkletThread.cpp
blobe59c7c791988334d09b47246bbf267fd62205494
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 "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"
22 namespace mozilla {
23 namespace dom {
25 namespace {
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
31 // consistency.
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
37 // Helper functions
39 bool PreserveWrapper(JSContext* aCx, JS::HandleObject aObj) {
40 MOZ_ASSERT(aCx);
41 MOZ_ASSERT(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) {
48 if (aExisting) {
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 = {
58 Wrap,
59 nullptr,
62 } // namespace
64 // This classes control CC in the worklet thread.
66 class WorkletJSRuntime final : public mozilla::CycleCollectedJSRuntime {
67 public:
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 {
98 public:
99 WorkletJSContext() {
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
107 // bit of a pain.
108 MOZ_CAN_RUN_SCRIPT_BOUNDARY ~WorkletJSContext() override {
109 MOZ_ASSERT(!NS_IsMainThread());
111 JSContext* cx = MaybeContext();
112 if (!cx) {
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))) {
131 return 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);
141 return NS_OK;
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();
152 MOZ_ASSERT(cx);
154 #ifdef DEBUG
155 JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
156 MOZ_ASSERT(global);
157 #endif
159 JS::JobQueueMayNotBeEmpty(cx);
160 GetMicroTaskQueue().push(std::move(runnable));
163 bool IsSystemCaller() const override {
164 // Currently no support for special system worklet privileges.
165 return false;
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)) {
174 return 0;
176 nsIGlobalObject* nativeGlobal = xpc::NativeGlobal(global);
177 nsCOMPtr<WorkletGlobalScope> workletGlobal =
178 do_QueryInterface(nativeGlobal);
179 if (NS_WARN_IF(!workletGlobal)) {
180 return 0;
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,
201 &stackGlobal);
202 if (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 {
217 public:
218 explicit PrimaryRunnable(WorkletThread* aWorkletThread)
219 : Runnable("WorkletThread::PrimaryRunnable"),
220 mWorkletThread(aWorkletThread) {
221 MOZ_ASSERT(aWorkletThread);
222 MOZ_ASSERT(NS_IsMainThread());
225 NS_IMETHOD
226 Run() override {
227 mWorkletThread->RunEventLoop();
228 return NS_OK;
231 private:
232 RefPtr<WorkletThread> mWorkletThread;
235 // This is the last runnable to be dispatched. It calls the TerminateInternal()
236 class WorkletThread::TerminateRunnable final : public Runnable {
237 public:
238 explicit TerminateRunnable(WorkletThread* aWorkletThread)
239 : Runnable("WorkletThread::TerminateRunnable"),
240 mWorkletThread(aWorkletThread) {
241 MOZ_ASSERT(aWorkletThread);
242 MOZ_ASSERT(NS_IsMainThread());
245 NS_IMETHOD
246 Run() override {
247 mWorkletThread->TerminateInternal();
248 return NS_OK;
251 private:
252 RefPtr<WorkletThread> mWorkletThread;
255 WorkletThread::WorkletThread(WorkletImpl* aWorkletImpl)
256 : nsThread(
257 MakeNotNull<ThreadEventQueue*>(MakeUnique<mozilla::EventQueue>()),
258 nsThread::NOT_MAIN_THREAD, kWorkletStackSize),
259 mWorkletImpl(aWorkletImpl),
260 mExitLoop(false),
261 mIsTerminating(false) {
262 MOZ_ASSERT(NS_IsMainThread());
263 nsContentUtils::RegisterShutdownObserver(this);
266 WorkletThread::~WorkletThread() = default;
268 // static
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)))) {
273 return nullptr;
276 RefPtr<PrimaryRunnable> runnable = new PrimaryRunnable(thread);
277 if (NS_WARN_IF(NS_FAILED(thread->DispatchRunnable(runnable.forget())))) {
278 return nullptr;
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);
290 NS_IMETHODIMP
291 WorkletThread::DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags) {
292 nsCOMPtr<nsIRunnable> runnable(aRunnable);
293 return Dispatch(runnable.forget(), aFlags);
296 NS_IMETHODIMP
297 WorkletThread::Dispatch(already_AddRefed<nsIRunnable> aRunnable,
298 uint32_t aFlags) {
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);
309 NS_IMETHODIMP
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();
326 if (!ccjscx) {
327 return;
330 WorkletJSContext* wjc = ccjscx->GetAsWorkletJSContext();
331 if (!wjc) {
332 return;
335 aDispatchable->run(wjc->Context(), JS::Dispatchable::NotShuttingDown);
336 }));
338 return NS_SUCCEEDED(rv);
341 void WorkletThread::EnsureCycleCollectedJSContext(JSRuntime* aParentRuntime) {
342 CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get();
343 if (ccjscx) {
344 MOZ_ASSERT(ccjscx->GetAsWorkletJSContext());
345 return;
348 WorkletJSContext* context = new WorkletJSContext();
349 nsresult rv = context->Initialize(aParentRuntime);
350 if (NS_WARN_IF(NS_FAILED(rv))) {
351 // TODO: error propagation
352 return;
355 JS_SetGCParameter(context->Context(), JSGC_MAX_BYTES, uint32_t(-1));
357 // FIXME: JS_SetDefaultLocale
358 // FIXME: JSSettings
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,
367 (void*)this);
369 JS_SetNativeStackQuota(context->Context(),
370 WORKLET_CONTEXT_NATIVE_STACK_LIMIT);
372 if (!JS::InitSelfHostedCode(context->Context())) {
373 // TODO: error propagation
374 return;
378 void WorkletThread::RunEventLoop() {
379 MOZ_ASSERT(!NS_IsMainThread());
381 PR_SetCurrentThreadName("worklet");
383 while (!mExitLoop) {
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.
396 return;
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());
410 mExitLoop = true;
412 nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod(
413 "WorkletThread::Shutdown", this, &WorkletThread::Shutdown);
414 NS_DispatchToMainThread(runnable);
417 /* static */
418 void WorkletThread::DeleteCycleCollectedJSContext() {
419 CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get();
420 if (!ccjscx) {
421 return;
424 // Release any MessagePort kept alive by its ipc actor.
425 mozilla::ipc::BackgroundChild::CloseForCurrentThread();
427 WorkletJSContext* workletjscx = ccjscx->GetAsWorkletJSContext();
428 MOZ_ASSERT(workletjscx);
429 delete workletjscx;
432 /* static */
433 bool WorkletThread::IsOnWorkletThread() {
434 CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get();
435 return ccjscx && ccjscx->GetAsWorkletJSContext();
438 /* static */
439 void WorkletThread::AssertIsOnWorkletThread() {
440 MOZ_ASSERT(IsOnWorkletThread());
443 // nsIObserver
444 NS_IMETHODIMP
445 WorkletThread::Observe(nsISupports* aSubject, const char* aTopic,
446 const char16_t*) {
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();
452 return NS_OK;
455 NS_IMPL_ISUPPORTS_INHERITED(WorkletThread, nsThread, nsIObserver)
457 } // namespace dom
458 } // namespace mozilla