[gjs/ewlsh/wrap-es-class: 2/3] gi: Add class wrapping utilities




commit 52a05b09fcab4ba7311b51bdf9049d9f38ad91bd
Author: Evan Welsh <contact evanwelsh com>
Date:   Thu Dec 30 21:33:17 2021 -0800

    gi: Add class wrapping utilities

 gi/private.cpp    | 185 ++++++++++++++++++++++++++++++++++++++++++++++--------
 gi/wrapperutils.h |  71 ++++++++++++++++++++-
 gjs/atoms.h       |   1 +
 3 files changed, 227 insertions(+), 30 deletions(-)
---
diff --git a/gi/private.cpp b/gi/private.cpp
index 4a07afb1..d859f50b 100644
--- a/gi/private.cpp
+++ b/gi/private.cpp
@@ -16,6 +16,7 @@
 #include <js/RootingAPI.h>
 #include <js/TypeDecls.h>
 #include <js/Utility.h>  // for UniqueChars
+#include <js/ValueArray.h>
 #include <jsapi.h>       // for JS_GetElement
 
 #include "gi/gobject.h"
@@ -175,17 +176,30 @@ static bool get_interface_gtypes(JSContext* cx, JS::HandleObject interfaces,
 }
 
 GJS_JSAPI_RETURN_CONVENTION
-static bool gjs_register_interface(JSContext* cx, unsigned argc,
-                                   JS::Value* vp) {
-    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+static bool create_wrapper_array(JSContext* cx, JS::HandleObject prototype,
+                                 GType type, JS::MutableHandleValue rval) {
+    JS::RootedObject gtype_wrapper(cx,
+                                   gjs_gtype_create_gtype_wrapper(cx, type));
+    if (!gtype_wrapper)
+        return false;
 
-    JS::UniqueChars name;
-    JS::RootedObject interfaces(cx), properties(cx);
-    if (!gjs_parse_call_args(cx, "register_interface", args, "soo", "name",
-                             &name, "interfaces", &interfaces, "properties",
-                             &properties))
+    JS::RootedValueArray<2> tuple(cx);
+    tuple[0].setObject(*prototype);
+    tuple[1].setObject(*gtype_wrapper);
+
+    JS::RootedObject array(cx, JS::NewArrayObject(cx, tuple));
+    if (!array)
         return false;
 
+    rval.setObject(*array);
+    return true;
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_register_interface_impl(JSContext* cx, const char* name,
+                                        JS::HandleObject interfaces,
+                                        JS::HandleObject properties,
+                                        GType* gtype) {
     uint32_t n_interfaces, n_properties;
     if (!validate_interfaces_and_properties_args(cx, interfaces, properties,
                                                  &n_interfaces, &n_properties))
@@ -198,13 +212,13 @@ static bool gjs_register_interface(JSContext* cx, unsigned argc,
     if (!get_interface_gtypes(cx, interfaces, n_interfaces, iface_types))
         return false;
 
-    if (g_type_from_name(name.get()) != G_TYPE_INVALID) {
-        gjs_throw(cx, "Type name %s is already registered", name.get());
+    if (g_type_from_name(name) != G_TYPE_INVALID) {
+        gjs_throw(cx, "Type name %s is already registered", name);
         return false;
     }
 
     GTypeInfo type_info = gjs_gobject_interface_info;
-    GType interface_type = g_type_register_static(G_TYPE_INTERFACE, name.get(),
+    GType interface_type = g_type_register_static(G_TYPE_INTERFACE, name,
                                                   &type_info, GTypeFlags(0));
 
     g_type_set_qdata(interface_type, ObjectBase::custom_type_quark(),
@@ -217,6 +231,27 @@ static bool gjs_register_interface(JSContext* cx, unsigned argc,
     for (uint32_t ix = 0; ix < n_interfaces; ix++)
         g_type_interface_add_prerequisite(interface_type, iface_types[ix]);
 
+    *gtype = interface_type;
+    return true;
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_register_interface(JSContext* cx, unsigned argc,
+                                   JS::Value* vp) {
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+
+    JS::UniqueChars name;
+    JS::RootedObject interfaces(cx), properties(cx);
+    if (!gjs_parse_call_args(cx, "register_interface", args, "soo", "name",
+                             &name, "interfaces", &interfaces, "properties",
+                             &properties))
+        return false;
+
+    GType interface_type;
+    if (!gjs_register_interface_impl(cx, name.get(), interfaces, properties,
+                                     &interface_type))
+        return false;
+
     /* create a custom JSClass */
     JS::RootedObject module(cx, gjs_lookup_private_namespace(cx));
     if (!module)
@@ -231,6 +266,36 @@ static bool gjs_register_interface(JSContext* cx, unsigned argc,
     return true;
 }
 
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_register_interface_with_class(JSContext* cx, unsigned argc,
+                                              JS::Value* vp) {
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+
+    JS::UniqueChars name;
+    JS::RootedObject klass(cx), interfaces(cx), properties(cx);
+    if (!gjs_parse_call_args(cx, "register_interface_with_class", args, "osoo",
+                             "class", &klass, "name", &name, "interfaces",
+                             &interfaces, "properties", &properties))
+        return false;
+
+    GType interface_type;
+    if (!gjs_register_interface_impl(cx, name.get(), interfaces, properties,
+                                     &interface_type))
+        return false;
+
+    /* create a custom JSClass */
+    JS::RootedObject module(cx, gjs_lookup_private_namespace(cx));
+    if (!module)
+        return false;  // error will have been thrown already
+
+    JS::RootedObject prototype(cx);
+    if (!InterfacePrototype::wrap_class(cx, module, nullptr, interface_type,
+                                        klass, &prototype))
+        return false;
+
+    return create_wrapper_array(cx, prototype, interface_type, args.rval());
+}
+
 static inline void gjs_add_interface(GType instance_type,
                                      GType interface_type) {
     static GInterfaceInfo interface_vtable{nullptr, nullptr, nullptr};
@@ -239,18 +304,13 @@ static inline void gjs_add_interface(GType instance_type,
 }
 
 GJS_JSAPI_RETURN_CONVENTION
-static bool gjs_register_type(JSContext* cx, unsigned argc, JS::Value* vp) {
-    JS::CallArgs argv = JS::CallArgsFromVp(argc, vp);
-
-    JS::UniqueChars name;
-    GTypeFlags type_flags;
-    JS::RootedObject parent(cx), interfaces(cx), properties(cx);
-    if (!gjs_parse_call_args(cx, "register_type", argv, "osioo", "parent",
-                             &parent, "name", &name, "flags", &type_flags,
-                             "interfaces", &interfaces,
-                             "properties", &properties))
-        return false;
-
+static bool gjs_register_type_impl(JSContext* cx, const char* name,
+                                   GTypeFlags type_flags,
+                                   JS::HandleObject parent,
+                                   JS::HandleObject interfaces,
+                                   JS::HandleObject properties,
+                                   GType** iface_types_out,
+                                   uint32_t* n_interfaces_out, GType* gtype) {
     if (!parent)
         return false;
 
@@ -272,8 +332,8 @@ static bool gjs_register_type(JSContext* cx, unsigned argc, JS::Value* vp) {
     if (!get_interface_gtypes(cx, interfaces, n_interfaces, iface_types))
         return false;
 
-    if (g_type_from_name(name.get()) != G_TYPE_INVALID) {
-        gjs_throw(cx, "Type name %s is already registered", name.get());
+    if (g_type_from_name(name) != G_TYPE_INVALID) {
+        gjs_throw(cx, "Type name %s is already registered", name);
         return false;
     }
 
@@ -292,8 +352,8 @@ static bool gjs_register_type(JSContext* cx, unsigned argc, JS::Value* vp) {
     type_info.class_size = query.class_size;
     type_info.instance_size = query.instance_size;
 
-    GType instance_type = g_type_register_static(
-        parent_priv->gtype(), name.get(), &type_info, type_flags);
+    GType instance_type = g_type_register_static(parent_priv->gtype(), name,
+                                                 &type_info, type_flags);
 
     g_type_set_qdata(instance_type, ObjectBase::custom_type_quark(),
                      GINT_TO_POINTER(1));
@@ -305,6 +365,33 @@ static bool gjs_register_type(JSContext* cx, unsigned argc, JS::Value* vp) {
     for (uint32_t ix = 0; ix < n_interfaces; ix++)
         gjs_add_interface(instance_type, iface_types[ix]);
 
+    *gtype = instance_type;
+    *n_interfaces_out = n_interfaces;
+    *iface_types_out = iface_types.release();
+    return true;
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_register_type(JSContext* cx, unsigned argc, JS::Value* vp) {
+    JS::CallArgs argv = JS::CallArgsFromVp(argc, vp);
+
+    JS::UniqueChars name;
+    GTypeFlags type_flags;
+    JS::RootedObject parent(cx), interfaces(cx), properties(cx);
+    if (!gjs_parse_call_args(cx, "register_type", argv, "osioo", "parent",
+                             &parent, "name", &name, "flags", &type_flags,
+                             "interfaces", &interfaces, "properties",
+                             &properties))
+        return false;
+
+    GType instance_type;
+    GjsAutoPointer<GType> iface_types;
+    uint32_t n_interfaces;
+    if (!gjs_register_type_impl(cx, name.get(), type_flags, parent, interfaces,
+                                properties, iface_types.out(), &n_interfaces,
+                                &instance_type))
+        return false;
+
     /* create a custom JSClass */
     JS::RootedObject module(cx, gjs_lookup_private_namespace(cx));
     JS::RootedObject constructor(cx), prototype(cx);
@@ -321,6 +408,44 @@ static bool gjs_register_type(JSContext* cx, unsigned argc, JS::Value* vp) {
     return true;
 }
 
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_register_type_with_class(JSContext* cx, unsigned argc,
+                                         JS::Value* vp) {
+    JS::CallArgs argv = JS::CallArgsFromVp(argc, vp);
+
+    JS::UniqueChars name;
+    GTypeFlags type_flags;
+    JS::RootedObject klass(cx), parent(cx), interfaces(cx), properties(cx);
+    if (!gjs_parse_call_args(cx, "register_type_with_class", argv, "oosioo",
+                             "class", &klass, "parent", &parent, "name", &name,
+                             "flags", &type_flags, "interfaces", &interfaces,
+                             "properties", &properties))
+        return false;
+
+    GType instance_type;
+    uint32_t n_interfaces;
+    GjsAutoPointer<GType> iface_types;
+    if (!gjs_register_type_impl(cx, name.get(), type_flags, parent, interfaces,
+                                properties, iface_types.out(), &n_interfaces,
+                                &instance_type))
+        return false;
+
+    /* create a custom JSClass */
+    JS::RootedObject module(cx, gjs_lookup_private_namespace(cx));
+    JS::RootedObject prototype(cx);
+
+    auto* priv = ObjectPrototype::wrap_class(cx, module, nullptr, instance_type,
+                                             klass, &prototype);
+
+    if (!priv)
+        return false;
+
+    priv->set_interfaces(iface_types, n_interfaces);
+    priv->set_type_qdata();
+
+    return create_wrapper_array(cx, prototype, instance_type, argv.rval());
+}
+
 GJS_JSAPI_RETURN_CONVENTION
 static bool gjs_signal_new(JSContext* cx, unsigned argc, JS::Value* vp) {
     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
@@ -407,12 +532,18 @@ static JSFunctionSpec private_module_funcs[] = {
     JS_FN("override_property", gjs_override_property, 2, GJS_MODULE_PROP_FLAGS),
     JS_FN("register_interface", gjs_register_interface, 3,
           GJS_MODULE_PROP_FLAGS),
+    JS_FN("register_interface_with_class", gjs_register_interface_with_class, 4,
+          GJS_MODULE_PROP_FLAGS),
     JS_FN("register_type", gjs_register_type, 4, GJS_MODULE_PROP_FLAGS),
+    JS_FN("register_type_with_class", gjs_register_type_with_class, 5,
+          GJS_MODULE_PROP_FLAGS),
     JS_FN("signal_new", gjs_signal_new, 6, GJS_MODULE_PROP_FLAGS),
     JS_FS_END,
 };
 
 static JSPropertySpec private_module_props[] = {
+    JS_PSG("gobject_prototype_symbol",
+           symbol_getter<&GjsAtoms::gobject_prototype>, GJS_MODULE_PROP_FLAGS),
     JS_PSG("hook_up_vfunc_symbol", symbol_getter<&GjsAtoms::hook_up_vfunc>,
            GJS_MODULE_PROP_FLAGS),
     JS_PSG("signal_find_symbol", symbol_getter<&GjsAtoms::signal_find>,
diff --git a/gi/wrapperutils.h b/gi/wrapperutils.h
index 1a20b1b5..cbfb1884 100644
--- a/gi/wrapperutils.h
+++ b/gi/wrapperutils.h
@@ -303,12 +303,38 @@ class GIWrapperBase : public CWrapperPointerOps<Base> {
      */
     [[nodiscard]] static Prototype* resolve_prototype(JSContext* cx,
                                                       JS::HandleObject proto) {
-        if (JS_GetClass(proto) != &Base::klass) {
-            gjs_throw(cx, "Tried to construct an object without a GType");
+        if (JS_GetClass(proto) == &Base::klass) {
+            return Prototype::for_js(cx, proto);
+        }
+
+        const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
+
+        bool has_property = false;
+        if (!JS_HasOwnPropertyById(cx, proto, atoms.gobject_prototype(),
+                                   &has_property))
+            return nullptr;
+
+        if (!has_property) {
+            gjs_throw(cx, "Tried to construct an object without a GType!");
+            return nullptr;
+        }
+
+        JS::RootedValue gobject_proto(cx);
+        if (!JS_GetPropertyById(cx, proto, atoms.gobject_prototype(),
+                                &gobject_proto))
+            return nullptr;
+
+        if (!gobject_proto.isObject()) {
+            gjs_throw(cx, "Tried to construct an object without a GType!");
             return nullptr;
         }
 
-        return Prototype::for_js(cx, proto);
+        JS::RootedObject obj(cx, &gobject_proto.toObject());
+        // gobject_prototype is an internal symbol so we can assert that it is
+        // only assigned to objects with &Base::klass definitions
+        g_assert(JS_GetClass(obj) == &Base::klass);
+
+        return Prototype::for_js(cx, obj);
     }
 
     /*
@@ -897,6 +923,45 @@ class GIWrapperPrototype : public Base {
         return proto;
     }
 
+    GJS_JSAPI_RETURN_CONVENTION
+    static Prototype* wrap_class(JSContext* cx, JS::HandleObject in_object,
+                                 Info* info, GType gtype,
+                                 JS::HandleObject constructor,
+                                 JS::MutableHandleObject prototype) {
+        g_assert(in_object);
+
+        GjsAutoPrototype priv = create_prototype(info, gtype);
+        if (!priv->init(cx))
+            return nullptr;
+
+        JS::RootedObject parent_proto(cx);
+        if (!priv->get_parent_proto(cx, &parent_proto))
+            return nullptr;
+
+        if (parent_proto) {
+            prototype.set(
+                JS_NewObjectWithGivenProto(cx, &Base::klass, parent_proto));
+        } else {
+            prototype.set(JS_NewObject(cx, &Base::klass));
+        }
+
+        if (!prototype)
+            return nullptr;
+
+        Prototype* proto = priv.release();
+        JS_SetPrivate(prototype, proto);
+
+        if (!proto->define_static_methods(cx, constructor))
+            return nullptr;
+
+        GjsAutoChar class_name = g_strdup_printf("%s", proto->name());
+        if (!JS_DefineProperty(cx, in_object, class_name, constructor,
+                               GJS_MODULE_PROP_FLAGS))
+            return nullptr;
+
+        return proto;
+    }
+
     // Methods to get an existing Prototype
 
     /*
diff --git a/gjs/atoms.h b/gjs/atoms.h
index 2c6e7c6b..1e0b72fc 100644
--- a/gjs/atoms.h
+++ b/gjs/atoms.h
@@ -75,6 +75,7 @@ class JSTracer;
     macro(zone, "zone")
 
 #define FOR_EACH_SYMBOL_ATOM(macro) \
+    macro(gobject_prototype, "__GObject__prototype") \
     macro(hook_up_vfunc, "__GObject__hook_up_vfunc") \
     macro(private_ns_marker, "__gjsPrivateNS") \
     macro(signal_find, "__GObject__signal_find") \


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