Bug 1328964 - part 2 - WorkletThread r=smaug draft
authorAndrea Marchesini <amarchesini@mozilla.com>
Thu, 12 Apr 2018 15:14:48 +1200
changeset 780857 2d356616204dc437ed28d5b46a80c0842ec31ec8
parent 780856 2df845458d030ad8700d15ca517ca269117e4cf4
child 780858 cec368f75bc4b6ffd05e6f1f1b9b79a2b85ed09f
push id106145
push userktomlinson@mozilla.com
push dateThu, 12 Apr 2018 05:09:40 +0000
reviewerssmaug
bugs1328964
milestone61.0a1
Bug 1328964 - part 2 - WorkletThread r=smaug Initial version r=smaug. Rebased to c616a6fd5e4b by Jan-Ivar Bruaroey <jib@mozilla.com> r=karlt. Rebased to 83de58ddda20 by Karl Tomlinson <karlt+@karlt.net> r=baku. MozReview-Commit-ID: Lo8TWtN8qyz
caps/nsJSPrincipals.cpp
dom/base/nsContentUtils.cpp
dom/console/Console.cpp
dom/worklet/AudioWorkletGlobalScope.cpp
dom/worklet/AudioWorkletGlobalScope.h
dom/worklet/PaintWorkletGlobalScope.cpp
dom/worklet/PaintWorkletGlobalScope.h
dom/worklet/Worklet.cpp
dom/worklet/Worklet.h
dom/worklet/WorkletGlobalScope.h
dom/worklet/WorkletPrincipal.cpp
dom/worklet/WorkletPrincipal.h
dom/worklet/WorkletThread.cpp
dom/worklet/WorkletThread.h
dom/worklet/moz.build
--- a/caps/nsJSPrincipals.cpp
+++ b/caps/nsJSPrincipals.cpp
@@ -12,16 +12,18 @@
 #include "nsCOMPtr.h"
 #include "nsIServiceManager.h"
 #include "nsMemory.h"
 #include "nsStringBuffer.h"
 
 #include "mozilla/dom/StructuredCloneTags.h"
 // for mozilla::dom::workerinternals::kJSPrincipalsDebugToken
 #include "mozilla/dom/workerinternals/JSSettings.h"
+// for mozilla::dom::worklet::kJSPrincipalsDebugToken
+#include "mozilla/dom/WorkletPrincipal.h"
 #include "mozilla/ipc/BackgroundUtils.h"
 
 using namespace mozilla;
 using namespace mozilla::ipc;
 
 NS_IMETHODIMP_(MozExternalRefCountType)
 nsJSPrincipals::AddRef()
 {
@@ -87,16 +89,18 @@ JSPrincipals::dump()
 {
     if (debugToken == nsJSPrincipals::DEBUG_TOKEN) {
       nsAutoCString str;
       nsresult rv = static_cast<nsJSPrincipals *>(this)->GetScriptLocation(str);
       fprintf(stderr, "nsIPrincipal (%p) = %s\n", static_cast<void*>(this),
               NS_SUCCEEDED(rv) ? str.get() : "(unknown)");
     } else if (debugToken == dom::workerinternals::kJSPrincipalsDebugToken) {
         fprintf(stderr, "Web Worker principal singleton (%p)\n", this);
+    } else if (debugToken == mozilla::dom::WorkletPrincipal::kJSPrincipalsDebugToken) {
+        fprintf(stderr, "Web Worklet principal singleton (%p)\n", this);
     } else {
         fprintf(stderr,
                 "!!! JSPrincipals (%p) is not nsJSPrincipals instance - bad token: "
                 "actual=0x%x expected=0x%x\n",
                 this, unsigned(debugToken), unsigned(nsJSPrincipals::DEBUG_TOKEN));
     }
 }
 
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -59,16 +59,17 @@
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/TabParent.h"
 #include "mozilla/dom/Text.h"
 #include "mozilla/dom/TouchEvent.h"
 #include "mozilla/dom/ShadowRoot.h"
 #include "mozilla/dom/XULCommandEvent.h"
 #include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkletThread.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/gfx/DataSurfaceHelpers.h"
 #include "mozilla/HTMLEditor.h"
 #include "mozilla/IMEStateManager.h"
 #include "mozilla/InternalMutationEvent.h"
 #include "mozilla/Likely.h"
@@ -6073,16 +6074,21 @@ nsContentUtils::GetCurrentJSContext()
 /* static */
 JSContext *
 nsContentUtils::GetCurrentJSContextForThread()
 {
   MOZ_ASSERT(IsInitialized());
   if (MOZ_LIKELY(NS_IsMainThread())) {
     return GetCurrentJSContext();
   }
+
+  if (WorkletThread::IsOnWorkletThread()) {
+    return WorkletThread::Get()->GetJSContext();
+  }
+
   return GetCurrentWorkerThreadJSContext();
 }
 
 template<typename StringType, typename CharType>
 void
 _ASCIIToLowerInSitu(StringType& aStr)
 {
   CharType* iter = aStr.BeginWriting();
--- a/dom/console/Console.cpp
+++ b/dom/console/Console.cpp
@@ -17,16 +17,17 @@
 #include "mozilla/dom/Performance.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/StructuredCloneHolder.h"
 #include "mozilla/dom/ToJSValue.h"
 #include "mozilla/dom/WorkerPrivate.h"
 #include "mozilla/dom/WorkerRunnable.h"
 #include "mozilla/dom/WorkerScope.h"
 #include "mozilla/dom/WorkletGlobalScope.h"
+#include "mozilla/dom/WorkletThread.h"
 #include "mozilla/Maybe.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsDocument.h"
 #include "nsDOMNavigationTiming.h"
 #include "nsGlobalWindow.h"
 #include "nsJSUtils.h"
 #include "nsNetUtil.h"
 #include "xpcpublic.h"
@@ -2416,25 +2417,16 @@ Console::GetConsole(const GlobalObject& 
   }
 
   return console.forget();
 }
 
 /* static */ already_AddRefed<Console>
 Console::GetConsoleInternal(const GlobalObject& aGlobal, ErrorResult& aRv)
 {
-  // Worklet
-  if (NS_IsMainThread()) {
-    nsCOMPtr<WorkletGlobalScope> workletScope =
-      do_QueryInterface(aGlobal.GetAsSupports());
-    if (workletScope) {
-      return workletScope->GetConsole(aGlobal.Context(), aRv);
-    }
-  }
-
   // Window
   if (NS_IsMainThread()) {
     nsCOMPtr<nsPIDOMWindowInner> innerWindow =
       do_QueryInterface(aGlobal.GetAsSupports());
 
     // we are probably running a chrome script.
     if (!innerWindow) {
       RefPtr<Console> console = new Console(aGlobal.Context(), nullptr);
@@ -2445,16 +2437,24 @@ Console::GetConsoleInternal(const Global
 
       return console.forget();
     }
 
     nsGlobalWindowInner* window = nsGlobalWindowInner::Cast(innerWindow);
     return window->GetConsole(aGlobal.Context(), aRv);
   }
 
+  // Worklet
+  nsCOMPtr<WorkletGlobalScope> workletScope =
+    do_QueryInterface(aGlobal.GetAsSupports());
+  if (workletScope) {
+    WorkletThread::AssertIsOnWorkletThread();
+    return workletScope->GetConsole(aGlobal.Context(), aRv);
+  }
+
   // Workers
   MOZ_ASSERT(!NS_IsMainThread());
 
   JSContext* cx = aGlobal.Context();
   WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
   MOZ_ASSERT(workerPrivate);
 
   nsCOMPtr<nsIGlobalObject> global =
--- a/dom/worklet/AudioWorkletGlobalScope.cpp
+++ b/dom/worklet/AudioWorkletGlobalScope.cpp
@@ -1,34 +1,34 @@
 /* -*- 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 "AudioWorkletGlobalScope.h"
+#include "WorkletPrincipal.h"
 #include "mozilla/dom/AudioWorkletGlobalScopeBinding.h"
 #include "mozilla/dom/FunctionBinding.h"
 
 namespace mozilla {
 namespace dom {
 
 AudioWorkletGlobalScope::AudioWorkletGlobalScope()
 {
 }
 
 bool
 AudioWorkletGlobalScope::WrapGlobalObject(JSContext* aCx,
-                                          nsIPrincipal* aPrincipal,
                                           JS::MutableHandle<JSObject*> aReflector)
 {
   JS::CompartmentOptions options;
   return AudioWorkletGlobalScopeBinding::Wrap(aCx, this, this,
                                               options,
-                                              nsJSPrincipals::get(aPrincipal),
+                                              WorkletPrincipal::GetWorkletPrincipal(),
                                               true, aReflector);
 }
 
 void
 AudioWorkletGlobalScope::RegisterProcessor(const nsAString& aType,
                                            VoidFunction& aProcessorCtor)
 {
   // Nothing to do here.
--- a/dom/worklet/AudioWorkletGlobalScope.h
+++ b/dom/worklet/AudioWorkletGlobalScope.h
@@ -15,17 +15,17 @@ namespace dom {
 class VoidFunction;
 
 class AudioWorkletGlobalScope final : public WorkletGlobalScope
 {
 public:
   AudioWorkletGlobalScope();
 
   bool
-  WrapGlobalObject(JSContext* aCx, nsIPrincipal* aPrincipal,
+  WrapGlobalObject(JSContext* aCx,
                    JS::MutableHandle<JSObject*> aReflector) override;
 
   void
   RegisterProcessor(const nsAString& aType,
                     VoidFunction& aProcessorCtor);
 
 private:
   ~AudioWorkletGlobalScope() = default;
--- a/dom/worklet/PaintWorkletGlobalScope.cpp
+++ b/dom/worklet/PaintWorkletGlobalScope.cpp
@@ -1,34 +1,34 @@
 /* -*- 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 "PaintWorkletGlobalScope.h"
+#include "WorkletPrincipal.h"
 #include "mozilla/dom/PaintWorkletGlobalScopeBinding.h"
 #include "mozilla/dom/FunctionBinding.h"
 
 namespace mozilla {
 namespace dom {
 
 PaintWorkletGlobalScope::PaintWorkletGlobalScope()
 {
 }
 
 bool
 PaintWorkletGlobalScope::WrapGlobalObject(JSContext* aCx,
-                                          nsIPrincipal* aPrincipal,
                                           JS::MutableHandle<JSObject*> aReflector)
 {
   JS::CompartmentOptions options;
   return PaintWorkletGlobalScopeBinding::Wrap(aCx, this, this,
                                               options,
-                                              nsJSPrincipals::get(aPrincipal),
+                                              WorkletPrincipal::GetWorkletPrincipal(),
                                               true, aReflector);
 }
 
 void
 PaintWorkletGlobalScope::RegisterPaint(const nsAString& aType,
                                        VoidFunction& aProcessorCtor)
 {
   // Nothing to do here, yet.
--- a/dom/worklet/PaintWorkletGlobalScope.h
+++ b/dom/worklet/PaintWorkletGlobalScope.h
@@ -15,17 +15,17 @@ namespace dom {
 class VoidFunction;
 
 class PaintWorkletGlobalScope final : public WorkletGlobalScope
 {
 public:
   PaintWorkletGlobalScope();
 
   bool
-  WrapGlobalObject(JSContext* aCx, nsIPrincipal* aPrincipal,
+  WrapGlobalObject(JSContext* aCx,
                    JS::MutableHandle<JSObject*> aReflector) override;
 
   void
   RegisterPaint(const nsAString& aType, VoidFunction& aProcessorCtor);
 
 private:
   ~PaintWorkletGlobalScope() = default;
 };
--- a/dom/worklet/Worklet.cpp
+++ b/dom/worklet/Worklet.cpp
@@ -1,15 +1,16 @@
 /* -*- 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 "Worklet.h"
+#include "WorkletThread.h"
 #include "AudioWorkletGlobalScope.h"
 #include "PaintWorkletGlobalScope.h"
 
 #include "mozilla/dom/WorkletBinding.h"
 #include "mozilla/dom/BlobBinding.h"
 #include "mozilla/dom/Fetch.h"
 #include "mozilla/dom/PromiseNativeHandler.h"
 #include "mozilla/dom/RegisterWorkletBindings.h"
@@ -19,30 +20,62 @@
 #include "nsIInputStreamPump.h"
 #include "nsIThreadRetargetableRequest.h"
 #include "nsNetUtil.h"
 #include "xpcprivate.h"
 
 namespace mozilla {
 namespace dom {
 
+class ExecutionRunnable final : public Runnable
+{
+public:
+  ExecutionRunnable(WorkletFetchHandler* aHandler, Worklet::WorkletType aType,
+                    char16_t* aScriptBuffer, size_t aScriptLength)
+    : Runnable("Worklet::ExecutionRunnable")
+    , mHandler(aHandler)
+    , mWorkletType(aType)
+    , mBuffer(aScriptBuffer, aScriptLength,
+              JS::SourceBufferHolder::GiveOwnership)
+    , mResult(NS_ERROR_FAILURE)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+  }
+
+  NS_IMETHOD
+  Run() override;
+
+private:
+  void
+  RunOnWorkletThread();
+
+  void
+  RunOnMainThread();
+
+  RefPtr<WorkletFetchHandler> mHandler;
+  Worklet::WorkletType mWorkletType;
+  JS::SourceBufferHolder mBuffer;
+  nsresult mResult;
+};
+
 // ---------------------------------------------------------------------------
 // WorkletFetchHandler
 
-class WorkletFetchHandler : public PromiseNativeHandler
-                          , public nsIStreamLoaderObserver
+class WorkletFetchHandler final : public PromiseNativeHandler
+                                , public nsIStreamLoaderObserver
 {
 public:
-  NS_DECL_ISUPPORTS
+  NS_DECL_THREADSAFE_ISUPPORTS
 
   static already_AddRefed<Promise>
   Fetch(Worklet* aWorklet, const nsAString& aModuleURL, CallerType aCallerType,
         ErrorResult& aRv)
   {
     MOZ_ASSERT(aWorklet);
+    MOZ_ASSERT(NS_IsMainThread());
 
     nsCOMPtr<nsIGlobalObject> global =
       do_QueryInterface(aWorklet->GetParentObject());
     MOZ_ASSERT(global);
 
     RefPtr<Promise> promise = Promise::Create(global, aRv);
     if (NS_WARN_IF(aRv.Failed())) {
       return nullptr;
@@ -55,17 +88,18 @@ public:
     doc = window->GetExtantDoc();
     if (!doc) {
       promise->MaybeReject(NS_ERROR_FAILURE);
       return promise.forget();
     }
 
     nsCOMPtr<nsIURI> baseURI = doc->GetBaseURI();
     nsCOMPtr<nsIURI> resolvedURI;
-    nsresult rv = NS_NewURI(getter_AddRefs(resolvedURI), aModuleURL, nullptr, baseURI);
+    nsresult rv = NS_NewURI(getter_AddRefs(resolvedURI), aModuleURL, nullptr,
+                            baseURI);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       promise->MaybeReject(rv);
       return promise.forget();
     }
 
     nsAutoCString spec;
     rv = resolvedURI->GetSpec(spec);
     if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -100,16 +134,18 @@ public:
 
     aWorklet->AddImportFetchHandler(spec, handler);
     return promise.forget();
   }
 
   virtual void
   ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
   {
+    MOZ_ASSERT(NS_IsMainThread());
+
     if (!aValue.isObject()) {
       RejectPromises(NS_ERROR_FAILURE);
       return;
     }
 
     RefPtr<Response> response;
     nsresult rv = UNWRAP_OBJECT(Response, &aValue.toObject(), response);
     if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -179,84 +215,83 @@ public:
                                    NS_LITERAL_STRING("UTF-8"), nullptr,
                                    scriptTextBuf, scriptTextLength);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       RejectPromises(rv);
       return NS_OK;
     }
 
     // Moving the ownership of the buffer
-    JS::SourceBufferHolder buffer(scriptTextBuf, scriptTextLength,
-                                  JS::SourceBufferHolder::GiveOwnership);
-
-    AutoJSAPI jsapi;
-    jsapi.Init();
-
-    RefPtr<WorkletGlobalScope> globalScope =
-      mWorklet->GetOrCreateGlobalScope(jsapi.cx());
-    MOZ_ASSERT(globalScope);
-
-    AutoEntryScript aes(globalScope, "Worklet");
-    JSContext* cx = aes.cx();
-
-    JS::Rooted<JSObject*> globalObj(cx, globalScope->GetGlobalJSObject());
-
-    (void) new XPCWrappedNativeScope(cx, globalObj);
+    nsCOMPtr<nsIRunnable> runnable =
+      new ExecutionRunnable(this, mWorklet->Type(), scriptTextBuf,
+                            scriptTextLength);
 
-    NS_ConvertUTF16toUTF8 url(mURL);
-
-    JS::CompileOptions compileOptions(cx);
-    compileOptions.setIntroductionType("Worklet");
-    compileOptions.setFileAndLine(url.get(), 0);
-    compileOptions.setIsRunOnce(true);
-    compileOptions.setNoScriptRval(true);
-
-    JSAutoCompartment comp(cx, globalObj);
-
-    JS::Rooted<JS::Value> unused(cx);
-    if (!JS::Evaluate(cx, compileOptions, buffer, &unused)) {
-      ErrorResult error;
-      error.MightThrowJSException();
-      error.StealExceptionFromJSContext(cx);
-      RejectPromises(error.StealNSResult());
+    RefPtr<WorkletThread> thread = mWorklet->GetOrCreateThread();
+    if (!thread) {
+      RejectPromises(NS_ERROR_FAILURE);
       return NS_OK;
     }
 
-    // All done.
-    ResolvePromises();
+    if (NS_FAILED(thread->DispatchRunnable(runnable.forget()))) {
+      RejectPromises(NS_ERROR_FAILURE);
+      return NS_OK;
+    }
+
     return NS_OK;
   }
 
   virtual void
   RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
   {
+    MOZ_ASSERT(NS_IsMainThread());
     RejectPromises(NS_ERROR_DOM_NETWORK_ERR);
   }
 
+  const nsString& URL() const
+  {
+    return mURL;
+  }
+
+  void
+  ExecutionFailed(nsresult aRv)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    RejectPromises(aRv);
+  }
+
+  void
+  ExecutionSucceeded()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    ResolvePromises();
+  }
+
 private:
   WorkletFetchHandler(Worklet* aWorklet, const nsAString& aURL,
                       Promise* aPromise)
     : mWorklet(aWorklet)
     , mStatus(ePending)
     , mErrorStatus(NS_OK)
     , mURL(aURL)
   {
     MOZ_ASSERT(aWorklet);
     MOZ_ASSERT(aPromise);
+    MOZ_ASSERT(NS_IsMainThread());
 
     mPromises.AppendElement(aPromise);
   }
 
   ~WorkletFetchHandler()
   {}
 
   void
   AddPromise(Promise* aPromise)
   {
     MOZ_ASSERT(aPromise);
+    MOZ_ASSERT(NS_IsMainThread());
 
     switch (mStatus) {
       case ePending:
         mPromises.AppendElement(aPromise);
         return;
 
       case eRejected:
         MOZ_ASSERT(NS_FAILED(mErrorStatus));
@@ -269,31 +304,33 @@ private:
     }
   }
 
   void
   RejectPromises(nsresult aResult)
   {
     MOZ_ASSERT(mStatus == ePending);
     MOZ_ASSERT(NS_FAILED(aResult));
+    MOZ_ASSERT(NS_IsMainThread());
 
     for (uint32_t i = 0; i < mPromises.Length(); ++i) {
       mPromises[i]->MaybeReject(aResult);
     }
     mPromises.Clear();
 
     mStatus = eRejected;
     mErrorStatus = aResult;
     mWorklet = nullptr;
   }
 
   void
   ResolvePromises()
   {
     MOZ_ASSERT(mStatus == ePending);
+    MOZ_ASSERT(NS_IsMainThread());
 
     for (uint32_t i = 0; i < mPromises.Length(); ++i) {
       mPromises[i]->MaybeResolveWithUndefined();
     }
     mPromises.Clear();
 
     mStatus = eResolved;
     mWorklet = nullptr;
@@ -310,98 +347,216 @@ private:
 
   nsresult mErrorStatus;
 
   nsString mURL;
 };
 
 NS_IMPL_ISUPPORTS(WorkletFetchHandler, nsIStreamLoaderObserver)
 
+NS_IMETHODIMP
+ExecutionRunnable::Run()
+{
+  if (WorkletThread::IsOnWorkletThread()) {
+    RunOnWorkletThread();
+    return NS_DispatchToMainThread(this);
+  }
+
+  MOZ_ASSERT(NS_IsMainThread());
+  RunOnMainThread();
+  return NS_OK;
+}
+
+void
+ExecutionRunnable::RunOnWorkletThread()
+{
+  WorkletThread::AssertIsOnWorkletThread();
+
+  WorkletThread* workletThread = WorkletThread::Get();
+  MOZ_ASSERT(workletThread);
+
+  JSContext* cx = workletThread->GetJSContext();
+  JSAutoRequest ar(cx);
+
+  AutoJSAPI jsapi;
+  jsapi.Init();
+
+  RefPtr<WorkletGlobalScope> globalScope =
+    Worklet::CreateGlobalScope(jsapi.cx(), mWorkletType);
+  MOZ_ASSERT(globalScope);
+
+  AutoEntryScript aes(globalScope, "Worklet");
+  cx = aes.cx();
+
+  JS::Rooted<JSObject*> globalObj(cx, globalScope->GetGlobalJSObject());
+
+  NS_ConvertUTF16toUTF8 url(mHandler->URL());
+
+  JS::CompileOptions compileOptions(cx);
+  compileOptions.setIntroductionType("Worklet");
+  compileOptions.setFileAndLine(url.get(), 0);
+  compileOptions.setIsRunOnce(true);
+  compileOptions.setNoScriptRval(true);
+
+  JSAutoCompartment comp(cx, globalObj);
+
+  JS::Rooted<JS::Value> unused(cx);
+  if (!JS::Evaluate(cx, compileOptions, mBuffer, &unused)) {
+    ErrorResult error;
+    error.MightThrowJSException();
+    error.StealExceptionFromJSContext(cx);
+    mResult = error.StealNSResult();
+    return;
+  }
+
+  // All done.
+  mResult = NS_OK;
+}
+
+void
+ExecutionRunnable::RunOnMainThread()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (NS_FAILED(mResult)) {
+    mHandler->ExecutionFailed(mResult);
+    return;
+  }
+
+  mHandler->ExecutionSucceeded();
+}
+
 // ---------------------------------------------------------------------------
 // Worklet
 
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Worklet, mWindow, mScope)
+NS_IMPL_CYCLE_COLLECTION_CLASS(Worklet)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Worklet)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+  tmp->TerminateThread();
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Worklet)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(Worklet)
+
 NS_IMPL_CYCLE_COLLECTING_ADDREF(Worklet)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(Worklet)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Worklet)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 Worklet::Worklet(nsPIDOMWindowInner* aWindow, nsIPrincipal* aPrincipal,
                  WorkletType aWorkletType)
   : mWindow(aWindow)
   , mPrincipal(aPrincipal)
   , mWorkletType(aWorkletType)
 {
   MOZ_ASSERT(aWindow);
   MOZ_ASSERT(aPrincipal);
+  MOZ_ASSERT(NS_IsMainThread());
 
 #ifdef RELEASE_OR_BETA
   MOZ_CRASH("This code should not go to release/beta yet!");
 #endif
 }
 
 Worklet::~Worklet()
-{}
+{
+  TerminateThread();
+}
 
 JSObject*
 Worklet::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
+  MOZ_ASSERT(NS_IsMainThread());
   return WorkletBinding::Wrap(aCx, this, aGivenProto);
 }
 
 already_AddRefed<Promise>
 Worklet::Import(const nsAString& aModuleURL, CallerType aCallerType,
                 ErrorResult& aRv)
 {
+  MOZ_ASSERT(NS_IsMainThread());
   return WorkletFetchHandler::Fetch(this, aModuleURL, aCallerType, aRv);
 }
 
-WorkletGlobalScope*
-Worklet::GetOrCreateGlobalScope(JSContext* aCx)
+/* static */ already_AddRefed<WorkletGlobalScope>
+Worklet::CreateGlobalScope(JSContext* aCx, WorkletType aWorkletType)
 {
-  if (!mScope) {
-    switch (mWorkletType) {
-      case eAudioWorklet:
-        mScope = new AudioWorkletGlobalScope();
-        break;
-      case ePaintWorklet:
-        mScope = new PaintWorkletGlobalScope();
-        break;
-    }
+  WorkletThread::AssertIsOnWorkletThread();
+
+  RefPtr<WorkletGlobalScope> scope;
 
-    JS::Rooted<JSObject*> global(aCx);
-    NS_ENSURE_TRUE(mScope->WrapGlobalObject(aCx, mPrincipal, &global), nullptr);
-
-    JSAutoCompartment ac(aCx, global);
-
-    // Init Web IDL bindings
-    if (!RegisterWorkletBindings(aCx, global)) {
-      mScope = nullptr;
-      return nullptr;
-    }
-
-    JS_FireOnNewGlobalObject(aCx, global);
+  switch (aWorkletType) {
+    case eAudioWorklet:
+      scope = new AudioWorkletGlobalScope();
+      break;
+    case ePaintWorklet:
+      scope = new PaintWorkletGlobalScope();
+      break;
   }
 
-  return mScope;
+  JS::Rooted<JSObject*> global(aCx);
+  NS_ENSURE_TRUE(scope->WrapGlobalObject(aCx, &global), nullptr);
+
+  JSAutoCompartment ac(aCx, global);
+
+  // Init Web IDL bindings
+  if (!RegisterWorkletBindings(aCx, global)) {
+    return nullptr;
+  }
+
+  JS_FireOnNewGlobalObject(aCx, global);
+
+  return scope.forget();
 }
 
 WorkletFetchHandler*
 Worklet::GetImportFetchHandler(const nsACString& aURI)
 {
+  MOZ_ASSERT(NS_IsMainThread());
   return mImportHandlers.GetWeak(aURI);
 }
 
 void
 Worklet::AddImportFetchHandler(const nsACString& aURI,
                                WorkletFetchHandler* aHandler)
 {
   MOZ_ASSERT(aHandler);
   MOZ_ASSERT(!mImportHandlers.GetWeak(aURI));
+  MOZ_ASSERT(NS_IsMainThread());
 
   mImportHandlers.Put(aURI, aHandler);
 }
 
+WorkletThread*
+Worklet::GetOrCreateThread()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!mWorkletThread) {
+    // Thread creation. FIXME: this will change.
+    mWorkletThread = WorkletThread::Create();
+  }
+
+  return mWorkletThread;
+}
+
+void
+Worklet::TerminateThread()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (!mWorkletThread) {
+    return;
+  }
+
+  mWorkletThread->Terminate();
+  mWorkletThread = nullptr;
+}
+
 } // dom namespace
 } // mozilla namespace
--- a/dom/worklet/Worklet.h
+++ b/dom/worklet/Worklet.h
@@ -15,18 +15,19 @@
 
 class nsPIDOMWindowInner;
 class nsIPrincipal;
 
 namespace mozilla {
 namespace dom {
 
 class Promise;
+class WorkletFetchHandler;
 class WorkletGlobalScope;
-class WorkletFetchHandler;
+class WorkletThread;
 enum class CallerType : uint32_t;
 
 class Worklet final : public nsISupports
                     , public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Worklet)
@@ -46,35 +47,47 @@ public:
 
   virtual JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   already_AddRefed<Promise>
   Import(const nsAString& aModuleURL, CallerType aCallerType,
          ErrorResult& aRv);
 
-  WorkletGlobalScope*
-  GetOrCreateGlobalScope(JSContext* aCx);
+  WorkletType Type() const
+  {
+    return mWorkletType;
+  }
+
+  static already_AddRefed<WorkletGlobalScope>
+  CreateGlobalScope(JSContext* aCx, WorkletType aWorkletType);
+
+  WorkletThread*
+  GetOrCreateThread();
 
 private:
   ~Worklet();
 
   WorkletFetchHandler*
   GetImportFetchHandler(const nsACString& aURI);
 
   void
   AddImportFetchHandler(const nsACString& aURI, WorkletFetchHandler* aHandler);
 
+  void
+  TerminateThread();
+
   nsCOMPtr<nsPIDOMWindowInner> mWindow;
   nsCOMPtr<nsIPrincipal> mPrincipal;
 
   WorkletType mWorkletType;
 
-  RefPtr<WorkletGlobalScope> mScope;
   nsRefPtrHashtable<nsCStringHashKey, WorkletFetchHandler> mImportHandlers;
 
+  RefPtr<WorkletThread> mWorkletThread;
+
   friend class WorkletFetchHandler;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_Worklet_h
--- a/dom/worklet/WorkletGlobalScope.h
+++ b/dom/worklet/WorkletGlobalScope.h
@@ -12,18 +12,16 @@
 #include "mozilla/dom/BindingDeclarations.h"
 #include "nsIGlobalObject.h"
 #include "nsWrapperCache.h"
 
 #define WORKLET_IID \
   { 0x1b3f62e7, 0xe357, 0x44be, \
     { 0xbf, 0xe0, 0xdf, 0x85, 0xe6, 0x56, 0x85, 0xac } }
 
-class nsIPrincipal;
-
 namespace mozilla {
 namespace dom {
 
 class Console;
 
 class WorkletGlobalScope : public nsIGlobalObject
                          , public nsWrapperCache
 {
@@ -39,18 +37,17 @@ public:
   {
     return nullptr;
   }
 
   virtual JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   virtual bool
-  WrapGlobalObject(JSContext* aCx, nsIPrincipal* aPrincipal,
-                   JS::MutableHandle<JSObject*> aReflector) = 0;
+  WrapGlobalObject(JSContext* aCx, JS::MutableHandle<JSObject*> aReflector) = 0;
 
   virtual JSObject*
   GetGlobalJSObject() override
   {
     return GetWrapper();
   }
 
   already_AddRefed<Console>
new file mode 100644
--- /dev/null
+++ b/dom/worklet/WorkletPrincipal.cpp
@@ -0,0 +1,49 @@
+/* -*- 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 "WorkletPrincipal.h"
+#include "jsapi.h"
+#include "mozilla/Assertions.h"
+
+namespace mozilla {
+namespace dom {
+namespace WorkletPrincipal {
+
+struct WorkletPrincipal final : public JSPrincipals
+{
+  bool write(JSContext* aCx, JSStructuredCloneWriter* aWriter) override
+  {
+    MOZ_CRASH("WorkletPrincipal::write not implemented");
+    return false;
+  }
+};
+
+JSPrincipals*
+GetWorkletPrincipal()
+{
+  static WorkletPrincipal sPrincipal;
+
+  /*
+   * To make sure the the principals refcount is initialized to one, atomically
+   * increment it on every pass though this function. If we discover this wasn't
+   * the first time, decrement it again. This avoids the need for
+   * synchronization.
+   */
+  int32_t prevRefcount = sPrincipal.refcount++;
+  if (prevRefcount > 0) {
+    --sPrincipal.refcount;
+  } else {
+#ifdef DEBUG
+    sPrincipal.debugToken = kJSPrincipalsDebugToken;
+#endif
+  }
+
+  return &sPrincipal;
+}
+
+} // namespace WorkletPrincipal
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/worklet/WorkletPrincipal.h
@@ -0,0 +1,25 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_worklet_WorkletPrincipal_h
+#define mozilla_dom_worklet_WorkletPrincipal_h
+
+namespace mozilla {
+namespace dom {
+
+namespace WorkletPrincipal {
+
+JSPrincipals*
+GetWorkletPrincipal();
+
+static const uint32_t kJSPrincipalsDebugToken = 0x7e2df9f4;
+
+} // WorkletPrincipal
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_worklet_WorkletPrincipal_h
new file mode 100644
--- /dev/null
+++ b/dom/worklet/WorkletThread.cpp
@@ -0,0 +1,450 @@
+/* -*- 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 "nsCycleCollector.h"
+#include "mozilla/dom/AtomList.h"
+#include "mozilla/EventQueue.h"
+#include "mozilla/ThreadEventQueue.h"
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+// The size of the worklet runtime heaps in bytes.
+#define WORKLET_DEFAULT_RUNTIME_HEAPSIZE 32 * 1024 * 1024
+
+// The size of the generational GC nursery for worklet, in bytes.
+#define WORKLET_DEFAULT_NURSERY_SIZE 1 * 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;
+
+// This class is allocated per thread and can be retrieved from CC.
+// It's used to get the current WorketThread object.
+class WorkletThreadContextPrivate : private PerThreadAtomCache
+{
+public:
+  explicit
+  WorkletThreadContextPrivate(WorkletThread* aWorkletThread)
+    : mWorkletThread(aWorkletThread)
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+
+    // Zero out the base class members.
+    memset(this, 0, sizeof(PerThreadAtomCache));
+
+    MOZ_ASSERT(mWorkletThread);
+  }
+
+  ~WorkletThreadContextPrivate()
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+  }
+
+  WorkletThread*
+  GetWorkletThread() const
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+    MOZ_ASSERT(mWorkletThread);
+    return mWorkletThread;
+  }
+
+private:
+  WorkletThreadContextPrivate(const WorkletThreadContextPrivate&) = delete;
+  WorkletThreadContextPrivate& operator=(const WorkletThreadContextPrivate&) = delete;
+
+  RefPtr<WorkletThread> mWorkletThread;
+};
+
+// Helper functions
+
+bool
+PreserveWrapper(JSContext* aCx, JSObject* aObj)
+{
+  MOZ_ASSERT(aCx);
+  MOZ_ASSERT(aObj);
+  MOZ_ASSERT(mozilla::dom::IsDOMObject(aObj));
+  return mozilla::dom::TryPreserveWrapper(aObj);
+}
+
+void
+DestroyWorkletPrincipals(JSPrincipals* aPrincipals)
+{
+  MOZ_ASSERT_UNREACHABLE("Worklet principals refcount should never fall below one");
+}
+
+JSObject*
+Wrap(JSContext* aCx, JS::HandleObject aExisting, JS::HandleObject 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()
+  {
+  }
+
+  virtual void
+  PrepareForForgetSkippable() override
+  {
+  }
+
+  virtual void
+  BeginCycleCollectionCallback() 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
+  {
+    if (aStatus == JSGC_END) {
+      nsCycleCollector_collect(nullptr);
+    }
+  }
+};
+
+class WorkletJSContext final : public CycleCollectedJSContext
+{
+public:
+  explicit WorkletJSContext(WorkletThread* aWorkletThread)
+    : mWorkletThread(aWorkletThread)
+  {
+    MOZ_ASSERT(aWorkletThread);
+    MOZ_ASSERT(!NS_IsMainThread());
+
+    nsCycleCollector_startup();
+  }
+
+  ~WorkletJSContext()
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+
+    JSContext* cx = MaybeContext();
+    if (!cx) {
+      return;   // Initialize() must have failed
+    }
+
+    delete static_cast<WorkletThreadContextPrivate*>(JS_GetContextPrivate(cx));
+    JS_SetContextPrivate(cx, nullptr);
+
+    nsCycleCollector_shutdown();
+  }
+
+  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,
+                                          WORKLET_DEFAULT_NURSERY_SIZE);
+     if (NS_WARN_IF(NS_FAILED(rv))) {
+       return rv;
+     }
+
+    JSContext* cx = Context();
+
+    JS_SetContextPrivate(cx, new WorkletThreadContextPrivate(mWorkletThread));
+
+    js::SetPreserveWrapperCallback(cx, PreserveWrapper);
+    JS_InitDestroyPrincipalsCallback(cx, DestroyWorkletPrincipals);
+    JS_SetWrapObjectCallbacks(cx, &WrapObjectCallbacks);
+    JS_SetFutexCanWait(cx);
+
+    return NS_OK;
+  }
+
+  void
+  DispatchToMicroTask(already_AddRefed<MicroTaskRunnable> aRunnable) override
+  {
+    RefPtr<MicroTaskRunnable> runnable(aRunnable);
+
+#ifdef DEBUG
+    MOZ_ASSERT(!NS_IsMainThread());
+    MOZ_ASSERT(runnable);
+
+    WorkletThread* workletThread = WorkletThread::Get();
+    MOZ_ASSERT(workletThread);
+
+    JSContext* cx = workletThread->GetJSContext();
+    MOZ_ASSERT(cx);
+
+    JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
+    MOZ_ASSERT(global);
+#endif
+
+    GetMicroTaskQueue().push(runnable.forget());
+  }
+
+private:
+  RefPtr<WorkletThread> mWorkletThread;
+};
+
+// 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());
+
+    mParentRuntime =
+      JS_GetParentRuntime(CycleCollectedJSContext::Get()->Context());
+    MOZ_ASSERT(mParentRuntime);
+  }
+
+  NS_IMETHOD
+  Run() override
+  {
+    mWorkletThread->RunEventLoop(mParentRuntime);
+    return NS_OK;
+  }
+
+private:
+  RefPtr<WorkletThread> mWorkletThread;
+  JSRuntime* mParentRuntime;
+};
+
+// 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()
+  : nsThread(MakeNotNull<ThreadEventQueue<mozilla::EventQueue>*>(
+               MakeUnique<mozilla::EventQueue>()),
+             nsThread::NOT_MAIN_THREAD, kWorkletStackSize)
+  , mJSContext(nullptr)
+{
+}
+
+WorkletThread::~WorkletThread()
+{
+  // This should be gone during the termination step.
+  MOZ_ASSERT(!mJSContext);
+}
+
+// static
+already_AddRefed<WorkletThread>
+WorkletThread::Create()
+{
+  RefPtr<WorkletThread> thread = new WorkletThread();
+  if (NS_WARN_IF(NS_FAILED(thread->Init()))) {
+    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;
+}
+
+void
+WorkletThread::RunEventLoop(JSRuntime* aParentRuntime)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  PR_SetCurrentThreadName("worklet");
+
+  WorkletJSContext context(this);
+  nsresult rv = context.Initialize(aParentRuntime);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    // TODO: error propagation
+    return;
+  }
+
+  // FIXME: JS_SetDefaultLocale
+  // FIXME: JSSettings
+  // FIXME: JS_SetNativeStackQuota
+  // FIXME: JS_SetSecurityCallbacks
+  // FIXME: JS::SetAsmJSCacheOps
+  // FIXME: JS::SetAsyncTaskCallbacks
+  // FIXME: JS_AddInterruptCallback
+  // FIXME: JS::SetCTypesActivityCallback
+  // FIXME: JS_SetGCZeal
+
+  if (!JS::InitSelfHostedCode(context.Context())) {
+    // TODO: error propagation
+    return;
+  }
+
+  mJSContext = context.Context();
+
+  while (mJSContext) {
+    MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(this, /* wait: */ true));
+  }
+
+  MOZ_ASSERT(mJSContext == nullptr);
+}
+
+void
+WorkletThread::Terminate()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  RefPtr<TerminateRunnable> runnable = new TerminateRunnable(this);
+  DispatchRunnable(runnable.forget());
+}
+
+void
+WorkletThread::TerminateInternal()
+{
+  AssertIsOnWorkletThread();
+
+  mJSContext = nullptr;
+
+  nsCOMPtr<nsIRunnable> runnable =
+    NewRunnableMethod("WorkletThread::Shutdown", this,
+                      &WorkletThread::Shutdown);
+  NS_DispatchToMainThread(runnable);
+}
+
+JSContext*
+WorkletThread::GetJSContext() const
+{
+  AssertIsOnWorkletThread();
+  MOZ_ASSERT(mJSContext);
+  return mJSContext;
+}
+
+/* static */ bool
+WorkletThread::IsOnWorkletThread()
+{
+  const char* threadName = PR_GetThreadName(PR_GetCurrentThread());
+  return threadName && !strcmp(threadName, "worklet");
+}
+
+/* static */ void
+WorkletThread::AssertIsOnWorkletThread()
+{
+  MOZ_ASSERT(IsOnWorkletThread());
+}
+
+/* static */ WorkletThread*
+WorkletThread::Get()
+{
+  AssertIsOnWorkletThread();
+
+  CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get();
+  MOZ_ASSERT(ccjscx);
+
+  void* cxPrivate = JS_GetContextPrivate(ccjscx->Context());
+  MOZ_ASSERT(cxPrivate);
+
+  return
+    static_cast<WorkletThreadContextPrivate*>(cxPrivate)->GetWorkletThread();
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(WorkletThread, nsThread)
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/worklet/WorkletThread.h
@@ -0,0 +1,81 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_worklet_WorkletThread_h
+#define mozilla_dom_worklet_WorkletThread_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/CondVar.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "nsThread.h"
+
+class nsIRunnable;
+
+namespace mozilla {
+namespace dom {
+
+class WorkletThread final : public nsThread
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+
+  static already_AddRefed<WorkletThread>
+  Create();
+
+  static WorkletThread*
+  Get();
+
+  static bool
+  IsOnWorkletThread();
+
+  static void
+  AssertIsOnWorkletThread();
+
+  static JSPrincipals*
+  GetWorkerPrincipal();
+
+  JSContext*
+  GetJSContext() const;
+
+  nsresult
+  DispatchRunnable(already_AddRefed<nsIRunnable> aRunnable);
+
+  void
+  Terminate();
+
+private:
+  WorkletThread();
+  ~WorkletThread();
+
+  void
+  RunEventLoop(JSRuntime* aParentRuntime);
+  class PrimaryRunnable;
+
+  void
+  TerminateInternal();
+  class TerminateRunnable;
+
+  // This should only be called by consumers that have an
+  // nsIEventTarget/nsIThread pointer.
+  NS_IMETHOD
+  Dispatch(already_AddRefed<nsIRunnable> aRunnable, uint32_t aFlags) override;
+
+  NS_IMETHOD
+  DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags) override;
+
+  NS_IMETHOD
+  DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) override;
+
+  // Touched only on the worklet thread. This is a raw pointer because it's set
+  // and nullified by RunEventLoop().
+  JSContext* mJSContext;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_worklet_WorkletThread_h
--- a/dom/worklet/moz.build
+++ b/dom/worklet/moz.build
@@ -7,23 +7,27 @@
 with Files("**"):
     BUG_COMPONENT = ("Core", "DOM")
 
 EXPORTS.mozilla.dom += [
     'AudioWorkletGlobalScope.h',
     'PaintWorkletGlobalScope.h',
     'Worklet.h',
     'WorkletGlobalScope.h',
+    'WorkletPrincipal.h',
+    'WorkletThread.h',
 ]
 
 UNIFIED_SOURCES += [
     'AudioWorkletGlobalScope.cpp',
     'PaintWorkletGlobalScope.cpp',
     'Worklet.cpp',
     'WorkletGlobalScope.cpp',
+    'WorkletPrincipal.cpp',
+    'WorkletThread.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '/js/xpconnect/src',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')