Bug 1328964 - part 2 - WorkletThread r=baku
authorAndrea Marchesini <amarchesini@mozilla.com>
Thu, 12 Apr 2018 15:14:48 +1200
changeset 412950 16197254fa363db0fba9aae4211ff0e78733c2c7
parent 412949 b76bff2ddfc642e6d1da977c1b6a98ada13a2ae2
child 412951 bdd4af79d6f765e6071f67f39bea4c84085acb51
push id33828
push userarchaeopteryx@coole-files.de
push dateThu, 12 Apr 2018 19:19:41 +0000
treeherdermozilla-central@6e22c4a726c2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1328964
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1328964 - part 2 - WorkletThread r=baku 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"
@@ -6078,16 +6079,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')