bug 1442776 make CycleCollectedJSContext accessible from JSContext private r=peterv
authorKarl Tomlinson <karlt+@karlt.net>
Thu, 10 May 2018 17:04:12 +1200
changeset 421526 4d39fdf74ae76b02509a190b7081eecf49e25efe
parent 421525 077407e42b77e532de6e307307356673ecbda73e
child 421527 10c4e77579ca299b36ff975d71fdc1fa2a3fee36
push id64906
push userktomlinson@mozilla.com
push dateWed, 06 Jun 2018 03:49:27 +0000
treeherderautoland@4d39fdf74ae7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspeterv
bugs1442776
milestone62.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 1442776 make CycleCollectedJSContext accessible from JSContext private r=peterv Inheriting PerThreadAtomCache on CycleCollectedJSContext permits use of static_cast, avoiding one level of indirection compared to adding a CycleCollectedJSContext* to PerThreadAtomCache. PerThreadAtomCache is over 18kB, and so WorkerJSContext and WorkletJSContext are moved from the stack to the heap. MozReview-Commit-ID: 6jdJeZcviK4
dom/base/CustomElementRegistry.h
dom/bindings/BindingUtils.h
dom/geolocation/PositionError.cpp
dom/geolocation/nsGeolocation.cpp
dom/plugins/base/nsNPAPIPlugin.cpp
dom/serviceworkers/ServiceWorkerPrivate.cpp
dom/workers/RuntimeService.cpp
dom/worklet/WorkletThread.cpp
js/xpconnect/src/XPCJSContext.cpp
xpcom/base/CycleCollectedJSContext.cpp
xpcom/base/CycleCollectedJSContext.h
xpcom/base/nsTraceRefcnt.cpp
--- a/dom/base/CustomElementRegistry.h
+++ b/dom/base/CustomElementRegistry.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_CustomElementRegistry_h
 #define mozilla_dom_CustomElementRegistry_h
 
 #include "js/GCHashTable.h"
 #include "js/TypeDecls.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/CycleCollectedJSContext.h" // for MicroTaskRunnable
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/CustomElementRegistryBinding.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/FunctionBinding.h"
 #include "mozilla/dom/WebComponentsBinding.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsGenericHTMLElement.h"
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -9,17 +9,16 @@
 
 #include "jsfriendapi.h"
 #include "js/Wrapper.h"
 #include "js/Conversions.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Alignment.h"
 #include "mozilla/Array.h"
 #include "mozilla/Assertions.h"
-#include "mozilla/CycleCollectedJSContext.h"
 #include "mozilla/DeferredFinalize.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/CallbackObject.h"
 #include "mozilla/dom/DOMJSClass.h"
 #include "mozilla/dom/DOMJSProxyHandler.h"
 #include "mozilla/dom/Exceptions.h"
 #include "mozilla/dom/NonRefcountedDOMObject.h"
 #include "mozilla/dom/Nullable.h"
--- a/dom/geolocation/PositionError.cpp
+++ b/dom/geolocation/PositionError.cpp
@@ -1,16 +1,17 @@
 /* -*- 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 "mozilla/dom/PositionError.h"
 #include "mozilla/dom/PositionErrorBinding.h"
+#include "mozilla/CycleCollectedJSContext.h" // for nsAutoMicroTask
 #include "nsGeolocation.h"
 
 namespace mozilla {
 namespace dom {
 
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PositionError, mParent)
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(PositionError, AddRef)
--- a/dom/geolocation/nsGeolocation.cpp
+++ b/dom/geolocation/nsGeolocation.cpp
@@ -2,16 +2,17 @@
 /* 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 "nsGeolocation.h"
 
 #include "mozilla/ClearOnShutdown.h"
+#include "mozilla/CycleCollectedJSContext.h" // for nsAutoMicroTask
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/PermissionMessageUtils.h"
 #include "mozilla/dom/PositionError.h"
 #include "mozilla/dom/PositionErrorBinding.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/UniquePtr.h"
--- a/dom/plugins/base/nsNPAPIPlugin.cpp
+++ b/dom/plugins/base/nsNPAPIPlugin.cpp
@@ -15,16 +15,17 @@
 
 #include "nsPluginHost.h"
 #include "nsNPAPIPlugin.h"
 #include "nsNPAPIPluginInstance.h"
 #include "nsNPAPIPluginStreamListener.h"
 #include "nsPluginStreamListenerPeer.h"
 #include "nsIServiceManager.h"
 #include "nsThreadUtils.h"
+#include "mozilla/CycleCollectedJSContext.h" // for nsAutoMicroTask
 #include "mozilla/Preferences.h"
 #include "nsPluginInstanceOwner.h"
 
 #include "nsPluginsDir.h"
 #include "nsPluginLogging.h"
 
 #include "nsPIDOMWindow.h"
 #include "nsGlobalWindow.h"
--- a/dom/serviceworkers/ServiceWorkerPrivate.cpp
+++ b/dom/serviceworkers/ServiceWorkerPrivate.cpp
@@ -18,16 +18,17 @@
 #include "nsITimedChannel.h"
 #include "nsIUploadChannel2.h"
 #include "nsNetUtil.h"
 #include "nsProxyRelease.h"
 #include "nsQueryObject.h"
 #include "nsStreamUtils.h"
 #include "nsStringStream.h"
 #include "mozilla/Assertions.h"
+#include "mozilla/CycleCollectedJSContext.h" // for MicroTaskRunnable
 #include "mozilla/JSObjectHolder.h"
 #include "mozilla/dom/Client.h"
 #include "mozilla/dom/ClientIPCTypes.h"
 #include "mozilla/dom/DOMPrefs.h"
 #include "mozilla/dom/FetchUtil.h"
 #include "mozilla/dom/IndexedDatabaseManager.h"
 #include "mozilla/dom/InternalHeaders.h"
 #include "mozilla/dom/NotificationEvent.h"
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -815,60 +815,16 @@ ConsumeStream(JSContext* aCx,
     JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr,
                               JSMSG_ERROR_CONSUMING_RESPONSE);
     return false;
   }
 
   return FetchUtil::StreamResponseToJS(aCx, aObj, aMimeType, aConsumer, worker);
 }
 
-class WorkerJSContext;
-
-class WorkerThreadContextPrivate : private PerThreadAtomCache
-{
-  friend class WorkerJSContext;
-
-  WorkerPrivate* mWorkerPrivate;
-
-public:
-  // This can't return null, but we can't lose the "Get" prefix in the name or
-  // it will be ambiguous with the WorkerPrivate class name.
-  WorkerPrivate*
-  GetWorkerPrivate() const
-  {
-    MOZ_ASSERT(!NS_IsMainThread());
-    MOZ_ASSERT(mWorkerPrivate);
-
-    return mWorkerPrivate;
-  }
-
-private:
-  explicit
-  WorkerThreadContextPrivate(WorkerPrivate* aWorkerPrivate)
-    : mWorkerPrivate(aWorkerPrivate)
-  {
-    MOZ_ASSERT(!NS_IsMainThread());
-
-    // Zero out the base class members.
-    memset(this, 0, sizeof(PerThreadAtomCache));
-
-    MOZ_ASSERT(mWorkerPrivate);
-  }
-
-  ~WorkerThreadContextPrivate()
-  {
-    MOZ_ASSERT(!NS_IsMainThread());
-  }
-
-  WorkerThreadContextPrivate(const WorkerThreadContextPrivate&) = delete;
-
-  WorkerThreadContextPrivate&
-  operator=(const WorkerThreadContextPrivate&) = delete;
-};
-
 bool
 InitJSContextForWorker(WorkerPrivate* aWorkerPrivate, JSContext* aWorkerCx)
 {
   aWorkerPrivate->AssertIsOnWorkerThread();
   NS_ASSERTION(!aWorkerPrivate->GetJSContext(), "Already has a context!");
 
   JSSettings settings;
   aWorkerPrivate->CopyJSSettings(settings);
@@ -1040,17 +996,21 @@ public:
       nsCycleCollector_collect(nullptr);
     }
   }
 
 private:
   WorkerPrivate* mWorkerPrivate;
 };
 
-class MOZ_STACK_CLASS WorkerJSContext final : public mozilla::CycleCollectedJSContext
+} // anonymous namespace
+
+} // workerinternals namespace
+
+class WorkerJSContext final : public mozilla::CycleCollectedJSContext
 {
 public:
   // The heap size passed here doesn't matter, we will change it later in the
   // call to JS_SetGCParameter inside InitJSContextForWorker.
   explicit WorkerJSContext(WorkerPrivate* aWorkerPrivate)
     : mWorkerPrivate(aWorkerPrivate)
   {
     MOZ_COUNT_CTOR_INHERITED(WorkerJSContext, CycleCollectedJSContext);
@@ -1064,30 +1024,29 @@ public:
   ~WorkerJSContext()
   {
     MOZ_COUNT_DTOR_INHERITED(WorkerJSContext, CycleCollectedJSContext);
     JSContext* cx = MaybeContext();
     if (!cx) {
       return;   // Initialize() must have failed
     }
 
-    delete static_cast<WorkerThreadContextPrivate*>(JS_GetContextPrivate(cx));
-    JS_SetContextPrivate(cx, nullptr);
-
     // The worker global should be unrooted and the shutdown cycle collection
     // should break all remaining cycles. The superclass destructor will run
     // the GC one final time and finalize any JSObjects that were participating
     // in cycles that were broken during CC shutdown.
     nsCycleCollector_shutdown();
 
     // The CC is shut down, and the superclass destructor will GC, so make sure
     // we don't try to CC again.
     mWorkerPrivate = nullptr;
   }
 
+  WorkerJSContext* GetAsWorkerJSContext() override { return this; }
+
   CycleCollectedJSRuntime* CreateRuntime(JSContext* aCx) override
   {
     return new WorkerJSRuntime(aCx, mWorkerPrivate);
   }
 
   nsresult Initialize(JSRuntime* aParentRuntime)
   {
     nsresult rv =
@@ -1095,18 +1054,16 @@ public:
                                           WORKER_DEFAULT_RUNTIME_HEAPSIZE,
                                           WORKER_DEFAULT_NURSERY_SIZE);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
 
     JSContext* cx = Context();
 
-    JS_SetContextPrivate(cx, new WorkerThreadContextPrivate(mWorkerPrivate));
-
     js::SetPreserveWrapperCallback(cx, PreserveWrapper);
     JS_InitDestroyPrincipalsCallback(cx, DestroyWorkerPrincipals);
     JS_SetWrapObjectCallbacks(cx, &WrapObjectCallbacks);
     if (mWorkerPrivate->IsDedicatedWorker()) {
       JS_SetFutexCanWait(cx);
     }
 
     return NS_OK;
@@ -1143,20 +1100,29 @@ public:
     microTaskQueue->push(runnable.forget());
   }
 
   bool IsSystemCaller() const override
   {
     return mWorkerPrivate->UsesSystemPrincipal();
   }
 
+  WorkerPrivate* GetWorkerPrivate() const
+  {
+    return mWorkerPrivate;
+  }
+
 private:
   WorkerPrivate* mWorkerPrivate;
 };
 
+namespace workerinternals {
+
+namespace {
+
 class WorkerThreadPrimaryRunnable final : public Runnable
 {
   WorkerPrivate* mWorkerPrivate;
   RefPtr<WorkerThread> mThread;
   JSRuntime* mParentRuntime;
 
   class FinishedRunnable final : public Runnable
   {
@@ -2713,23 +2679,23 @@ WorkerThreadPrimaryRunnable::Run()
 
   SetThreadHelper threadHelper(mWorkerPrivate, mThread);
 
   mWorkerPrivate->AssertIsOnWorkerThread();
 
   {
     nsCycleCollector_startup();
 
-    WorkerJSContext context(mWorkerPrivate);
-    nsresult rv = context.Initialize(mParentRuntime);
+    auto context = MakeUnique<WorkerJSContext>(mWorkerPrivate);
+    nsresult rv = context->Initialize(mParentRuntime);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
-    JSContext* cx = context.Context();
+    JSContext* cx = context->Context();
 
     if (!InitJSContextForWorker(mWorkerPrivate, cx)) {
       // XXX need to fire an error at parent.
       NS_ERROR("Failed to create context!");
       return NS_ERROR_FAILURE;
     }
 
     {
@@ -2862,43 +2828,49 @@ ResumeWorkersForWindow(nsPIDOMWindowInne
 }
 
 WorkerPrivate*
 GetWorkerPrivateFromContext(JSContext* aCx)
 {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(aCx);
 
-  void* cxPrivate = JS_GetContextPrivate(aCx);
-  if (!cxPrivate) {
+  CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::GetFor(aCx);
+  if (!ccjscx) {
     return nullptr;
   }
 
-  return
-    static_cast<WorkerThreadContextPrivate*>(cxPrivate)->GetWorkerPrivate();
+  WorkerJSContext* workerjscx = ccjscx->GetAsWorkerJSContext();
+  // GetWorkerPrivateFromContext is called only for worker contexts.  The
+  // context private is cleared early in ~CycleCollectedJSContext() and so
+  // GetFor() returns null above if called after ccjscx is no longer a
+  // WorkerJSContext.
+  MOZ_ASSERT(workerjscx);
+  return workerjscx->GetWorkerPrivate();
 }
 
 WorkerPrivate*
 GetCurrentThreadWorkerPrivate()
 {
   MOZ_ASSERT(!NS_IsMainThread());
 
   CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get();
   if (!ccjscx) {
     return nullptr;
   }
 
-  JSContext* cx = ccjscx->Context();
-  MOZ_ASSERT(cx);
-
-  // Note that we can return nullptr if the nsCycleCollector_shutdown() in
-  // ~WorkerJSContext() triggers any calls to GetCurrentThreadWorkerPrivate().
-  // At this stage CycleCollectedJSContext::Get() will still return a context,
-  // but the context private has already been cleared.
-  return GetWorkerPrivateFromContext(cx);
+  WorkerJSContext* workerjscx = ccjscx->GetAsWorkerJSContext();
+  // Although GetCurrentThreadWorkerPrivate() is called only for worker
+  // threads, the ccjscx will no longer be a WorkerJSContext if called from
+  // stable state events during ~CycleCollectedJSContext().
+  if (!workerjscx) {
+    return nullptr;
+  }
+
+  return workerjscx->GetWorkerPrivate();
 }
 
 bool
 IsCurrentThreadRunningWorker()
 {
   return !NS_IsMainThread() && !!GetCurrentThreadWorkerPrivate();
 }
 
--- a/dom/worklet/WorkletThread.cpp
+++ b/dom/worklet/WorkletThread.cpp
@@ -22,53 +22,16 @@ namespace {
 
 // 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 WorkletThread 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));
@@ -165,19 +128,16 @@ public:
   {
     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();
   }
 
   WorkletJSContext* GetAsWorkletJSContext() override { return this; }
 
   CycleCollectedJSRuntime* CreateRuntime(JSContext* aCx) override
   {
     return new WorkletJSRuntime(aCx);
@@ -193,18 +153,16 @@ public:
                                           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;
   }
 
@@ -225,16 +183,21 @@ public:
 
     JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
     MOZ_ASSERT(global);
 #endif
 
     GetMicroTaskQueue().push(runnable.forget());
   }
 
+  WorkletThread* GetWorkletThread() const
+  {
+    return mWorkletThread;
+  }
+
   bool IsSystemCaller() const override
   {
     // Currently no support for special system worklet privileges.
     return false;
   }
 
 private:
   RefPtr<WorkletThread> mWorkletThread;
@@ -368,39 +331,39 @@ WorkletThread::DelayedDispatch(already_A
 
 void
 WorkletThread::RunEventLoop(JSRuntime* aParentRuntime)
 {
   MOZ_ASSERT(!NS_IsMainThread());
 
   PR_SetCurrentThreadName("worklet");
 
-  WorkletJSContext context(this);
-  nsresult rv = context.Initialize(aParentRuntime);
+  auto context = MakeUnique<WorkletJSContext>(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())) {
+  if (!JS::InitSelfHostedCode(context->Context())) {
     // TODO: error propagation
     return;
   }
 
-  mJSContext = context.Context();
+  mJSContext = context->Context();
 
   while (mJSContext) {
     MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(this, /* wait: */ true));
   }
 
   MOZ_ASSERT(mJSContext == nullptr);
 }
 
@@ -466,21 +429,19 @@ WorkletThread::AssertIsOnWorkletThread()
 /* 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();
+  WorkletJSContext* workletjscx = ccjscx->GetAsWorkletJSContext();
+  MOZ_ASSERT(workletjscx);
+  return workletjscx->GetWorkletThread();
 }
 
 // nsIObserver
 NS_IMETHODIMP
 WorkletThread::Observe(nsISupports* aSubject, const char* aTopic,
                        const char16_t*)
 {
   MOZ_ASSERT(strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0);
--- a/js/xpconnect/src/XPCJSContext.cpp
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -33,17 +33,16 @@
 #include "mozilla/dom/ScriptSettings.h"
 
 #include "nsContentUtils.h"
 #include "nsCCUncollectableMarker.h"
 #include "nsCycleCollectionNoteRootCallback.h"
 #include "nsCycleCollector.h"
 #include "jsapi.h"
 #include "js/MemoryMetrics.h"
-#include "mozilla/dom/GeneratedAtomList.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/ScriptLoader.h"
 #include "mozilla/dom/WindowBinding.h"
 #include "mozilla/extensions/WebExtensionPolicy.h"
 #include "mozilla/jsipc/CrossProcessObjectWrappers.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/Attributes.h"
@@ -73,17 +72,16 @@
 #include <windows.h>
 #endif
 
 static MOZ_THREAD_LOCAL(XPCJSContext*) gTlsContext;
 
 using namespace mozilla;
 using namespace xpc;
 using namespace JS;
-using mozilla::dom::PerThreadAtomCache;
 using mozilla::dom::AutoEntryScript;
 
 static void WatchdogMain(void* arg);
 class Watchdog;
 class WatchdogManager;
 class MOZ_RAII AutoLockWatchdog final
 {
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
@@ -900,20 +898,16 @@ XPCJSContext::~XPCJSContext()
     } else {
         // Otherwise, simply remove ourselves from the list.
         mWatchdogManager->UnregisterContext(this);
     }
 
     if (mCallContext)
         mCallContext->SystemIsBeingShutDown();
 
-    auto rtPrivate = static_cast<PerThreadAtomCache*>(JS_GetContextPrivate(Context()));
-    delete rtPrivate;
-    JS_SetContextPrivate(Context(), nullptr);
-
     PROFILER_CLEAR_JS_CONTEXT();
 
     gTlsContext.set(nullptr);
 }
 
 XPCJSContext::XPCJSContext()
  : mCallContext(nullptr),
    mAutoRoots(nullptr),
@@ -1000,20 +994,16 @@ XPCJSContext::Initialize(XPCJSContext* a
     }
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     MOZ_ASSERT(Context());
     JSContext* cx = Context();
 
-    auto cxPrivate = new PerThreadAtomCache();
-    memset(cxPrivate, 0, sizeof(PerThreadAtomCache));
-    JS_SetContextPrivate(cx, cxPrivate);
-
     // The JS engine permits us to set different stack limits for system code,
     // trusted script, and untrusted script. We have tests that ensure that
     // we can always execute 10 "heavy" (eval+with) stack frames deeper in
     // privileged code. Our stack sizes vary greatly in different configurations,
     // so satisfying those tests requires some care. Manual measurements of the
     // number of heavy stack frames achievable gives us the following rough data,
     // ordered by the effective categories in which they are grouped in the
     // JS_SetNativeStackQuota call (which predates this analysis).
--- a/xpcom/base/CycleCollectedJSContext.cpp
+++ b/xpcom/base/CycleCollectedJSContext.cpp
@@ -52,29 +52,36 @@ CycleCollectedJSContext::CycleCollectedJ
   , mRuntime(nullptr)
   , mJSContext(nullptr)
   , mDoingStableStates(false)
   , mTargetedMicroTaskRecursionDepth(0)
   , mMicroTaskLevel(0)
   , mMicroTaskRecursionDepth(0)
 {
   MOZ_COUNT_CTOR(CycleCollectedJSContext);
+
+  // Reinitialize PerThreadAtomCache because dom/bindings/Codegen.py compares
+  // against zero rather than JSID_VOID to detect uninitialized jsid members.
+  memset(static_cast<PerThreadAtomCache*>(this), 0, sizeof(PerThreadAtomCache));
+
   nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
   mOwningThread = thread.forget().downcast<nsThread>().take();
   MOZ_RELEASE_ASSERT(mOwningThread);
 }
 
 CycleCollectedJSContext::~CycleCollectedJSContext()
 {
   MOZ_COUNT_DTOR(CycleCollectedJSContext);
   // If the allocation failed, here we are.
   if (!mJSContext) {
     return;
   }
 
+  JS_SetContextPrivate(mJSContext, nullptr);
+
   mRuntime->RemoveContext(this);
 
   if (mIsPrimaryContext) {
     mRuntime->Shutdown(mJSContext);
   }
 
   // Last chance to process any events.
   CleanupIDBTransactions(mBaseRecursionDepth);
@@ -124,16 +131,19 @@ CycleCollectedJSContext::InitializeCommo
   NS_GetCurrentThread()->SetCanInvokeJS(true);
 
   JS::SetGetIncumbentGlobalCallback(mJSContext, GetIncumbentGlobalCallback);
 
   JS::SetEnqueuePromiseJobCallback(mJSContext, EnqueuePromiseJobCallback, this);
   JS::SetPromiseRejectionTrackerCallback(mJSContext, PromiseRejectionTrackerCallback, this);
   mUncaughtRejections.init(mJSContext, JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>(js::SystemAllocPolicy()));
   mConsumedRejections.init(mJSContext, JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>(js::SystemAllocPolicy()));
+
+  // Cast to PerThreadAtomCache for dom::GetAtomCache(JSContext*).
+  JS_SetContextPrivate(mJSContext, static_cast<PerThreadAtomCache*>(this));
 }
 
 nsresult
 CycleCollectedJSContext::Initialize(JSRuntime* aParentRuntime,
                                     uint32_t aMaxBytes,
                                     uint32_t aMaxNurseryBytes)
 {
   MOZ_ASSERT(!mJSContext);
@@ -170,16 +180,25 @@ CycleCollectedJSContext::InitializeNonPr
 
   InitializeCommon();
 
   nsCycleCollector_registerNonPrimaryContext(this);
 
   return NS_OK;
 }
 
+/* static */ CycleCollectedJSContext*
+CycleCollectedJSContext::GetFor(JSContext* aCx)
+{
+  // Cast from void* matching JS_SetContextPrivate.
+  auto atomCache = static_cast<PerThreadAtomCache*>(JS_GetContextPrivate(aCx));
+  // Down cast.
+  return static_cast<CycleCollectedJSContext*>(atomCache);
+}
+
 size_t
 CycleCollectedJSContext::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
 {
   return 0;
 }
 
 class PromiseJobRunnable final : public MicroTaskRunnable
 {
--- a/xpcom/base/CycleCollectedJSContext.h
+++ b/xpcom/base/CycleCollectedJSContext.h
@@ -8,16 +8,17 @@
 #define mozilla_CycleCollectedJSContext_h
 
 #include <queue>
 
 #include "mozilla/DeferredFinalize.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/mozalloc.h"
 #include "mozilla/MemoryReporting.h"
+#include "mozilla/dom/AtomList.h"
 #include "jsapi.h"
 #include "jsfriendapi.h"
 
 #include "nsCOMPtr.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsTArray.h"
 
 class nsCycleCollectionNoteRootCallback;
@@ -27,16 +28,17 @@ class nsWrapperCache;
 
 namespace mozilla {
 class AutoSlowOperation;
 
 class CycleCollectedJSRuntime;
 
 namespace dom {
 class Exception;
+class WorkerJSContext;
 class WorkletJSContext;
 } // namespace dom
 
 // Contains various stats about the cycle collection.
 struct CycleCollectorResults
 {
   CycleCollectorResults()
   {
@@ -78,17 +80,18 @@ public:
   NS_INLINE_DECL_REFCOUNTING(MicroTaskRunnable)
   virtual void Run(AutoSlowOperation& aAso) = 0;
   virtual bool Suppressed() { return false; }
 protected:
   virtual ~MicroTaskRunnable() {}
 };
 
 class CycleCollectedJSContext
-  : public LinkedListElement<CycleCollectedJSContext>
+  : dom::PerThreadAtomCache
+  , public LinkedListElement<CycleCollectedJSContext>
 {
   friend class CycleCollectedJSRuntime;
 
 protected:
   CycleCollectedJSContext();
   virtual ~CycleCollectedJSContext();
 
   MOZ_IS_CLASS_INIT
@@ -126,16 +129,17 @@ private:
   void CleanupIDBTransactions(uint32_t aRecursionDepth);
 
 public:
   enum DeferredFinalizeType {
     FinalizeIncrementally,
     FinalizeNow,
   };
 
+  virtual dom::WorkerJSContext* GetAsWorkerJSContext() { return nullptr; }
   virtual dom::WorkletJSContext* GetAsWorkletJSContext() { return nullptr; }
 
   CycleCollectedJSRuntime* Runtime() const
   {
     MOZ_ASSERT(mRuntime);
     return mRuntime;
   }
 
@@ -175,16 +179,21 @@ public:
 
   uint32_t RecursionDepth();
 
   // Run in stable state (call through nsContentUtils)
   void RunInStableState(already_AddRefed<nsIRunnable>&& aRunnable);
 
   void AddPendingIDBTransaction(already_AddRefed<nsIRunnable>&& aTransaction);
 
+  // Get the CycleCollectedJSContext for a JSContext.
+  // Returns null only if Initialize() has not completed on or during
+  // destruction of the CycleCollectedJSContext.
+  static CycleCollectedJSContext* GetFor(JSContext* aCx);
+
   // Get the current thread's CycleCollectedJSContext.  Returns null if there
   // isn't one.
   static CycleCollectedJSContext* Get();
 
   // Queue an async microtask to the current main or worker thread.
   virtual void DispatchToMicroTask(already_AddRefed<MicroTaskRunnable> aRunnable);
 
   // Call EnterMicroTask when you're entering JS execution.
--- a/xpcom/base/nsTraceRefcnt.cpp
+++ b/xpcom/base/nsTraceRefcnt.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 "nsTraceRefcnt.h"
+#include "mozilla/CycleCollectedJSContext.h"
 #include "mozilla/IntegerPrintfMacros.h"
 #include "mozilla/Path.h"
 #include "mozilla/StaticPtr.h"
 #include "nsXPCOMPrivate.h"
 #include "nscore.h"
 #include "nsISupports.h"
 #include "nsTArray.h"
 #include "nsTHashtable.h"