[gjs/ewlsh/register-type: 3/5] gi: Add class wrapping utilities
- From: Evan Welsh <ewlsh src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gjs/ewlsh/register-type: 3/5] gi: Add class wrapping utilities
- Date: Fri, 31 Dec 2021 05:59:11 +0000 (UTC)
commit a950ecfa3a7e93eb15f24b564245d7b4e8fedd3c
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 | 72 ++++++++++++++++++++-
gjs/atoms.h | 1 +
3 files changed, 230 insertions(+), 28 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 ddb8df65..cbfb1884 100644
--- a/gi/wrapperutils.h
+++ b/gi/wrapperutils.h
@@ -303,7 +303,38 @@ class GIWrapperBase : public CWrapperPointerOps<Base> {
*/
[[nodiscard]] static Prototype* resolve_prototype(JSContext* cx,
JS::HandleObject proto) {
- return Prototype::for_js(cx, proto);
+ 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;
+ }
+
+ 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);
}
/*
@@ -892,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]