[gjs: 1/10] context: Move all private context methods to C++ class



commit 1abdf08fd18b594cbbaa7d774b29825de2dcd23d
Author: Philip Chimento <philip chimento gmail com>
Date:   Mon Sep 3 13:26:01 2018 -0400

    context: Move all private context methods to C++ class
    
    This is in preparation for having an "Atoms" accessor object for strings
    that are interned in the JS engine. This moves all internal GjsContext
    API to GjsContextPrivate, the private struct of GjsContext which is now
    also a C++ class.

 gi/function.cpp       |   9 +-
 gi/object.cpp         |  15 +-
 gi/value.cpp          |   3 +-
 gjs/context-private.h | 158 +++++++++++----
 gjs/context.cpp       | 544 ++++++++++++++++++++------------------------------
 gjs/debugger.cpp      |   4 +-
 gjs/engine.cpp        |  32 ++-
 gjs/engine.h          |   4 +-
 gjs/jsapi-util-root.h |  13 +-
 gjs/jsapi-util.cpp    |   6 +-
 modules/console.cpp   |   4 +-
 modules/system.cpp    |  44 +---
 12 files changed, 389 insertions(+), 447 deletions(-)
---
diff --git a/gi/function.cpp b/gi/function.cpp
index 41586f14..eb8ace4a 100644
--- a/gi/function.cpp
+++ b/gi/function.cpp
@@ -209,7 +209,8 @@ gjs_callback_closure(ffi_cif *cif,
     }
 
     context = gjs_closure_get_context(trampoline->js_function);
-    if (G_UNLIKELY(_gjs_context_is_sweeping(context))) {
+    GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context);
+    if (G_UNLIKELY(gjs->sweeping())) {
         warn_about_illegal_js_callback(trampoline, "during garbage collection",
             "destroying a Clutter actor or GTK widget with ::destroy signal "
             "connected, or using the destroy(), dispose(), or remove() vfuncs");
@@ -217,8 +218,7 @@ gjs_callback_closure(ffi_cif *cif,
         return;
     }
 
-    auto gjs_cx = static_cast<GjsContext *>(JS_GetContextPrivate(context));
-    if (G_UNLIKELY (!_gjs_context_get_is_owner_thread(gjs_cx))) {
+    if (G_UNLIKELY(!gjs->is_owner_thread())) {
         warn_about_illegal_js_callback(trampoline, "on a different thread",
             "an API not intended to be used in JS");
         gjs_callback_trampoline_unref(trampoline);
@@ -434,9 +434,8 @@ out:
              * main loop, or maybe not, but there's no way to tell, so we have
              * to exit here instead of propagating the exception back to the
              * original calling JS code. */
-            auto gcx = static_cast<GjsContext *>(JS_GetContextPrivate(context));
             uint8_t code;
-            if (_gjs_context_should_exit(gcx, &code))
+            if (gjs->should_exit(&code))
                 exit(code);
 
             /* Some other uncatchable exception, e.g. out of memory */
diff --git a/gi/object.cpp b/gi/object.cpp
index ee31e1b6..36201166 100644
--- a/gi/object.cpp
+++ b/gi/object.cpp
@@ -1208,8 +1208,6 @@ ObjectInstance::toggle_down(void)
      * collected by the GC
      */
     if (wrapper_is_rooted()) {
-        GjsContext *context;
-
         debug_lifecycle("Unrooting wrapper");
         switch_to_unrooted();
 
@@ -1229,9 +1227,9 @@ ObjectInstance::toggle_down(void)
          * always queue a garbage collection when a toggle reference goes
          * down.
          */
-        context = gjs_context_get_current();
-        if (!_gjs_context_destroying(context))
-            _gjs_context_schedule_gc(context);
+        GjsContextPrivate* gjs = GjsContextPrivate::from_current_context();
+        if (!gjs->destroying())
+            gjs->schedule_gc();
     }
 }
 
@@ -1283,10 +1281,9 @@ wrapped_gobj_toggle_notify(gpointer      data,
 {
     bool is_main_thread;
     bool toggle_up_queued, toggle_down_queued;
-    GjsContext *context;
 
-    context = gjs_context_get_current();
-    if (_gjs_context_destroying(context)) {
+    GjsContextPrivate* gjs = GjsContextPrivate::from_current_context();
+    if (gjs->destroying()) {
         /* Do nothing here - we're in the process of disassociating
          * the objects.
          */
@@ -1321,7 +1318,7 @@ wrapped_gobj_toggle_notify(gpointer      data,
      * weak singletons like g_bus_get_sync() objects can see toggle-ups
      * from different threads too.
      */
-    is_main_thread = _gjs_context_get_is_owner_thread(context);
+    is_main_thread = gjs->is_owner_thread();
 
     auto& toggle_queue = ToggleQueue::get_default();
     std::tie(toggle_down_queued, toggle_up_queued) = toggle_queue.is_queued(gobj);
diff --git a/gi/value.cpp b/gi/value.cpp
index 1e51fd96..a4827652 100644
--- a/gi/value.cpp
+++ b/gi/value.cpp
@@ -141,7 +141,8 @@ closure_marshal(GClosure        *closure,
     }
 
     context = gjs_closure_get_context(closure);
-    if (G_UNLIKELY(_gjs_context_is_sweeping(context))) {
+    GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context);
+    if (G_UNLIKELY(gjs->sweeping())) {
         GSignalInvocationHint *hint = (GSignalInvocationHint*) invocation_hint;
 
         g_critical("Attempting to call back into JSAPI during the sweeping phase of GC. "
diff --git a/gjs/context-private.h b/gjs/context-private.h
index a5e7be34..07d402c1 100644
--- a/gjs/context-private.h
+++ b/gjs/context-private.h
@@ -26,49 +26,127 @@
 
 #include <inttypes.h>
 
+#include <unordered_map>
+
 #include "context.h"
 #include "jsapi-util.h"
 #include "jsapi-wrapper.h"
 
-G_BEGIN_DECLS
-
-GJS_USE
-bool         _gjs_context_destroying                  (GjsContext *js_context);
-
-void         _gjs_context_schedule_gc_if_needed       (GjsContext *js_context);
-
-void _gjs_context_schedule_gc(GjsContext *js_context);
-
-void _gjs_context_exit(GjsContext *js_context,
-                       uint8_t     exit_code);
-
-GJS_USE
-bool _gjs_context_get_is_owner_thread(GjsContext *js_context);
-
-GJS_USE
-bool _gjs_context_should_exit(GjsContext *js_context,
-                              uint8_t    *exit_code_p);
-
-void _gjs_context_set_sweeping(GjsContext *js_context,
-                               bool        sweeping);
-
-GJS_USE
-bool _gjs_context_is_sweeping(JSContext *cx);
-
-GJS_JSAPI_RETURN_CONVENTION
-bool _gjs_context_enqueue_job(GjsContext      *gjs_context,
-                              JS::HandleObject job);
-
-GJS_USE
-bool _gjs_context_run_jobs(GjsContext *gjs_context);
-
-void _gjs_context_unregister_unhandled_promise_rejection(GjsContext *gjs_context,
-                                                         uint64_t    promise_id);
-
-G_END_DECLS
-
-void _gjs_context_register_unhandled_promise_rejection(GjsContext   *gjs_context,
-                                                       uint64_t      promise_id,
-                                                       GjsAutoChar&& stack);
+using JobQueue = JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>;
+
+class GjsContextPrivate {
+    GjsContext* m_public_context;
+    JSContext* m_cx;
+    JS::Heap<JSObject*> m_global;
+    GThread* m_owner_thread;
+
+    char* m_program_name;
+
+    char** m_search_path;
+
+    unsigned m_auto_gc_id;
+
+    std::array<JS::PersistentRootedId*, GJS_STRING_LAST> m_atoms;
+
+    JS::PersistentRooted<JobQueue>* m_job_queue;
+    unsigned m_idle_drain_handler;
+
+    std::unordered_map<uint64_t, GjsAutoChar> m_unhandled_rejection_stacks;
+
+    GjsProfiler* m_profiler;
+
+    /* Environment preparer needed for debugger, taken from SpiderMonkey's
+     * JS shell */
+    struct EnvironmentPreparer final : public js::ScriptEnvironmentPreparer {
+        JSContext* m_cx;
+
+        explicit EnvironmentPreparer(JSContext* cx) : m_cx(cx) {
+            js::SetScriptEnvironmentPreparer(m_cx, this);
+        }
+
+        void invoke(JS::HandleObject scope, Closure& closure) override;
+    };
+    EnvironmentPreparer m_environment_preparer;
+
+    uint8_t m_exit_code;
+
+    /* flags */
+    bool m_destroying : 1;
+    bool m_in_gc_sweep : 1;
+    bool m_should_exit : 1;
+    bool m_force_gc : 1;
+    bool m_draining_job_queue : 1;
+    bool m_should_profile : 1;
+    bool m_should_listen_sigusr2 : 1;
+
+    void schedule_gc_internal(bool force_gc);
+    static gboolean trigger_gc_if_needed(void* data);
+    static gboolean drain_job_queue_idle_handler(void* data);
+    void warn_about_unhandled_promise_rejections(void);
+    void reset_exit(void) {
+        m_should_exit = false;
+        m_exit_code = 0;
+    }
+
+ public:
+    /* Retrieving a GjsContextPrivate from JSContext or GjsContext */
+    GJS_USE static GjsContextPrivate* from_cx(JSContext* cx) {
+        return static_cast<GjsContextPrivate*>(JS_GetContextPrivate(cx));
+    }
+    GJS_USE static GjsContextPrivate* from_object(GObject* public_context);
+    GJS_USE static GjsContextPrivate* from_object(GjsContext* public_context);
+    GJS_USE static GjsContextPrivate* from_current_context(void) {
+        return from_object(gjs_context_get_current());
+    }
+
+    GjsContextPrivate(JSContext* cx, GjsContext* public_context);
+    ~GjsContextPrivate(void);
+
+    /* Accessors */
+    GJS_USE GjsContext* public_context(void) const { return m_public_context; }
+    GJS_USE JSContext* context(void) const { return m_cx; }
+    GJS_USE JSObject* global(void) const { return m_global.get(); }
+    GJS_USE GjsProfiler* profiler(void) const { return m_profiler; }
+    GJS_USE bool destroying(void) const { return m_destroying; }
+    GJS_USE bool sweeping(void) const { return m_in_gc_sweep; }
+    void set_sweeping(bool value) { m_in_gc_sweep = value; }
+    GJS_USE const char* program_name(void) const { return m_program_name; }
+    void set_program_name(char* value) { m_program_name = value; }
+    void set_search_path(char** value) { m_search_path = value; }
+    void set_should_profile(bool value) { m_should_profile = value; }
+    void set_should_listen_sigusr2(bool value) {
+        m_should_listen_sigusr2 = value;
+    }
+    GJS_USE bool is_owner_thread(void) const {
+        return m_owner_thread == g_thread_self();
+    }
+
+    GJS_JSAPI_RETURN_CONVENTION
+    bool eval(const char* script, ssize_t script_len, const char* filename,
+              int* exit_status_p, GError** error);
+
+    void schedule_gc(void) { schedule_gc_internal(true); }
+    void schedule_gc_if_needed(void) { schedule_gc_internal(false); }
+
+    void exit(uint8_t exit_code);
+    GJS_USE bool should_exit(uint8_t* exit_code_p) const;
+
+    GJS_JSAPI_RETURN_CONVENTION bool enqueue_job(JS::HandleObject job);
+    GJS_JSAPI_RETURN_CONVENTION bool run_jobs(void);
+    void register_unhandled_promise_rejection(uint64_t id, GjsAutoChar&& stack);
+    void unregister_unhandled_promise_rejection(uint64_t id);
+
+    static void trace(JSTracer* trc, void* data);
+
+    void free_profiler(void);
+    void dispose(void);
+
+    /* It's OK to return JS::HandleId here, to avoid an extra root, with the
+     * caveat that you should not use this value after the GjsContext has
+     * been destroyed. */
+    GJS_USE static JS::HandleId atom(JSContext* cx, GjsConstString name) {
+        return *GjsContextPrivate::from_cx(cx)->m_atoms[name];
+    }
+};
 
 #endif  /* __GJS_CONTEXT_PRIVATE_H__ */
diff --git a/gjs/context.cpp b/gjs/context.cpp
index 2e5a714a..d0d06bb4 100644
--- a/gjs/context.cpp
+++ b/gjs/context.cpp
@@ -71,19 +71,8 @@ static void     gjs_context_set_property      (GObject               *object,
                                                   const GValue          *value,
                                                   GParamSpec            *pspec);
 
-/* Environment preparer needed for debugger, taken from SpiderMonkey's
- * JS shell */
-struct GjsEnvironmentPreparer final : public js::ScriptEnvironmentPreparer {
-    JSContext* m_cx;
-
-    explicit GjsEnvironmentPreparer(JSContext* cx) : m_cx(cx) {
-        js::SetScriptEnvironmentPreparer(m_cx, this);
-    }
-
-    void invoke(JS::HandleObject scope, Closure& closure) override;
-};
-
-void GjsEnvironmentPreparer::invoke(JS::HandleObject scope, Closure& closure) {
+void GjsContextPrivate::EnvironmentPreparer::invoke(JS::HandleObject scope,
+                                                    Closure& closure) {
     g_assert(!JS_IsExceptionPending(m_cx));
 
     JSAutoCompartment ac(m_cx, scope);
@@ -91,44 +80,11 @@ void GjsEnvironmentPreparer::invoke(JS::HandleObject scope, Closure& closure) {
         gjs_log_exception(m_cx);
 }
 
-using JobQueue = JS::GCVector<JSObject *, 0, js::SystemAllocPolicy>;
-
 struct _GjsContext {
     GObject parent;
-
-    JSContext *context;
-    JS::Heap<JSObject*> global;
-    GThread *owner_thread;
-
-    char *program_name;
-
-    char **search_path;
-
-    bool destroying;
-    bool in_gc_sweep;
-
-    bool should_exit;
-    uint8_t exit_code;
-
-    guint    auto_gc_id;
-    bool     force_gc;
-
-    std::array<JS::PersistentRootedId*, GJS_STRING_LAST> const_strings;
-
-    JS::PersistentRooted<JobQueue> *job_queue;
-    unsigned idle_drain_handler;
-    bool draining_job_queue;
-
-    std::unordered_map<uint64_t, GjsAutoChar> unhandled_rejection_stacks;
-
-    GjsProfiler *profiler;
-    bool should_profile : 1;
-    bool should_listen_sigusr2 : 1;
-
-    GjsEnvironmentPreparer environment_preparer;
 };
 
-/* Keep this consistent with GjsConstString */
+/* Keep this consistent with GjsConstString in context-private.h */
 static const char *const_strings[] = {
     "constructor", "prototype", "length",
     "imports", "__parentModule__", "__init__", "searchPath",
@@ -150,11 +106,23 @@ struct _GjsContextClass {
 _Pragma("GCC diagnostic push")
 _Pragma("GCC diagnostic ignored \"-Wcast-function-type\"")
 #endif
-G_DEFINE_TYPE(GjsContext, gjs_context, G_TYPE_OBJECT);
+G_DEFINE_TYPE_WITH_PRIVATE(GjsContext, gjs_context, G_TYPE_OBJECT);
 #if __GNUC__ >= 8
 _Pragma("GCC diagnostic pop")
 #endif
 
+GjsContextPrivate* GjsContextPrivate::from_object(GObject* js_context) {
+    g_return_val_if_fail(GJS_IS_CONTEXT(js_context), nullptr);
+    return static_cast<GjsContextPrivate*>(
+        gjs_context_get_instance_private(GJS_CONTEXT(js_context)));
+}
+
+GjsContextPrivate* GjsContextPrivate::from_object(GjsContext* js_context) {
+    g_return_val_if_fail(GJS_IS_CONTEXT(js_context), nullptr);
+    return static_cast<GjsContextPrivate*>(
+        gjs_context_get_instance_private(js_context));
+}
+
 enum {
     PROP_0,
     PROP_SEARCH_PATH,
@@ -186,8 +154,8 @@ gjs_context_dump_heaps(void)
         return;
 
     for (GList *l = all_contexts; l; l = g_list_next(l)) {
-        auto js_context = static_cast<GjsContext *>(l->data);
-        js::DumpHeap(js_context->context, fp, js::IgnoreNurseryObjects);
+        auto* gjs = static_cast<GjsContextPrivate*>(l->data);
+        js::DumpHeap(gjs->context(), fp, js::IgnoreNurseryObjects);
     }
 
     fclose(fp);
@@ -327,17 +295,13 @@ gjs_context_class_init(GjsContextClass *klass)
     gjs_register_static_modules();
 }
 
-static void
-gjs_context_tracer(JSTracer *trc, void *data)
-{
-    GjsContext *gjs_context = reinterpret_cast<GjsContext *>(data);
-    JS::TraceEdge<JSObject *>(trc, &gjs_context->global, "GJS global object");
+void GjsContextPrivate::trace(JSTracer* trc, void* data) {
+    auto* gjs = static_cast<GjsContextPrivate*>(data);
+    JS::TraceEdge<JSObject*>(trc, &gjs->m_global, "GJS global object");
 }
 
-static void
-warn_about_unhandled_promise_rejections(GjsContext *gjs_context)
-{
-    for (auto& kv : gjs_context->unhandled_rejection_stacks) {
+void GjsContextPrivate::warn_about_unhandled_promise_rejections(void) {
+    for (auto& kv : m_unhandled_rejection_stacks) {
         const char *stack = kv.second;
         g_warning("Unhandled promise rejection. To suppress this warning, add "
                   "an error handler to your promise chain with .catch() or a "
@@ -346,7 +310,7 @@ warn_about_unhandled_promise_rejections(GjsContext *gjs_context)
                     "Unfortunately there is no stack trace of the failed promise.",
                   stack ? stack : "");
     }
-    gjs_context->unhandled_rejection_stacks.clear();
+    m_unhandled_rejection_stacks.clear();
 }
 
 static void
@@ -354,14 +318,10 @@ gjs_context_dispose(GObject *object)
 {
     gjs_debug(GJS_DEBUG_CONTEXT, "JS shutdown sequence");
 
-    GjsContext *js_context;
-
-    js_context = GJS_CONTEXT(object);
+    GjsContextPrivate* gjs = GjsContextPrivate::from_object(object);
 
     /* Profiler must be stopped and freed before context is shut down */
-    gjs_debug(GJS_DEBUG_CONTEXT, "Stopping profiler");
-    if (js_context->profiler)
-        g_clear_pointer(&js_context->profiler, _gjs_profiler_free);
+    gjs->free_profiler();
 
     /* Stop accepting entries in the toggle queue before running dispose
      * notifications, which causes all GjsMaybeOwned instances to unroot.
@@ -376,24 +336,33 @@ gjs_context_dispose(GObject *object)
               "Notifying reference holders of GjsContext dispose");
     G_OBJECT_CLASS(gjs_context_parent_class)->dispose(object);
 
-    if (js_context->context != NULL) {
+    gjs->dispose();
+}
+
+void GjsContextPrivate::free_profiler(void) {
+    gjs_debug(GJS_DEBUG_CONTEXT, "Stopping profiler");
+    if (m_profiler)
+        g_clear_pointer(&m_profiler, _gjs_profiler_free);
+}
 
+void GjsContextPrivate::dispose(void) {
+    if (m_cx) {
         gjs_debug(GJS_DEBUG_CONTEXT,
                   "Checking unhandled promise rejections");
-        warn_about_unhandled_promise_rejections(js_context);
+        warn_about_unhandled_promise_rejections();
 
-        JS_BeginRequest(js_context->context);
+        JS_BeginRequest(m_cx);
 
         /* Do a full GC here before tearing down, since once we do
          * that we may not have the JS_GetPrivate() to access the
          * context
          */
         gjs_debug(GJS_DEBUG_CONTEXT, "Final triggered GC");
-        JS_GC(js_context->context);
-        JS_EndRequest(js_context->context);
+        JS_GC(m_cx);
+        JS_EndRequest(m_cx);
 
         gjs_debug(GJS_DEBUG_CONTEXT, "Destroying JS context");
-        js_context->destroying = true;
+        m_destroying = true;
 
         /* Now, release all native objects, to avoid recursion between
          * the JS teardown and the C teardown.  The JSObject proxies
@@ -403,47 +372,39 @@ gjs_context_dispose(GObject *object)
         gjs_object_prepare_shutdown();
 
         gjs_debug(GJS_DEBUG_CONTEXT, "Disabling auto GC");
-        if (js_context->auto_gc_id > 0) {
-            g_source_remove (js_context->auto_gc_id);
-            js_context->auto_gc_id = 0;
+        if (m_auto_gc_id > 0) {
+            g_source_remove(m_auto_gc_id);
+            m_auto_gc_id = 0;
         }
 
         gjs_debug(GJS_DEBUG_CONTEXT, "Ending trace on global object");
-        JS_RemoveExtraGCRootsTracer(js_context->context, gjs_context_tracer,
-                                    js_context);
-        js_context->global = NULL;
+        JS_RemoveExtraGCRootsTracer(m_cx, &GjsContextPrivate::trace, this);
+        m_global = nullptr;
 
         gjs_debug(GJS_DEBUG_CONTEXT, "Unrooting atoms");
-        for (auto& root : js_context->const_strings)
+        for (auto& root : m_atoms)
             delete root;
 
         gjs_debug(GJS_DEBUG_CONTEXT, "Freeing allocated resources");
-        delete js_context->job_queue;
+        delete m_job_queue;
 
         /* Tear down JS */
-        JS_DestroyContext(js_context->context);
-        js_context->context = NULL;
+        JS_DestroyContext(m_cx);
+        m_cx = nullptr;
+        // don't use g_clear_pointer() as we want the pointer intact while we
+        // destroy the context in case we dump stack
         gjs_debug(GJS_DEBUG_CONTEXT, "JS context destroyed");
     }
 }
 
+GjsContextPrivate::~GjsContextPrivate(void) {
+    g_clear_pointer(&m_search_path, g_strfreev);
+    g_clear_pointer(&m_program_name, g_free);
+}
+
 static void
 gjs_context_finalize(GObject *object)
 {
-    GjsContext *js_context;
-
-    js_context = GJS_CONTEXT(object);
-
-    if (js_context->search_path != NULL) {
-        g_strfreev(js_context->search_path);
-        js_context->search_path = NULL;
-    }
-
-    if (js_context->program_name != NULL) {
-        g_free(js_context->program_name);
-        js_context->program_name = NULL;
-    }
-
     if (gjs_context_get_current() == (GjsContext*)object)
         gjs_context_make_current(NULL);
 
@@ -451,10 +412,8 @@ gjs_context_finalize(GObject *object)
     all_contexts = g_list_remove(all_contexts, object);
     g_mutex_unlock(&contexts_lock);
 
-    js_context->global.~Heap();
-    js_context->const_strings.~array();
-    js_context->unhandled_rejection_stacks.~unordered_map();
-    js_context->environment_preparer.~GjsEnvironmentPreparer();
+    GjsContextPrivate* gjs = GjsContextPrivate::from_object(object);
+    gjs->~GjsContextPrivate();
     G_OBJECT_CLASS(gjs_context_parent_class)->finalize(object);
 }
 
@@ -462,84 +421,86 @@ static void
 gjs_context_constructed(GObject *object)
 {
     GjsContext *js_context = GJS_CONTEXT(object);
-    int i;
 
     G_OBJECT_CLASS(gjs_context_parent_class)->constructed(object);
 
-    js_context->owner_thread = g_thread_self();
-
-    JSContext *cx = gjs_create_js_context(js_context);
+    GjsContextPrivate* gjs_location = GjsContextPrivate::from_object(object);
+    JSContext* cx = gjs_create_js_context(gjs_location);
     if (!cx)
         g_error("Failed to create javascript context");
-    js_context->context = cx;
+
+    new (gjs_location) GjsContextPrivate(cx, js_context);
+
+    g_mutex_lock(&contexts_lock);
+    all_contexts = g_list_prepend(all_contexts, object);
+    g_mutex_unlock(&contexts_lock);
+
+    setup_dump_heap();
+
+    g_object_weak_ref(object, gjs_object_context_dispose_notify, nullptr);
+}
+
+GjsContextPrivate::GjsContextPrivate(JSContext* cx, GjsContext* public_context)
+    : m_public_context(public_context), m_cx(cx), m_environment_preparer(cx) {
+    m_owner_thread = g_thread_self();
 
     const char *env_profiler = g_getenv("GJS_ENABLE_PROFILER");
-    if (env_profiler || js_context->should_listen_sigusr2)
-        js_context->should_profile = true;
+    if (env_profiler || m_should_listen_sigusr2)
+        m_should_profile = true;
 
-    if (js_context->should_profile) {
-        js_context->profiler = _gjs_profiler_new(js_context);
+    if (m_should_profile) {
+        m_profiler = _gjs_profiler_new(public_context);
 
-        if (!js_context->profiler) {
-            js_context->should_profile = false;
+        if (!m_profiler) {
+            m_should_profile = false;
         } else {
-            if (js_context->should_listen_sigusr2)
-                _gjs_profiler_setup_signals(js_context->profiler, js_context);
+            if (m_should_listen_sigusr2)
+                _gjs_profiler_setup_signals(m_profiler, public_context);
         }
     }
 
-    new (&js_context->unhandled_rejection_stacks) std::unordered_map<uint64_t, GjsAutoChar>;
-    new (&js_context->const_strings) std::array<JS::PersistentRootedId*, GJS_STRING_LAST>;
-    new (&js_context->environment_preparer) GjsEnvironmentPreparer(cx);
-    for (i = 0; i < GJS_STRING_LAST; i++) {
-        js_context->const_strings[i] = new JS::PersistentRootedId(cx,
-            gjs_intern_string_to_id(cx, const_strings[i]));
+    for (size_t ix = 0; ix < GJS_STRING_LAST; ix++) {
+        m_atoms[ix] = new JS::PersistentRootedId(
+            m_cx, gjs_intern_string_to_id(m_cx, const_strings[ix]));
     }
 
-    js_context->job_queue = new JS::PersistentRooted<JobQueue>(cx);
-    if (!js_context->job_queue)
+    m_job_queue = new JS::PersistentRooted<JobQueue>(m_cx);
+    if (!m_job_queue)
         g_error("Failed to initialize promise job queue");
 
-    JS_BeginRequest(cx);
+    JS_BeginRequest(m_cx);
 
-    JS::RootedObject global(cx, gjs_create_global_object(cx));
+    JS::RootedObject global(m_cx, gjs_create_global_object(m_cx));
     if (!global) {
-        gjs_log_exception(js_context->context);
+        gjs_log_exception(m_cx);
         g_error("Failed to initialize global object");
     }
 
-    JSAutoCompartment ac(cx, global);
+    JSAutoCompartment ac(m_cx, global);
 
-    new (&js_context->global) JS::Heap<JSObject *>(global);
-    JS_AddExtraGCRootsTracer(cx, gjs_context_tracer, js_context);
+    m_global = global;
+    JS_AddExtraGCRootsTracer(m_cx, &GjsContextPrivate::trace, this);
 
-    JS::RootedObject importer(cx, gjs_create_root_importer(cx,
-        js_context->search_path ? js_context->search_path : nullptr));
+    JS::RootedObject importer(m_cx,
+                              gjs_create_root_importer(m_cx, m_search_path));
     if (!importer) {
         gjs_log_exception(cx);
         g_error("Failed to create root importer");
     }
 
-    JS::Value v_importer = gjs_get_global_slot(cx, GJS_GLOBAL_SLOT_IMPORTS);
+    JS::Value v_importer = gjs_get_global_slot(m_cx, GJS_GLOBAL_SLOT_IMPORTS);
     g_assert(((void) "Someone else already created root importer",
               v_importer.isUndefined()));
 
-    gjs_set_global_slot(cx, GJS_GLOBAL_SLOT_IMPORTS, JS::ObjectValue(*importer));
+    gjs_set_global_slot(m_cx, GJS_GLOBAL_SLOT_IMPORTS,
+                        JS::ObjectValue(*importer));
 
-    if (!gjs_define_global_properties(cx, global, "default")) {
-        gjs_log_exception(cx);
+    if (!gjs_define_global_properties(m_cx, global, "default")) {
+        gjs_log_exception(m_cx);
         g_error("Failed to define properties on global object");
     }
 
-    JS_EndRequest(cx);
-
-    g_mutex_lock (&contexts_lock);
-    all_contexts = g_list_prepend(all_contexts, object);
-    g_mutex_unlock (&contexts_lock);
-
-    setup_dump_heap();
-
-    g_object_weak_ref(object, gjs_object_context_dispose_notify, nullptr);
+    JS_EndRequest(m_cx);
 }
 
 static void
@@ -548,13 +509,11 @@ gjs_context_get_property (GObject     *object,
                           GValue      *value,
                           GParamSpec  *pspec)
 {
-    GjsContext *js_context;
-
-    js_context = GJS_CONTEXT (object);
+    GjsContextPrivate* gjs = GjsContextPrivate::from_object(object);
 
     switch (prop_id) {
     case PROP_PROGRAM_NAME:
-        g_value_set_string(value, js_context->program_name);
+        g_value_set_string(value, gjs->program_name());
         break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -568,22 +527,20 @@ gjs_context_set_property (GObject      *object,
                           const GValue *value,
                           GParamSpec   *pspec)
 {
-    GjsContext *js_context;
-
-    js_context = GJS_CONTEXT (object);
+    GjsContextPrivate* gjs = GjsContextPrivate::from_object(object);
 
     switch (prop_id) {
     case PROP_SEARCH_PATH:
-        js_context->search_path = (char**) g_value_dup_boxed(value);
+        gjs->set_search_path(static_cast<char**>(g_value_dup_boxed(value)));
         break;
     case PROP_PROGRAM_NAME:
-        js_context->program_name = g_value_dup_string(value);
+        gjs->set_program_name(g_value_dup_string(value));
         break;
     case PROP_PROFILER_ENABLED:
-        js_context->should_profile = g_value_get_boolean(value);
+        gjs->set_should_profile(g_value_get_boolean(value));
         break;
     case PROP_PROFILER_SIGUSR2:
-        js_context->should_listen_sigusr2 = g_value_get_boolean(value);
+        gjs->set_should_listen_sigusr2(g_value_get_boolean(value));
         break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -606,143 +563,74 @@ gjs_context_new_with_search_path(char** search_path)
                          NULL);
 }
 
-bool
-_gjs_context_destroying (GjsContext *context)
-{
-    return context->destroying;
-}
-
-static gboolean
-trigger_gc_if_needed (gpointer user_data)
-{
-    GjsContext *js_context = GJS_CONTEXT(user_data);
-    js_context->auto_gc_id = 0;
+gboolean GjsContextPrivate::trigger_gc_if_needed(void* data) {
+    auto* gjs = static_cast<GjsContextPrivate*>(data);
+    gjs->m_auto_gc_id = 0;
 
-    if (js_context->force_gc)
-        JS_GC(js_context->context);
+    if (gjs->m_force_gc)
+        JS_GC(gjs->m_cx);
     else
-        gjs_gc_if_needed(js_context->context);
+        gjs_gc_if_needed(gjs->m_cx);
 
-    js_context->force_gc = false;
+    gjs->m_force_gc = false;
 
     return G_SOURCE_REMOVE;
 }
 
+void GjsContextPrivate::schedule_gc_internal(bool force_gc) {
+    m_force_gc |= force_gc;
 
-static void
-_gjs_context_schedule_gc_internal(GjsContext *js_context,
-                                  bool        force_gc)
-{
-    js_context->force_gc |= force_gc;
-
-    if (js_context->auto_gc_id > 0)
+    if (m_auto_gc_id > 0)
         return;
 
-    js_context->auto_gc_id = g_timeout_add_seconds_full(G_PRIORITY_LOW, 10,
-                                                        trigger_gc_if_needed,
-                                                        js_context, NULL);
-}
-
-void
-_gjs_context_schedule_gc(GjsContext *js_context)
-{
-    _gjs_context_schedule_gc_internal(js_context, true);
-}
-
-void
-_gjs_context_schedule_gc_if_needed(GjsContext *js_context)
-{
-    _gjs_context_schedule_gc_internal(js_context, false);
+    m_auto_gc_id = g_timeout_add_seconds_full(G_PRIORITY_LOW, 10,
+                                              trigger_gc_if_needed, this,
+                                              nullptr);
 }
 
-void
-_gjs_context_exit(GjsContext *js_context,
-                  uint8_t     exit_code)
-{
-    g_assert(!js_context->should_exit);
-    js_context->should_exit = true;
-    js_context->exit_code = exit_code;
+void GjsContextPrivate::exit(uint8_t exit_code) {
+    g_assert(!m_should_exit);
+    m_should_exit = true;
+    m_exit_code = exit_code;
 }
 
-bool
-_gjs_context_should_exit(GjsContext *js_context,
-                         uint8_t    *exit_code_p)
-{
+bool GjsContextPrivate::should_exit(uint8_t* exit_code_p) const {
     if (exit_code_p != NULL)
-        *exit_code_p = js_context->exit_code;
-    return js_context->should_exit;
+        *exit_code_p = m_exit_code;
+    return m_should_exit;
 }
 
-static void
-context_reset_exit(GjsContext *js_context)
-{
-    js_context->should_exit = false;
-    js_context->exit_code = 0;
-}
-
-bool
-_gjs_context_get_is_owner_thread(GjsContext *js_context)
-{
-    return js_context->owner_thread == g_thread_self();
-}
-
-void
-_gjs_context_set_sweeping(GjsContext *js_context,
-                          bool        sweeping)
-{
-    js_context->in_gc_sweep = sweeping;
-}
-
-bool
-_gjs_context_is_sweeping(JSContext *cx)
-{
-    auto js_context = static_cast<GjsContext *>(JS_GetContextPrivate(cx));
-    return js_context->in_gc_sweep;
-}
-
-static gboolean
-drain_job_queue_idle_handler(void *data)
-{
-    auto gjs_context = static_cast<GjsContext *>(data);
-    if (!_gjs_context_run_jobs(gjs_context)) {
-        auto* cx = static_cast<JSContext*>(
-            gjs_context_get_native_context(gjs_context));
-        gjs_log_exception(cx);
-    }
+gboolean GjsContextPrivate::drain_job_queue_idle_handler(void* data) {
+    auto* gjs = static_cast<GjsContextPrivate*>(data);
+    if (!gjs->run_jobs())
+        gjs_log_exception(gjs->context());
     /* Uncatchable exceptions are swallowed here - no way to get a handle on
      * the main loop to exit it from this idle handler */
-    g_assert(((void) "_gjs_context_run_jobs() should have emptied queue",
-              gjs_context->idle_drain_handler == 0));
+    g_assert(((void)"GjsContextPrivate::run_jobs() should have emptied queue",
+              gjs->m_idle_drain_handler == 0));
     return G_SOURCE_REMOVE;
 }
 
 /* See engine.cpp and JS::SetEnqueuePromiseJobCallback(). */
-bool
-_gjs_context_enqueue_job(GjsContext      *gjs_context,
-                         JS::HandleObject job)
-{
-    if (gjs_context->idle_drain_handler)
-        g_assert(gjs_context->job_queue->length() > 0);
+bool GjsContextPrivate::enqueue_job(JS::HandleObject job) {
+    if (m_idle_drain_handler)
+        g_assert(m_job_queue->length() > 0);
     else
-        g_assert(gjs_context->job_queue->length() == 0);
+        g_assert(m_job_queue->length() == 0);
 
-    if (!gjs_context->job_queue->append(job)) {
-        auto* cx = static_cast<JSContext*>(
-            gjs_context_get_native_context(gjs_context));
-        JS_ReportOutOfMemory(cx);
+    if (!m_job_queue->append(job)) {
+        JS_ReportOutOfMemory(m_cx);
         return false;
     }
-    if (!gjs_context->idle_drain_handler)
-        gjs_context->idle_drain_handler =
-            g_idle_add_full(G_PRIORITY_DEFAULT, drain_job_queue_idle_handler,
-                            gjs_context, nullptr);
+    if (!m_idle_drain_handler)
+        m_idle_drain_handler = g_idle_add_full(
+            G_PRIORITY_DEFAULT, drain_job_queue_idle_handler, this, nullptr);
 
     return true;
 }
 
-/**
- * _gjs_context_run_jobs:
- * @gjs_context: The #GjsContext instance
+/*
+ * GjsContext::run_jobs:
  *
  * Drains the queue of promise callbacks that the JS engine has reported
  * finished, calling each one and logging any exceptions that it throws.
@@ -753,33 +641,30 @@ _gjs_context_enqueue_job(GjsContext      *gjs_context,
  * Returns: false if one of the jobs threw an uncatchable exception;
  * otherwise true.
  */
-bool
-_gjs_context_run_jobs(GjsContext *gjs_context)
-{
+bool GjsContextPrivate::run_jobs(void) {
     bool retval = true;
-    g_assert(gjs_context->job_queue);
+    g_assert(m_job_queue);
 
-    if (gjs_context->draining_job_queue || gjs_context->should_exit)
+    if (m_draining_job_queue || m_should_exit)
         return true;
 
-    auto cx = static_cast<JSContext *>(gjs_context_get_native_context(gjs_context));
-    JSAutoRequest ar(cx);
+    JSAutoRequest ar(m_cx);
 
-    gjs_context->draining_job_queue = true;  /* Ignore reentrant calls */
+    m_draining_job_queue = true;  // Ignore reentrant calls
 
-    JS::RootedObject job(cx);
+    JS::RootedObject job(m_cx);
     JS::HandleValueArray args(JS::HandleValueArray::empty());
-    JS::RootedValue rval(cx);
+    JS::RootedValue rval(m_cx);
 
     /* Execute jobs in a loop until we've reached the end of the queue.
      * Since executing a job can trigger enqueueing of additional jobs,
      * it's crucial to recheck the queue length during each iteration. */
-    for (size_t ix = 0; ix < gjs_context->job_queue->length(); ix++) {
+    for (size_t ix = 0; ix < m_job_queue->length(); ix++) {
         /* A previous job might have set this flag. e.g., System.exit(). */
-        if (gjs_context->should_exit)
+        if (m_should_exit)
             break;
 
-        job = gjs_context->job_queue->get()[ix];
+        job = m_job_queue->get()[ix];
 
         /* It's possible that job draining was interrupted prematurely,
          * leaving the queue partly processed. In that case, slots for
@@ -788,50 +673,44 @@ _gjs_context_run_jobs(GjsContext *gjs_context)
         if (!job)
             continue;
 
-        gjs_context->job_queue->get()[ix] = nullptr;
+        m_job_queue->get()[ix] = nullptr;
         {
-            JSAutoCompartment ac(cx, job);
-            if (!JS::Call(cx, JS::UndefinedHandleValue, job, args, &rval)) {
+            JSAutoCompartment ac(m_cx, job);
+            if (!JS::Call(m_cx, JS::UndefinedHandleValue, job, args, &rval)) {
                 /* Uncatchable exception - return false so that
                  * System.exit() works in the interactive shell and when
                  * exiting the interpreter. */
-                if (!JS_IsExceptionPending(cx)) {
+                if (!JS_IsExceptionPending(m_cx)) {
                     /* System.exit() is an uncatchable exception, but does not
                      * indicate a bug. Log everything else. */
-                    if (!_gjs_context_should_exit(gjs_context, nullptr))
+                    if (!should_exit(nullptr))
                         g_critical("Promise callback terminated with uncatchable exception");
                     retval = false;
                     continue;
                 }
 
                 /* There's nowhere for the exception to go at this point */
-                gjs_log_exception(cx);
+                gjs_log_exception(m_cx);
             }
         }
     }
 
-    gjs_context->draining_job_queue = false;
-    gjs_context->job_queue->clear();
-    if (gjs_context->idle_drain_handler) {
-        g_source_remove(gjs_context->idle_drain_handler);
-        gjs_context->idle_drain_handler = 0;
+    m_draining_job_queue = false;
+    m_job_queue->clear();
+    if (m_idle_drain_handler) {
+        g_source_remove(m_idle_drain_handler);
+        m_idle_drain_handler = 0;
     }
     return retval;
 }
 
-void
-_gjs_context_register_unhandled_promise_rejection(GjsContext   *gjs_context,
-                                                  uint64_t      id,
-                                                  GjsAutoChar&& stack)
-{
-    gjs_context->unhandled_rejection_stacks[id] = std::move(stack);
+void GjsContextPrivate::register_unhandled_promise_rejection(
+    uint64_t id, GjsAutoChar&& stack) {
+    m_unhandled_rejection_stacks[id] = std::move(stack);
 }
 
-void
-_gjs_context_unregister_unhandled_promise_rejection(GjsContext *gjs_context,
-                                                    uint64_t    id)
-{
-    size_t erased = gjs_context->unhandled_rejection_stacks.erase(id);
+void GjsContextPrivate::unregister_unhandled_promise_rejection(uint64_t id) {
+    size_t erased = m_unhandled_rejection_stacks.erase(id);
     g_assert(((void)"Handler attached to rejected promise that wasn't "
               "previously marked as unhandled", erased == 1));
 }
@@ -860,7 +739,8 @@ _gjs_context_unregister_unhandled_promise_rejection(GjsContext *gjs_context,
 void
 gjs_context_maybe_gc (GjsContext  *context)
 {
-    gjs_maybe_gc(context->context);
+    GjsContextPrivate* gjs = GjsContextPrivate::from_object(context);
+    gjs_maybe_gc(gjs->context());
 }
 
 /**
@@ -873,7 +753,8 @@ gjs_context_maybe_gc (GjsContext  *context)
 void
 gjs_context_gc (GjsContext  *context)
 {
-    JS_GC(context->context);
+    GjsContextPrivate* gjs = GjsContextPrivate::from_object(context);
+    JS_GC(gjs->context());
 }
 
 /**
@@ -908,7 +789,8 @@ void*
 gjs_context_get_native_context (GjsContext *js_context)
 {
     g_return_val_if_fail(GJS_IS_CONTEXT(js_context), NULL);
-    return js_context->context;
+    GjsContextPrivate* gjs = GjsContextPrivate::from_object(js_context);
+    return gjs->context();
 }
 
 bool
@@ -919,38 +801,48 @@ gjs_context_eval(GjsContext   *js_context,
                  int          *exit_status_p,
                  GError      **error)
 {
+    g_return_val_if_fail(GJS_IS_CONTEXT(js_context), false);
+
+    GjsAutoUnref<GjsContext> js_context_ref(js_context, GjsAutoTakeOwnership());
+
+    GjsContextPrivate* gjs = GjsContextPrivate::from_object(js_context);
+    return gjs->eval(script, script_len, filename, exit_status_p, error);
+}
+
+bool GjsContextPrivate::eval(const char* script, ssize_t script_len,
+                             const char* filename, int* exit_status_p,
+                             GError** error) {
     bool ret = false;
 
-    bool auto_profile = js_context->should_profile;
-    if (auto_profile && (_gjs_profiler_is_running(js_context->profiler) ||
-                         js_context->should_listen_sigusr2))
+    bool auto_profile = m_should_profile;
+    if (auto_profile &&
+        (_gjs_profiler_is_running(m_profiler) || m_should_listen_sigusr2))
         auto_profile = false;
 
-    JSAutoCompartment ac(js_context->context, js_context->global);
-    JSAutoRequest ar(js_context->context);
-    GjsAutoUnref<GjsContext> ctx(js_context, GjsAutoTakeOwnership());
+    JSAutoCompartment ac(m_cx, m_global);
+    JSAutoRequest ar(m_cx);
 
     if (auto_profile)
-        gjs_profiler_start(js_context->profiler);
+        gjs_profiler_start(m_profiler);
 
-    JS::RootedValue retval(js_context->context);
-    bool ok = gjs_eval_with_scope(js_context->context, nullptr, script,
-                                  script_len, filename, &retval);
+    JS::RootedValue retval(m_cx);
+    bool ok = gjs_eval_with_scope(m_cx, nullptr, script, script_len, filename,
+                                  &retval);
 
     /* The promise job queue should be drained even on error, to finish
      * outstanding async tasks before the context is torn down. Drain after
      * uncaught exceptions have been reported since draining runs callbacks. */
     {
-        JS::AutoSaveExceptionState saved_exc(js_context->context);
-        ok = _gjs_context_run_jobs(js_context) && ok;
+        JS::AutoSaveExceptionState saved_exc(m_cx);
+        ok = run_jobs() && ok;
     }
 
     if (auto_profile)
-        gjs_profiler_stop(js_context->profiler);
+        gjs_profiler_stop(m_profiler);
 
     if (!ok) {
         uint8_t code;
-        if (_gjs_context_should_exit(js_context, &code)) {
+        if (should_exit(&code)) {
             /* exit_status_p is public API so can't be changed, but should be
              * uint8_t, not int */
             *exit_status_p = code;
@@ -959,7 +851,7 @@ gjs_context_eval(GjsContext   *js_context,
             goto out;  /* Don't log anything */
         }
 
-        if (!JS_IsExceptionPending(js_context->context)) {
+        if (!JS_IsExceptionPending(m_cx)) {
             g_critical("Script %s terminated with an uncatchable exception",
                        filename);
             g_set_error(error, GJS_ERROR, GJS_ERROR_FAILED,
@@ -970,7 +862,7 @@ gjs_context_eval(GjsContext   *js_context,
                         "Script %s threw an exception", filename);
         }
 
-        gjs_log_exception(js_context->context);
+        gjs_log_exception(m_cx);
         /* No exit code from script, but we don't want to exit(0) */
         *exit_status_p = 1;
         goto out;
@@ -991,7 +883,7 @@ gjs_context_eval(GjsContext   *js_context,
     ret = true;
 
  out:
-    context_reset_exit(js_context);
+    reset_exit();
     return ret;
 }
 
@@ -1021,15 +913,17 @@ gjs_context_define_string_array(GjsContext  *js_context,
                                 const char   **array_values,
                                 GError       **error)
 {
-    JSAutoCompartment ac(js_context->context, js_context->global);
-    JSAutoRequest ar(js_context->context);
+    g_return_val_if_fail(GJS_IS_CONTEXT(js_context), false);
+    GjsContextPrivate* gjs = GjsContextPrivate::from_object(js_context);
+
+    JSAutoCompartment ac(gjs->context(), gjs->global());
+    JSAutoRequest ar(gjs->context());
 
-    JS::RootedObject global_root(js_context->context, js_context->global);
-    if (!gjs_define_string_array(js_context->context,
-                                 global_root,
-                                 array_name, array_length, array_values,
+    JS::RootedObject global_root(gjs->context(), gjs->global());
+    if (!gjs_define_string_array(gjs->context(), global_root, array_name,
+                                 array_length, array_values,
                                  JSPROP_READONLY | JSPROP_PERMANENT)) {
-        gjs_log_exception(js_context->context);
+        gjs_log_exception(gjs->context());
         g_set_error(error,
                     GJS_ERROR,
                     GJS_ERROR_FAILED,
@@ -1063,8 +957,7 @@ JS::HandleId
 gjs_context_get_const_string(JSContext      *context,
                              GjsConstString  name)
 {
-    GjsContext *gjs_context = (GjsContext *) JS_GetContextPrivate(context);
-    return *gjs_context->const_strings[name];
+    return GjsContextPrivate::atom(context, name);
 }
 
 /**
@@ -1089,8 +982,7 @@ gjs_context_get_const_string(JSContext      *context,
 JSObject*
 gjs_get_import_global(JSContext *context)
 {
-    GjsContext *gjs_context = (GjsContext *) JS_GetContextPrivate(context);
-    return gjs_context->global;
+    return GjsContextPrivate::from_cx(context)->global();
 }
 
 /**
@@ -1105,7 +997,7 @@ gjs_get_import_global(JSContext *context)
 GjsProfiler *
 gjs_context_get_profiler(GjsContext *self)
 {
-    return self->profiler;
+    return GjsContextPrivate::from_object(self)->profiler();
 }
 
 /**
diff --git a/gjs/debugger.cpp b/gjs/debugger.cpp
index 86f3ad5f..b83e0611 100644
--- a/gjs/debugger.cpp
+++ b/gjs/debugger.cpp
@@ -44,8 +44,8 @@ static bool quit(JSContext* cx, unsigned argc, JS::Value* vp) {
     if (!gjs_parse_call_args(cx, "quit", args, "i", "exitcode", &exitcode))
         return false;
 
-    auto* gjs = static_cast<GjsContext*>(JS_GetContextPrivate(cx));
-    _gjs_context_exit(gjs, exitcode);
+    GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx);
+    gjs->exit(exitcode);
     return false;  // without gjs_throw() == "throw uncatchable exception"
 }
 
diff --git a/gjs/engine.cpp b/gjs/engine.cpp
index 6cfccc4b..0db0942f 100644
--- a/gjs/engine.cpp
+++ b/gjs/engine.cpp
@@ -126,7 +126,7 @@ gjs_finalize_callback(JSFreeOp         *fop,
                       JSFinalizeStatus  status,
                       void             *data)
 {
-    auto js_context = static_cast<GjsContext *>(data);
+    auto* gjs = static_cast<GjsContextPrivate*>(data);
 
   /* Implementation note for mozjs 24:
      sweeping happens in two phases, in the first phase all
@@ -169,9 +169,9 @@ gjs_finalize_callback(JSFreeOp         *fop,
   */
 
   if (status == JSFINALIZE_GROUP_PREPARE)
-        _gjs_context_set_sweeping(js_context, true);
+        gjs->set_sweeping(true);
   else if (status == JSFINALIZE_GROUP_END)
-        _gjs_context_set_sweeping(js_context, false);
+        gjs->set_sweeping(false);
 }
 
 static void
@@ -195,26 +195,25 @@ on_enqueue_promise_job(JSContext       *cx,
                        JS::HandleObject global,
                        void            *data)
 {
-    auto gjs_context = static_cast<GjsContext *>(data);
-    return _gjs_context_enqueue_job(gjs_context, callback);
+    auto* gjs = static_cast<GjsContextPrivate*>(data);
+    return gjs->enqueue_job(callback);
 }
 
 static void on_promise_unhandled_rejection(
     JSContext* cx, JS::HandleObject promise,
     JS::PromiseRejectionHandlingState state, void* data) {
-    auto gjs_context = static_cast<GjsContext *>(data);
+    auto gjs = static_cast<GjsContextPrivate*>(data);
     uint64_t id = JS::GetPromiseID(promise);
 
     if (state == JS::PromiseRejectionHandlingState::Handled) {
         /* This happens when catching an exception from an await expression. */
-        _gjs_context_unregister_unhandled_promise_rejection(gjs_context, id);
+        gjs->unregister_unhandled_promise_rejection(id);
         return;
     }
 
     JS::RootedObject allocation_site(cx, JS::GetPromiseAllocationSite(promise));
     GjsAutoChar stack = gjs_format_stack_trace(cx, allocation_site);
-    _gjs_context_register_unhandled_promise_rejection(gjs_context, id,
-                                                      std::move(stack));
+    gjs->register_unhandled_promise_rejection(id, std::move(stack));
 }
 
 bool gjs_load_internal_source(JSContext* cx, const char* filename,
@@ -298,9 +297,7 @@ public:
 static GjsInit gjs_is_inited;
 #endif
 
-JSContext *
-gjs_create_js_context(GjsContext *js_context)
-{
+JSContext* gjs_create_js_context(GjsContextPrivate* uninitialized_gjs) {
     g_assert(gjs_is_inited);
     JSContext *cx = JS_NewContext(32 * 1024 * 1024 /* max bytes */);
     if (!cx)
@@ -329,16 +326,17 @@ gjs_create_js_context(GjsContext *js_context)
     // JS_SetGCParameter(cx, JSGC_DECOMMIT_THRESHOLD, 32);
 
     /* set ourselves as the private data */
-    JS_SetContextPrivate(cx, js_context);
+    JS_SetContextPrivate(cx, uninitialized_gjs);
 
-    JS_AddFinalizeCallback(cx, gjs_finalize_callback, js_context);
-    JS_SetGCCallback(cx, on_garbage_collect, js_context);
+    JS_AddFinalizeCallback(cx, gjs_finalize_callback, uninitialized_gjs);
+    JS_SetGCCallback(cx, on_garbage_collect, uninitialized_gjs);
     JS_SetLocaleCallbacks(JS_GetRuntime(cx), &gjs_locale_callbacks);
     JS::SetWarningReporter(cx, gjs_warning_reporter);
     JS::SetGetIncumbentGlobalCallback(cx, gjs_get_import_global);
-    JS::SetEnqueuePromiseJobCallback(cx, on_enqueue_promise_job, js_context);
+    JS::SetEnqueuePromiseJobCallback(cx, on_enqueue_promise_job,
+                                     uninitialized_gjs);
     JS::SetPromiseRejectionTrackerCallback(cx, on_promise_unhandled_rejection,
-                                           js_context);
+                                           uninitialized_gjs);
 
     /* We use this to handle "lazy sources" that SpiderMonkey doesn't need to
      * keep in memory. Most sources should be kept in memory, but we can skip
diff --git a/gjs/engine.h b/gjs/engine.h
index fab03d3a..8b14540f 100644
--- a/gjs/engine.h
+++ b/gjs/engine.h
@@ -24,10 +24,10 @@
 #ifndef GJS_ENGINE_H
 #define GJS_ENGINE_H
 
-#include "context.h"
+#include "gjs/context-private.h"
 #include "jsapi-wrapper.h"
 
-JSContext *gjs_create_js_context(GjsContext *js_context);
+JSContext* gjs_create_js_context(GjsContextPrivate* uninitialized_gjs);
 
 bool gjs_load_internal_source(JSContext* cx, const char* filename,
                               JS::UniqueTwoByteChars* src, size_t* length);
diff --git a/gjs/jsapi-util-root.h b/gjs/jsapi-util-root.h
index c81c4e55..427ff0c2 100644
--- a/gjs/jsapi-util-root.h
+++ b/gjs/jsapi-util-root.h
@@ -27,6 +27,7 @@
 #include <glib.h>
 #include <glib-object.h>
 
+#include "gjs/context-private.h"
 #include "gjs/context.h"
 #include "gjs/jsapi-wrapper.h"
 #include "util/log.h"
@@ -157,8 +158,9 @@ private:
         if (!m_has_weakref)
             return;
 
-        auto gjs_cx = static_cast<GjsContext *>(JS_GetContextPrivate(m_cx));
-        g_object_weak_unref(G_OBJECT(gjs_cx), on_context_destroy, this);
+        GjsContextPrivate* gjs = GjsContextPrivate::from_cx(m_cx);
+        g_object_weak_unref(G_OBJECT(gjs->public_context()), on_context_destroy,
+                            this);
         m_has_weakref = false;
     }
 
@@ -263,9 +265,10 @@ public:
         m_thing.root = new JS::PersistentRooted<T>(m_cx, thing);
 
         if (notify) {
-            auto gjs_cx = static_cast<GjsContext *>(JS_GetContextPrivate(m_cx));
-            g_assert(GJS_IS_CONTEXT(gjs_cx));
-            g_object_weak_ref(G_OBJECT(gjs_cx), on_context_destroy, this);
+            GjsContextPrivate* gjs = GjsContextPrivate::from_cx(m_cx);
+            g_assert(GJS_IS_CONTEXT(gjs->public_context()));
+            g_object_weak_ref(G_OBJECT(gjs->public_context()),
+                              on_context_destroy, this);
             m_has_weakref = true;
         }
     }
diff --git a/gjs/jsapi-util.cpp b/gjs/jsapi-util.cpp
index 8db9516b..aea0040a 100644
--- a/gjs/jsapi-util.cpp
+++ b/gjs/jsapi-util.cpp
@@ -718,16 +718,12 @@ gjs_maybe_gc (JSContext *context)
 void
 gjs_schedule_gc_if_needed (JSContext *context)
 {
-    GjsContext *gjs_context;
-
     /* We call JS_MaybeGC immediately, but defer a check for a full
      * GC cycle to an idle handler.
      */
     JS_MaybeGC(context);
 
-    gjs_context = (GjsContext *) JS_GetContextPrivate(context);
-    if (gjs_context)
-        _gjs_context_schedule_gc_if_needed(gjs_context);
+    GjsContextPrivate::from_cx(context)->schedule_gc_if_needed();
 }
 
 /**
diff --git a/modules/console.cpp b/modules/console.cpp
index 8fae56b2..bcfd6d50 100644
--- a/modules/console.cpp
+++ b/modules/console.cpp
@@ -348,8 +348,8 @@ gjs_console_interact(JSContext *context,
         }
         g_string_free(buffer, true);
 
-        auto gjs_context = static_cast<GjsContext *>(JS_GetContextPrivate(context));
-        ok = _gjs_context_run_jobs(gjs_context) && ok;
+        GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context);
+        ok = gjs->run_jobs() && ok;
 
         if (!ok) {
             /* If this was an uncatchable exception, throw another uncatchable
diff --git a/modules/system.cpp b/modules/system.cpp
index ea135a26..dfaa574f 100644
--- a/modules/system.cpp
+++ b/modules/system.cpp
@@ -140,8 +140,8 @@ gjs_exit(JSContext *context,
                              "ecode", &ecode))
         return false;
 
-    GjsContext *gjs_context = static_cast<GjsContext *>(JS_GetContextPrivate(context));
-    _gjs_context_exit(gjs_context, ecode);
+    GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context);
+    gjs->exit(ecode);
     return false;  /* without gjs_throw() == "throw uncatchable exception" */
 }
 
@@ -178,42 +178,20 @@ bool
 gjs_js_define_system_stuff(JSContext              *context,
                            JS::MutableHandleObject module)
 {
-    GjsContext *gjs_context;
-    char *program_name;
-    bool retval;
-
     module.set(JS_NewPlainObject(context));
 
     if (!JS_DefineFunctions(context, module, &module_funcs[0]))
         return false;
 
-    retval = false;
-
-    gjs_context = (GjsContext*) JS_GetContextPrivate(context);
-    g_object_get(gjs_context,
-                 "program-name", &program_name,
-                 NULL);
+    GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context);
+    const char* program_name = gjs->program_name();
 
     JS::RootedValue value(context);
-    if (!gjs_string_from_utf8(context, program_name, &value))
-        goto out;
-
-    /* The name is modeled after program_invocation_name,
-       part of the glibc */
-    if (!JS_DefineProperty(context, module,
-                           "programInvocationName",
-                           value,
-                           GJS_MODULE_PROP_FLAGS | JSPROP_READONLY))
-        goto out;
-
-    if (!JS_DefineProperty(context, module,
-                           "version", GJS_VERSION,
-                           GJS_MODULE_PROP_FLAGS | JSPROP_READONLY))
-        goto out;
-
-    retval = true;
-
- out:
-    g_free(program_name);
-    return retval;
+    return gjs_string_from_utf8(context, program_name, &value) &&
+           /* The name is modeled after program_invocation_name, part of glibc
+            */
+           JS_DefineProperty(context, module, "programInvocationName", value,
+                             GJS_MODULE_PROP_FLAGS | JSPROP_READONLY) &&
+           JS_DefineProperty(context, module, "version", GJS_VERSION,
+                             GJS_MODULE_PROP_FLAGS | JSPROP_READONLY);
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]