mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-10-31 08:18:54 +02:00 
			
		
		
		
	 5617e628be
			
		
	
	
		5617e628be
		
	
	
	
	
		
			
			[Atomics.waitAsync](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/waitAsync) creates a promise that can be resolved via Atomics.notify, or after an optional timeout. The timeout is [implemented](https://html.spec.whatwg.org/multipage/webappapis.html#hostenqueuetimeoutjob) using the [HostEnqueueTimeoutJob hook](https://html.spec.whatwg.org/multipage/webappapis.html#hostenqueuetimeoutjob), which queues a global task (not a microtask). This patch adds a new API (DelayedDispatchToEventLoopCallback) accessible by the JS engine that utilized the TimeoutManager for executing the steps outlined in the HostEnqueTimeoutJob. Unlike DispatchToEventCallback, this is currently restricted to only threads which support TimeoutManager (the Main thread and the Workers Thread). This should not be enabled on worklets per the discussion [here](https://phabricator.services.mozilla.com/D212876#inline-1206374). The next patch adds an implementation in worker threads. A later patch in the stack adds an implementation for the shell. Depends on D212875 Differential Revision: https://phabricator.services.mozilla.com/D212876
		
			
				
	
	
		
			490 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			490 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "WorkletThread.h"
 | |
| #include "prthread.h"
 | |
| #include "nsContentUtils.h"
 | |
| #include "nsCycleCollector.h"
 | |
| #include "nsJSEnvironment.h"
 | |
| #include "nsJSPrincipals.h"
 | |
| #include "mozilla/dom/AtomList.h"
 | |
| #include "mozilla/dom/WorkletGlobalScope.h"
 | |
| #include "mozilla/ipc/BackgroundChild.h"
 | |
| #include "mozilla/Attributes.h"
 | |
| #include "mozilla/CycleCollectedJSRuntime.h"
 | |
| #include "mozilla/EventQueue.h"
 | |
| #include "mozilla/ThreadEventQueue.h"
 | |
| #include "js/ContextOptions.h"
 | |
| #include "js/Exception.h"
 | |
| #include "js/Initialization.h"
 | |
| #include "XPCSelfHostedShmem.h"
 | |
| 
 | |
| namespace mozilla::dom {
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| // The size of the worklet runtime heaps in bytes.
 | |
| #define WORKLET_DEFAULT_RUNTIME_HEAPSIZE 32 * 1024 * 1024
 | |
| 
 | |
| // The C stack size. We use the same stack size on all platforms for
 | |
| // consistency.
 | |
| const uint32_t kWorkletStackSize = 256 * sizeof(size_t) * 1024;
 | |
| 
 | |
| // Half the size of the actual C stack, to be safe.
 | |
| #define WORKLET_CONTEXT_NATIVE_STACK_LIMIT 128 * sizeof(size_t) * 1024
 | |
| 
 | |
| // Helper functions
 | |
| 
 | |
| bool PreserveWrapper(JSContext* aCx, JS::Handle<JSObject*> aObj) {
 | |
|   MOZ_ASSERT(aCx);
 | |
|   MOZ_ASSERT(aObj);
 | |
|   MOZ_ASSERT(mozilla::dom::IsDOMObject(aObj));
 | |
|   return mozilla::dom::TryPreserveWrapper(aObj);
 | |
| }
 | |
| 
 | |
| JSObject* Wrap(JSContext* aCx, JS::Handle<JSObject*> aExisting,
 | |
|                JS::Handle<JSObject*> aObj) {
 | |
|   if (aExisting) {
 | |
|     js::Wrapper::Renew(aExisting, aObj,
 | |
|                        &js::OpaqueCrossCompartmentWrapper::singleton);
 | |
|   }
 | |
| 
 | |
|   return js::Wrapper::New(aCx, aObj,
 | |
|                           &js::OpaqueCrossCompartmentWrapper::singleton);
 | |
| }
 | |
| 
 | |
| const JSWrapObjectCallbacks WrapObjectCallbacks = {
 | |
|     Wrap,
 | |
|     nullptr,
 | |
| };
 | |
| 
 | |
| }  // namespace
 | |
| 
 | |
| // This classes control CC in the worklet thread.
 | |
| 
 | |
| class WorkletJSRuntime final : public mozilla::CycleCollectedJSRuntime {
 | |
|  public:
 | |
|   explicit WorkletJSRuntime(JSContext* aCx) : CycleCollectedJSRuntime(aCx) {}
 | |
| 
 | |
|   ~WorkletJSRuntime() override = default;
 | |
| 
 | |
|   virtual void PrepareForForgetSkippable() override {}
 | |
| 
 | |
|   virtual void BeginCycleCollectionCallback(
 | |
|       mozilla::CCReason aReason) override {}
 | |
| 
 | |
|   virtual void EndCycleCollectionCallback(
 | |
|       CycleCollectorResults& aResults) override {}
 | |
| 
 | |
|   virtual void DispatchDeferredDeletion(bool aContinuation,
 | |
|                                         bool aPurge) override {
 | |
|     MOZ_ASSERT(!aContinuation);
 | |
|     nsCycleCollector_doDeferredDeletion();
 | |
|   }
 | |
| 
 | |
|   virtual void CustomGCCallback(JSGCStatus aStatus) override {
 | |
|     // nsCycleCollector_collect() requires a cycle collector but
 | |
|     // ~WorkletJSContext calls nsCycleCollector_shutdown() and the base class
 | |
|     // destructor will trigger a final GC.  The nsCycleCollector_collect()
 | |
|     // call can be skipped in this GC as ~CycleCollectedJSContext removes the
 | |
|     // context from |this|.
 | |
|     if (aStatus == JSGC_END && GetContext()) {
 | |
|       nsCycleCollector_collect(CCReason::GC_FINISHED, nullptr);
 | |
|     }
 | |
|   }
 | |
| };
 | |
| 
 | |
| class WorkletJSContext final : public CycleCollectedJSContext {
 | |
|  public:
 | |
|   WorkletJSContext() {
 | |
|     MOZ_ASSERT(!NS_IsMainThread());
 | |
| 
 | |
|     nsCycleCollector_startup();
 | |
|   }
 | |
| 
 | |
|   // MOZ_CAN_RUN_SCRIPT_BOUNDARY because otherwise we have to annotate the
 | |
|   // SpiderMonkey JS::JobQueue's destructor as MOZ_CAN_RUN_SCRIPT, which is a
 | |
|   // bit of a pain.
 | |
|   MOZ_CAN_RUN_SCRIPT_BOUNDARY ~WorkletJSContext() override {
 | |
|     MOZ_ASSERT(!NS_IsMainThread());
 | |
| 
 | |
|     JSContext* cx = MaybeContext();
 | |
|     if (!cx) {
 | |
|       return;  // Initialize() must have failed
 | |
|     }
 | |
| 
 | |
|     nsCycleCollector_shutdown();
 | |
|   }
 | |
| 
 | |
|   WorkletJSContext* GetAsWorkletJSContext() override { return this; }
 | |
| 
 | |
|   CycleCollectedJSRuntime* CreateRuntime(JSContext* aCx) override {
 | |
|     return new WorkletJSRuntime(aCx);
 | |
|   }
 | |
| 
 | |
|   nsresult Initialize(JSRuntime* aParentRuntime) {
 | |
|     MOZ_ASSERT(!NS_IsMainThread());
 | |
| 
 | |
|     nsresult rv = CycleCollectedJSContext::Initialize(
 | |
|         aParentRuntime, WORKLET_DEFAULT_RUNTIME_HEAPSIZE);
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       return rv;
 | |
|     }
 | |
| 
 | |
|     JSContext* cx = Context();
 | |
| 
 | |
|     js::SetPreserveWrapperCallbacks(cx, PreserveWrapper, HasReleasedWrapper);
 | |
|     JS_InitDestroyPrincipalsCallback(cx, nsJSPrincipals::Destroy);
 | |
|     JS_InitReadPrincipalsCallback(cx, nsJSPrincipals::ReadPrincipals);
 | |
|     JS_SetWrapObjectCallbacks(cx, &WrapObjectCallbacks);
 | |
|     JS_SetFutexCanWait(cx);
 | |
| 
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   void DispatchToMicroTask(
 | |
|       already_AddRefed<MicroTaskRunnable> aRunnable) override {
 | |
|     RefPtr<MicroTaskRunnable> runnable(aRunnable);
 | |
| 
 | |
|     MOZ_ASSERT(!NS_IsMainThread());
 | |
|     MOZ_ASSERT(runnable);
 | |
| 
 | |
|     JSContext* cx = Context();
 | |
|     MOZ_ASSERT(cx);
 | |
| 
 | |
| #ifdef DEBUG
 | |
|     JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
 | |
|     MOZ_ASSERT(global);
 | |
| #endif
 | |
| 
 | |
|     JS::JobQueueMayNotBeEmpty(cx);
 | |
|     GetMicroTaskQueue().push_back(std::move(runnable));
 | |
|   }
 | |
| 
 | |
|   bool IsSystemCaller() const override {
 | |
|     // Currently no support for special system worklet privileges.
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   void ReportError(JSErrorReport* aReport,
 | |
|                    JS::ConstUTF8CharsZ aToStringResult) override;
 | |
| 
 | |
|   uint64_t GetCurrentWorkletWindowID() {
 | |
|     JSObject* global = JS::CurrentGlobalOrNull(Context());
 | |
|     if (NS_WARN_IF(!global)) {
 | |
|       return 0;
 | |
|     }
 | |
|     nsIGlobalObject* nativeGlobal = xpc::NativeGlobal(global);
 | |
|     nsCOMPtr<WorkletGlobalScope> workletGlobal =
 | |
|         do_QueryInterface(nativeGlobal);
 | |
|     if (NS_WARN_IF(!workletGlobal)) {
 | |
|       return 0;
 | |
|     }
 | |
|     return workletGlobal->Impl()->LoadInfo().InnerWindowID();
 | |
|   }
 | |
| };
 | |
| 
 | |
| void WorkletJSContext::ReportError(JSErrorReport* aReport,
 | |
|                                    JS::ConstUTF8CharsZ aToStringResult) {
 | |
|   RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
 | |
|   xpcReport->Init(aReport, aToStringResult.c_str(), IsSystemCaller(),
 | |
|                   GetCurrentWorkletWindowID());
 | |
|   RefPtr<AsyncErrorReporter> reporter = new AsyncErrorReporter(xpcReport);
 | |
| 
 | |
|   JSContext* cx = Context();
 | |
|   if (JS_IsExceptionPending(cx)) {
 | |
|     JS::ExceptionStack exnStack(cx);
 | |
|     if (JS::StealPendingExceptionStack(cx, &exnStack)) {
 | |
|       JS::Rooted<JSObject*> stack(cx);
 | |
|       JS::Rooted<JSObject*> stackGlobal(cx);
 | |
|       xpc::FindExceptionStackForConsoleReport(nullptr, exnStack.exception(),
 | |
|                                               exnStack.stack(), &stack,
 | |
|                                               &stackGlobal);
 | |
|       if (stack) {
 | |
|         reporter->SerializeStack(cx, stack);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   NS_DispatchToMainThread(reporter);
 | |
| }
 | |
| 
 | |
| // This is the first runnable to be dispatched. It calls the RunEventLoop() so
 | |
| // basically everything happens into this runnable. The reason behind this
 | |
| // approach is that, when the Worklet is terminated, it must not have any JS in
 | |
| // stack, but, because we have CC, nsIThread creates an AutoNoJSAPI object by
 | |
| // default. Using this runnable, CC exists only into it.
 | |
| class WorkletThread::PrimaryRunnable final : public Runnable {
 | |
|  public:
 | |
|   explicit PrimaryRunnable(WorkletThread* aWorkletThread)
 | |
|       : Runnable("WorkletThread::PrimaryRunnable"),
 | |
|         mWorkletThread(aWorkletThread) {
 | |
|     MOZ_ASSERT(aWorkletThread);
 | |
|     MOZ_ASSERT(NS_IsMainThread());
 | |
|   }
 | |
| 
 | |
|   NS_IMETHOD
 | |
|   Run() override {
 | |
|     mWorkletThread->RunEventLoop();
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   RefPtr<WorkletThread> mWorkletThread;
 | |
| };
 | |
| 
 | |
| // This is the last runnable to be dispatched. It calls the TerminateInternal()
 | |
| class WorkletThread::TerminateRunnable final : public Runnable {
 | |
|  public:
 | |
|   explicit TerminateRunnable(WorkletThread* aWorkletThread)
 | |
|       : Runnable("WorkletThread::TerminateRunnable"),
 | |
|         mWorkletThread(aWorkletThread) {
 | |
|     MOZ_ASSERT(aWorkletThread);
 | |
|     MOZ_ASSERT(NS_IsMainThread());
 | |
|   }
 | |
| 
 | |
|   NS_IMETHOD
 | |
|   Run() override {
 | |
|     mWorkletThread->TerminateInternal();
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   RefPtr<WorkletThread> mWorkletThread;
 | |
| };
 | |
| 
 | |
| WorkletThread::WorkletThread(WorkletImpl* aWorkletImpl)
 | |
|     : nsThread(
 | |
|           MakeNotNull<ThreadEventQueue*>(MakeUnique<mozilla::EventQueue>()),
 | |
|           nsThread::NOT_MAIN_THREAD, {.stackSize = kWorkletStackSize}),
 | |
|       mWorkletImpl(aWorkletImpl),
 | |
|       mExitLoop(false),
 | |
|       mIsTerminating(false) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   nsContentUtils::RegisterShutdownObserver(this);
 | |
| }
 | |
| 
 | |
| WorkletThread::~WorkletThread() = default;
 | |
| 
 | |
| // static
 | |
| already_AddRefed<WorkletThread> WorkletThread::Create(
 | |
|     WorkletImpl* aWorkletImpl) {
 | |
|   RefPtr<WorkletThread> thread = new WorkletThread(aWorkletImpl);
 | |
|   if (NS_WARN_IF(NS_FAILED(thread->Init("DOM Worklet"_ns)))) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   RefPtr<PrimaryRunnable> runnable = new PrimaryRunnable(thread);
 | |
|   if (NS_WARN_IF(NS_FAILED(thread->DispatchRunnable(runnable.forget())))) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return thread.forget();
 | |
| }
 | |
| 
 | |
| nsresult WorkletThread::DispatchRunnable(
 | |
|     already_AddRefed<nsIRunnable> aRunnable) {
 | |
|   nsCOMPtr<nsIRunnable> runnable(aRunnable);
 | |
|   return nsThread::Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| WorkletThread::DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags) {
 | |
|   nsCOMPtr<nsIRunnable> runnable(aRunnable);
 | |
|   return Dispatch(runnable.forget(), aFlags);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| WorkletThread::Dispatch(already_AddRefed<nsIRunnable> aRunnable,
 | |
|                         uint32_t aFlags) {
 | |
|   nsCOMPtr<nsIRunnable> runnable(aRunnable);
 | |
| 
 | |
|   // Worklet only supports asynchronous dispatch.
 | |
|   if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL)) {
 | |
|     return NS_ERROR_UNEXPECTED;
 | |
|   }
 | |
| 
 | |
|   return nsThread::Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| WorkletThread::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t aFlags) {
 | |
|   return NS_ERROR_NOT_IMPLEMENTED;
 | |
| }
 | |
| 
 | |
| static bool DispatchToEventLoop(
 | |
|     void* aClosure, js::UniquePtr<JS::Dispatchable>&& aDispatchable) {
 | |
|   // This callback may execute either on the worklet thread or a random
 | |
|   // JS-internal helper thread.
 | |
| 
 | |
|   // See comment at JS::InitDispatchToEventLoop() below for how we know the
 | |
|   // thread is alive.
 | |
|   nsIThread* thread = static_cast<nsIThread*>(aClosure);
 | |
| 
 | |
|   nsresult rv = thread->Dispatch(
 | |
|       NS_NewRunnableFunction(
 | |
|           "WorkletThread::DispatchToEventLoop",
 | |
|           [dispatchable = std::move(aDispatchable)]() mutable {
 | |
|             CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get();
 | |
|             if (!ccjscx) {
 | |
|               JS::Dispatchable::ReleaseFailedTask(std::move(dispatchable));
 | |
|               return;
 | |
|             }
 | |
| 
 | |
|             WorkletJSContext* wjc = ccjscx->GetAsWorkletJSContext();
 | |
|             if (!wjc) {
 | |
|               JS::Dispatchable::ReleaseFailedTask(std::move(dispatchable));
 | |
|               return;
 | |
|             }
 | |
| 
 | |
|             AutoJSAPI jsapi;
 | |
|             jsapi.Init();
 | |
|             JS::Dispatchable::Run(wjc->Context(), std::move(dispatchable),
 | |
|                                   JS::Dispatchable::NotShuttingDown);
 | |
|           }),
 | |
|       NS_DISPATCH_NORMAL);
 | |
| 
 | |
|   return NS_SUCCEEDED(rv);
 | |
| }
 | |
| 
 | |
| static bool DelayedDispatchToEventLoop(
 | |
|     void* aClosure, js::UniquePtr<JS::Dispatchable>&& aDispatchable,
 | |
|     uint32_t delay) {
 | |
|   // Worklets do not support delayed dispatch. If something is trying to use it,
 | |
|   // it should fail. For now we are warning.
 | |
|   NS_WARNING("Trying to perform a delayed dispatch on a worklet.");
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| // static
 | |
| void WorkletThread::EnsureCycleCollectedJSContext(
 | |
|     JSRuntime* aParentRuntime, const JS::ContextOptions& aOptions) {
 | |
|   CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get();
 | |
|   if (ccjscx) {
 | |
|     MOZ_ASSERT(ccjscx->GetAsWorkletJSContext());
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   WorkletJSContext* context = new WorkletJSContext();
 | |
|   nsresult rv = context->Initialize(aParentRuntime);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     // TODO: error propagation
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   JS::ContextOptionsRef(context->Context()) = aOptions;
 | |
| 
 | |
|   JS_SetGCParameter(context->Context(), JSGC_MAX_BYTES, uint32_t(-1));
 | |
| 
 | |
|   // FIXME: JS_SetDefaultLocale
 | |
|   // FIXME: JSSettings
 | |
|   // FIXME: JS_SetSecurityCallbacks
 | |
|   // FIXME: JS::SetAsyncTaskCallbacks
 | |
|   // FIXME: JS::SetCTypesActivityCallback
 | |
|   // FIXME: JS::SetGCZeal
 | |
| 
 | |
|   // A thread lives strictly longer than its JSRuntime so we can safely
 | |
|   // store a raw pointer as the callback's closure argument on the JSRuntime.
 | |
|   JS::InitDispatchsToEventLoop(context->Context(), DispatchToEventLoop,
 | |
|                                DelayedDispatchToEventLoop,
 | |
|                                NS_GetCurrentThread());
 | |
| 
 | |
|   JS_SetNativeStackQuota(context->Context(),
 | |
|                          WORKLET_CONTEXT_NATIVE_STACK_LIMIT);
 | |
| 
 | |
|   // When available, set the self-hosted shared memory to be read, so that we
 | |
|   // can decode the self-hosted content instead of parsing it.
 | |
|   auto& shm = xpc::SelfHostedShmem::GetSingleton();
 | |
|   JS::SelfHostedCache selfHostedContent = shm.Content();
 | |
| 
 | |
|   if (!JS::InitSelfHostedCode(context->Context(), selfHostedContent)) {
 | |
|     // TODO: error propagation
 | |
|     return;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void WorkletThread::RunEventLoop() {
 | |
|   MOZ_ASSERT(!NS_IsMainThread());
 | |
| 
 | |
|   PR_SetCurrentThreadName("worklet");
 | |
| 
 | |
|   while (!mExitLoop) {
 | |
|     MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(this, /* wait: */ true));
 | |
|   }
 | |
| 
 | |
|   DeleteCycleCollectedJSContext();
 | |
| }
 | |
| 
 | |
| void WorkletThread::Terminate() {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   if (mIsTerminating) {
 | |
|     // nsThread::Dispatch() would leak the runnable if the event queue is no
 | |
|     // longer accepting runnables.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mIsTerminating = true;
 | |
| 
 | |
|   nsContentUtils::UnregisterShutdownObserver(this);
 | |
| 
 | |
|   RefPtr<TerminateRunnable> runnable = new TerminateRunnable(this);
 | |
|   DispatchRunnable(runnable.forget());
 | |
| }
 | |
| 
 | |
| uint32_t WorkletThread::StackSize() { return kWorkletStackSize; }
 | |
| 
 | |
| void WorkletThread::TerminateInternal() {
 | |
|   MOZ_ASSERT(!CycleCollectedJSContext::Get() || IsOnWorkletThread());
 | |
| 
 | |
|   mExitLoop = true;
 | |
| 
 | |
|   nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod(
 | |
|       "WorkletThread::Shutdown", this, &WorkletThread::Shutdown);
 | |
|   NS_DispatchToMainThread(runnable);
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void WorkletThread::DeleteCycleCollectedJSContext() {
 | |
|   CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get();
 | |
|   if (!ccjscx) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Release any MessagePort kept alive by its ipc actor.
 | |
|   mozilla::ipc::BackgroundChild::CloseForCurrentThread();
 | |
| 
 | |
|   WorkletJSContext* workletjscx = ccjscx->GetAsWorkletJSContext();
 | |
|   MOZ_ASSERT(workletjscx);
 | |
|   delete workletjscx;
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| bool WorkletThread::IsOnWorkletThread() {
 | |
|   CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get();
 | |
|   return ccjscx && ccjscx->GetAsWorkletJSContext();
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void WorkletThread::AssertIsOnWorkletThread() {
 | |
|   MOZ_ASSERT(IsOnWorkletThread());
 | |
| }
 | |
| 
 | |
| // nsIObserver
 | |
| NS_IMETHODIMP
 | |
| WorkletThread::Observe(nsISupports* aSubject, const char* aTopic,
 | |
|                        const char16_t*) {
 | |
|   MOZ_ASSERT(strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0);
 | |
| 
 | |
|   // The WorkletImpl will terminate the worklet thread after sending a message
 | |
|   // to release worklet thread objects.
 | |
|   mWorkletImpl->NotifyWorkletFinished();
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMPL_ISUPPORTS_INHERITED(WorkletThread, nsThread, nsIObserver)
 | |
| 
 | |
| }  // namespace mozilla::dom
 |