[gjs/burninate-macros: 4/13] jsapi: Add new system for creating wrapper objects for native types



commit 9653f45d25b17d216569e28859ffb3c82361f003
Author: Philip Chimento <philip chimento gmail com>
Date:   Tue May 5 22:31:07 2020 -0700

    jsapi: Add new system for creating wrapper objects for native types
    
    FIXME: Move this into wrapperutils.h
    FIXME: Change global slot template parameter into static constexpr member

 gjs/jsapi-class.h | 252 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 250 insertions(+), 2 deletions(-)
---
diff --git a/gjs/jsapi-class.h b/gjs/jsapi-class.h
index 17ab8fbb..5c8240b4 100644
--- a/gjs/jsapi-class.h
+++ b/gjs/jsapi-class.h
@@ -31,10 +31,11 @@
 
 #include <js/TypeDecls.h>
 
-#include "gi/wrapperutils.h"  // IWYU pragma: keep
-#include "gjs/global.h"       // IWYU pragma: keep
+#include "gi/wrapperutils.h"
+#include "gjs/global.h"
 #include "gjs/jsapi-util.h"
 #include "gjs/macros.h"
+#include "util/log.h"
 
 GJS_JSAPI_RETURN_CONVENTION
 bool gjs_init_class_dynamic(
@@ -323,6 +324,253 @@ GJS_DEFINE_PROTO_FUNCS_WITH_PARENT(cname, no_parent)
         return false;                                           \
     }
 
+template <class Base, typename Wrapped, GjsGlobalSlot SLOT>
+class NativeObject {
+    GJS_USE
+    static bool typecheck(JSContext* cx, JS::HandleObject obj,
+                          JS::CallArgs* args = nullptr) {
+        return JS_InstanceOf(cx, obj, &Base::klass, args);
+    }
+
+    GJS_USE
+    static Wrapped* for_js_nocheck(JSObject* obj) {
+        return static_cast<Wrapped*>(JS_GetPrivate(obj));
+    }
+
+    // Default implementation for classes with no constructor
+    // Can remove once 'if constexpr' can be used in create_prototype()
+    GJS_JSAPI_RETURN_CONVENTION
+    static Wrapped* constructor_impl(JSContext* cx, const JS::CallArgs& args) {
+        gjs_throw_abstract_constructor_error(cx, args);
+        return nullptr;
+    }
+
+    GJS_JSAPI_RETURN_CONVENTION
+    static bool constructor(JSContext* cx, unsigned argc, JS::Value* vp) {
+        JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+
+        if (!args.isConstructing()) {
+            gjs_throw_constructor_error(cx);
+            return false;
+        }
+        JS::RootedObject object(
+            cx, JS_NewObjectForConstructor(cx, &Base::klass, args));
+        if (!object)
+            return false;
+
+        Wrapped* priv = Base::constructor_impl(cx, args);
+        if (!priv)
+            return false;
+        JS_SetPrivate(object, priv);
+
+        args.rval().setObject(*object);
+        return true;
+    }
+
+    GJS_JSAPI_RETURN_CONVENTION
+    static bool abstract_constructor(JSContext* cx, unsigned argc,
+                                     JS::Value* vp) {
+        JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+        gjs_throw_abstract_constructor_error(cx, args);
+        return false;
+    }
+
+ protected:
+    // Debug methods
+
+    static void debug_lifecycle(
+        const void* wrapped_ptr GJS_USED_VERBOSE_LIFECYCLE,
+        const void* obj GJS_USED_VERBOSE_LIFECYCLE,
+        const char* message GJS_USED_VERBOSE_LIFECYCLE) {
+        gjs_debug_lifecycle(Base::debug_topic, "[%p: JS wrapper %p] %s",
+                            wrapped_ptr, obj, message);
+    }
+
+    static void finalize(JSFreeOp* fop, JSObject* obj) {
+        Wrapped* priv = Base::for_js_nocheck(obj);
+
+        // Call only GIWrapperBase's original method here, not any overrides;
+        // e.g., we don't want to deal with a read barrier in ObjectInstance.
+        NativeObject::debug_lifecycle(priv, obj, "Finalize");
+
+        Base::finalize_impl(fop, priv);
+
+        // Remove the pointer from the JSObject
+        JS_SetPrivate(obj, nullptr);
+    }
+
+    static constexpr JSClassOps class_ops = {
+        nullptr,  // addProperty
+        nullptr,  // deleteProperty
+        nullptr,  // enumerate
+        nullptr,  // newEnumerate
+        nullptr,  // resolve
+        nullptr,  // mayResolve
+        &NativeObject::finalize,
+    };
+
+    GJS_JSAPI_RETURN_CONVENTION
+    static bool define_gtype_prop(JSContext* cx, JS::HandleObject ctor,
+                                  JS::HandleObject proto G_GNUC_UNUSED) {
+        return gjs_wrapper_define_gtype_prop(cx, ctor, Base::gtype());
+    }
+
+    GJS_JSAPI_RETURN_CONVENTION
+    static JSObject* prototype(JSContext* cx) {
+        JS::RootedValue v_proto(cx, gjs_get_global_slot(cx, SLOT));
+        g_assert(!v_proto.isUndefined() &&
+                 "create_prototype() must be called before prototype()");
+        g_assert(v_proto.isObject() &&
+                 "Someone stored some weird value in a global slot");
+        return &v_proto.toObject();
+    }
+
+    GJS_JSAPI_RETURN_CONVENTION
+    static bool resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
+                        bool* resolved) {
+        Wrapped* priv = for_js(cx, obj);
+        g_assert(priv && "resolve called on wrong object");
+        return priv->resolve_impl(cx, obj, id, resolved);
+    }
+
+    GJS_JSAPI_RETURN_CONVENTION
+    static bool new_enumerate(JSContext* cx, JS::HandleObject obj,
+                              JS::MutableHandleIdVector properties,
+                              bool only_enumerable) {
+        Wrapped* priv = for_js(cx, obj);
+        g_assert(priv && "enumerate called on wrong object");
+        return priv->new_enumerate_impl(cx, obj, properties, only_enumerable);
+    }
+
+ public:
+    GJS_USE
+    static Wrapped* for_js(JSContext* cx, JS::HandleObject obj,
+                           JS::CallArgs& args) {
+        return static_cast<Wrapped*>(
+            JS_GetInstancePrivate(cx, obj, &Base::klass, &args));
+    }
+
+    GJS_USE
+    static Wrapped* for_js(JSContext* cx, JS::HandleObject obj) {
+        return static_cast<Wrapped*>(
+            JS_GetInstancePrivate(cx, obj, &Base::klass, nullptr));
+    }
+
+    GJS_JSAPI_RETURN_CONVENTION
+    static bool for_js_typecheck(JSContext* cx, JS::HandleObject obj,
+                                 Wrapped** out, JS::CallArgs* args = nullptr) {
+        if (!typecheck(cx, obj, args)) {
+            if (!args) {
+                const JSClass* obj_class = JS_GetClass(obj);
+                gjs_throw_custom(cx, JSProto_TypeError, nullptr,
+                                 "Object %p is not a subclass of %s, it's a %s",
+                                 obj.get(), Base::klass.name, obj_class->name);
+            }
+            return false;
+        }
+
+        *out = for_js_nocheck(obj);
+        return true;
+    }
+
+    GJS_JSAPI_RETURN_CONVENTION
+    static JSObject* create_prototype(JSContext* cx,
+                                      JS::HandleObject module = nullptr) {
+        // If we've been here more than once, we already have the proto
+        JS::RootedValue v_proto(cx, gjs_get_global_slot(cx, SLOT));
+        if (!v_proto.isUndefined()) {
+            g_assert(v_proto.isObject() &&
+                     "Someone stored some weird value in a global slot");
+            return &v_proto.toObject();
+        }
+
+        // Create the prototype. If no createPrototype function is provided,
+        // then the default is to create a plain object as the prototype.
+        JS::RootedObject proto(cx);
+        if (Base::class_spec.createPrototype)
+            proto = Base::class_spec.createPrototype(
+                cx, JSProto_Object /* FIXME */);
+        else
+            proto = JS_NewPlainObject(cx);
+        if (!proto ||
+            (Base::class_spec.prototypeProperties &&
+             !JS_DefineProperties(cx, proto,
+                                  Base::class_spec.prototypeProperties)) ||
+            (Base::class_spec.prototypeFunctions &&
+             !JS_DefineFunctions(cx, proto,
+                                 Base::class_spec.prototypeFunctions)))
+            return nullptr;
+        gjs_set_global_slot(cx, SLOT, JS::ObjectValue(*proto));
+
+        // Create the constructor. If no createConstructor function is provided,
+        // then the default is to call NativeObject::constructor() which calls
+        // Base::constructor_impl().
+        JS::RootedObject ctor_obj(cx);
+        if (!(Base::class_spec.flags & js::ClassSpec::DontDefineConstructor)) {
+            if (Base::class_spec.defined()) {
+                ctor_obj =
+                    Base::class_spec.createConstructor(cx, JSProto_Object);
+            } else {
+                JSFunction* ctor =
+                    JS_NewFunction(cx, &Base::constructor, 0 /* FIXME */,
+                                   JSFUN_CONSTRUCTOR, Base::klass.name);
+                ctor_obj = JS_GetFunctionObject(ctor);
+            }
+            if (!ctor_obj ||
+                (Base::class_spec.constructorProperties &&
+                 !JS_DefineProperties(
+                     cx, ctor_obj, Base::class_spec.constructorProperties)) ||
+                (Base::class_spec.constructorFunctions &&
+                 !JS_DefineFunctions(cx, ctor_obj,
+                                     Base::class_spec.constructorFunctions)) ||
+                !JS_LinkConstructorAndPrototype(cx, ctor_obj, proto))
+                return nullptr;
+        }
+
+        if (Base::class_spec.finishInit) {
+            if (!Base::class_spec.finishInit(cx, ctor_obj, proto))
+                return nullptr;
+        }
+
+        // If module is not given, we are defining a global class
+        JS::RootedObject in_obj(cx, module);
+        if (!in_obj)
+            in_obj = gjs_get_import_global(cx);
+        // FIXME: JS_InitClass defined the prototype as the constructor if no
+        // constructor was given
+        if (!ctor_obj)
+            ctor_obj = proto;
+        JS::RootedId class_name(cx,
+                                gjs_intern_string_to_id(cx, Base::klass.name));
+        if (class_name == JSID_VOID ||
+            !JS_DefinePropertyById(cx, in_obj, class_name, ctor_obj,
+                                   GJS_MODULE_PROP_FLAGS))
+            return nullptr;
+
+        gjs_debug(GJS_DEBUG_CONTEXT, "Initialized class %s prototype %p",
+                  Base::klass.name, proto.get());
+        return proto;
+    }
+
+    GJS_JSAPI_RETURN_CONVENTION
+    static JSObject* from_c_ptr(JSContext* cx, Wrapped* ptr) {
+        JS::RootedObject proto(cx, Base::prototype(cx));
+        if (!proto)
+            return nullptr;
+
+        JS::RootedObject wrapper(
+            cx, JS_NewObjectWithGivenProto(cx, &Base::klass, proto));
+        if (!wrapper)
+            return nullptr;
+
+        JS_SetPrivate(wrapper, Base::copy_ptr(ptr));
+        return wrapper;
+    }
+};
+
+template <typename Base, typename Wrapped, GjsGlobalSlot SLOT>
+const JSClassOps NativeObject<Base, Wrapped, SLOT>::class_ops;
+
 GJS_USE
 JS::Value gjs_dynamic_property_private_slot(JSObject *accessor_obj);
 


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