[gjs/ewlsh/register-type: 2/2] Support class fields
- From: Evan Welsh <ewlsh src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gjs/ewlsh/register-type: 2/2] Support class fields
- Date: Thu, 30 Dec 2021 20:05:42 +0000 (UTC)
commit f452408ae1ca8915e9a8da4a681ca2283ab1b8db
Author: Evan Welsh <contact evanwelsh com>
Date: Thu Dec 30 12:05:09 2021 -0800
Support class fields
gi/boxed.cpp | 4 +-
gi/boxed.h | 2 +-
gi/fundamental.cpp | 5 +-
gi/fundamental.h | 3 +-
gi/gerror.cpp | 4 +-
gi/gerror.h | 2 +-
gi/interface.cpp | 100 +++++++-
gi/interface.h | 10 +-
gi/object.cpp | 30 ++-
gi/object.h | 3 +-
gi/private.cpp | 189 ++++++++++++---
gi/union.cpp | 4 +-
gi/union.h | 1 +
gi/wrapperutils.h | 135 +++++++++--
gjs/atoms.h | 1 +
installed-tests/js/testGObjectClass.js | 58 +++++
modules/core/_common.js | 16 +-
modules/core/overrides/GObject.js | 412 +++++++++++++++++++++++++--------
modules/core/overrides/Gtk.js | 157 ++++++++++---
modules/script/_legacy.js | 113 ++++-----
20 files changed, 989 insertions(+), 260 deletions(-)
---
diff --git a/gi/boxed.cpp b/gi/boxed.cpp
index 531544df..92af1366 100644
--- a/gi/boxed.cpp
+++ b/gi/boxed.cpp
@@ -39,8 +39,8 @@
#include "gjs/mem-private.h"
#include "util/log.h"
-BoxedInstance::BoxedInstance(JSContext* cx, JS::HandleObject obj)
- : GIWrapperInstance(cx, obj),
+BoxedInstance::BoxedInstance(BoxedPrototype* prototype, JS::HandleObject obj)
+ : GIWrapperInstance(prototype, obj),
m_allocated_directly(false),
m_owning_ptr(false) {
GJS_INC_COUNTER(boxed_instance);
diff --git a/gi/boxed.h b/gi/boxed.h
index fc053a6f..29a0d9d6 100644
--- a/gi/boxed.h
+++ b/gi/boxed.h
@@ -164,7 +164,7 @@ class BoxedInstance
bool m_owning_ptr : 1; // if set, the JS wrapper owns the C memory referred
// to by m_ptr.
- explicit BoxedInstance(JSContext* cx, JS::HandleObject obj);
+ explicit BoxedInstance(BoxedPrototype* prototype, JS::HandleObject obj);
~BoxedInstance(void);
// Don't set GIWrapperBase::m_ptr directly. Instead, use one of these
diff --git a/gi/fundamental.cpp b/gi/fundamental.cpp
index 72b69259..b776fdc1 100644
--- a/gi/fundamental.cpp
+++ b/gi/fundamental.cpp
@@ -33,8 +33,9 @@ namespace JS {
class CallArgs;
}
-FundamentalInstance::FundamentalInstance(JSContext* cx, JS::HandleObject obj)
- : GIWrapperInstance(cx, obj) {
+FundamentalInstance::FundamentalInstance(FundamentalPrototype* prototype,
+ JS::HandleObject obj)
+ : GIWrapperInstance(prototype, obj) {
GJS_INC_COUNTER(fundamental_instance);
}
diff --git a/gi/fundamental.h b/gi/fundamental.h
index 7d2f8de0..3d7caef6 100644
--- a/gi/fundamental.h
+++ b/gi/fundamental.h
@@ -144,7 +144,8 @@ class FundamentalInstance
friend class GIWrapperBase<FundamentalBase, FundamentalPrototype,
FundamentalInstance>;
- explicit FundamentalInstance(JSContext* cx, JS::HandleObject obj);
+ explicit FundamentalInstance(FundamentalPrototype* prototype,
+ JS::HandleObject obj);
~FundamentalInstance(void);
// Helper methods
diff --git a/gi/gerror.cpp b/gi/gerror.cpp
index 2e62cddb..737de4ce 100644
--- a/gi/gerror.cpp
+++ b/gi/gerror.cpp
@@ -43,8 +43,8 @@ ErrorPrototype::ErrorPrototype(GIEnumInfo* info, GType gtype)
ErrorPrototype::~ErrorPrototype(void) { GJS_DEC_COUNTER(gerror_prototype); }
-ErrorInstance::ErrorInstance(JSContext* cx, JS::HandleObject obj)
- : GIWrapperInstance(cx, obj) {
+ErrorInstance::ErrorInstance(ErrorPrototype* prototype, JS::HandleObject obj)
+ : GIWrapperInstance(prototype, obj) {
GJS_INC_COUNTER(gerror_instance);
}
diff --git a/gi/gerror.h b/gi/gerror.h
index 8841a994..c2224889 100644
--- a/gi/gerror.h
+++ b/gi/gerror.h
@@ -128,7 +128,7 @@ class ErrorInstance : public GIWrapperInstance<ErrorBase, ErrorPrototype,
GError>;
friend class GIWrapperBase<ErrorBase, ErrorPrototype, ErrorInstance>;
- explicit ErrorInstance(JSContext* cx, JS::HandleObject obj);
+ explicit ErrorInstance(ErrorPrototype* prototype, JS::HandleObject obj);
~ErrorInstance(void);
public:
diff --git a/gi/interface.cpp b/gi/interface.cpp
index 925097e8..c66a6199 100644
--- a/gi/interface.cpp
+++ b/gi/interface.cpp
@@ -8,8 +8,12 @@
#include <girepository.h>
#include <js/Class.h>
+#include <js/Id.h> // for JSID_VOID, PropertyKey, jsid
#include <js/TypeDecls.h>
#include <js/Utility.h> // for UniqueChars
+#include <jsapi.h> // for JS_ReportOutOfMemory
+
+#include <utility> // for forward
#include "gi/function.h"
#include "gi/interface.h"
@@ -31,6 +35,100 @@ InterfacePrototype::~InterfacePrototype(void) {
GJS_DEC_COUNTER(interface);
}
+bool InterfacePrototype::new_enumerate_impl(
+ JSContext* cx, JS::HandleObject obj [[maybe_unused]],
+ JS::MutableHandleIdVector properties,
+ bool only_enumerable [[maybe_unused]]) {
+ unsigned n_interfaces;
+ GType* interfaces = g_type_interfaces(gtype(), &n_interfaces);
+
+ for (unsigned k = 0; k < n_interfaces; k++) {
+ GjsAutoInterfaceInfo iface_info =
+ g_irepository_find_by_gtype(nullptr, interfaces[k]);
+
+ if (!iface_info) {
+ continue;
+ }
+
+ int n_methods = g_interface_info_get_n_methods(iface_info);
+ int n_properties = g_interface_info_get_n_properties(iface_info);
+ if (!properties.reserve(properties.length() + n_methods +
+ n_properties)) {
+ JS_ReportOutOfMemory(cx);
+ return false;
+ }
+
+ // Methods
+ for (int i = 0; i < n_methods; i++) {
+ GjsAutoFunctionInfo meth_info =
+ g_interface_info_get_method(iface_info, i);
+ GIFunctionInfoFlags flags = g_function_info_get_flags(meth_info);
+
+ if (flags & GI_FUNCTION_IS_METHOD) {
+ const char* name = meth_info.name();
+ jsid id = gjs_intern_string_to_id(cx, name);
+ if (id == JSID_VOID)
+ return false;
+ properties.infallibleAppend(id);
+ }
+ }
+
+ // Properties
+ for (int i = 0; i < n_properties; i++) {
+ GjsAutoPropertyInfo prop_info =
+ g_interface_info_get_property(iface_info, i);
+
+ GjsAutoChar js_name = gjs_hyphen_to_underscore(prop_info.name());
+
+ jsid id = gjs_intern_string_to_id(cx, js_name);
+ if (id == JSID_VOID)
+ return false;
+ properties.infallibleAppend(id);
+ }
+ }
+
+ g_free(interfaces);
+
+ if (info()) {
+ int n_methods = g_interface_info_get_n_methods(info());
+ int n_properties = g_interface_info_get_n_properties(info());
+ if (!properties.reserve(properties.length() + n_methods +
+ n_properties)) {
+ JS_ReportOutOfMemory(cx);
+ return false;
+ }
+
+ // Methods
+ for (int i = 0; i < n_methods; i++) {
+ GjsAutoFunctionInfo meth_info =
+ g_interface_info_get_method(info(), i);
+ GIFunctionInfoFlags flags = g_function_info_get_flags(meth_info);
+
+ if (flags & GI_FUNCTION_IS_METHOD) {
+ const char* name = meth_info.name();
+ jsid id = gjs_intern_string_to_id(cx, name);
+ if (id == JSID_VOID)
+ return false;
+ properties.infallibleAppend(id);
+ }
+ }
+
+ // Properties
+ for (int i = 0; i < n_properties; i++) {
+ GjsAutoPropertyInfo prop_info =
+ g_interface_info_get_property(info(), i);
+
+ GjsAutoChar js_name = gjs_hyphen_to_underscore(prop_info.name());
+ jsid id = gjs_intern_string_to_id(cx, js_name);
+ if (id == JSID_VOID)
+ return false;
+ properties.infallibleAppend(id);
+ }
+ }
+
+ return true;
+}
+
// See GIWrapperBase::resolve().
bool InterfacePrototype::resolve_impl(JSContext* context, JS::HandleObject obj,
JS::HandleId id, bool* resolved) {
@@ -111,7 +209,7 @@ const struct JSClassOps InterfaceBase::class_ops = {
nullptr, // addProperty
nullptr, // deleteProperty
nullptr, // enumerate
- nullptr, // newEnumerate
+ &InterfaceBase::new_enumerate,
&InterfaceBase::resolve,
nullptr, // mayResolve
&InterfaceBase::finalize,
diff --git a/gi/interface.h b/gi/interface.h
index 490b7093..a6d14425 100644
--- a/gi/interface.h
+++ b/gi/interface.h
@@ -92,6 +92,11 @@ class InterfacePrototype
bool resolve_impl(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
bool* resolved);
+ GJS_JSAPI_RETURN_CONVENTION
+ bool new_enumerate_impl(JSContext* cx, JS::HandleObject obj,
+ JS::MutableHandleIdVector properties,
+ bool only_enumerable);
+
// JS methods
GJS_JSAPI_RETURN_CONVENTION
@@ -106,8 +111,9 @@ class InterfaceInstance
friend class GIWrapperBase<InterfaceBase, InterfacePrototype,
InterfaceInstance>;
- [[noreturn]] InterfaceInstance(JSContext* cx, JS::HandleObject obj)
- : GIWrapperInstance(cx, obj) {
+ [[noreturn]] InterfaceInstance(InterfacePrototype* prototype,
+ JS::HandleObject obj)
+ : GIWrapperInstance(prototype, obj) {
g_assert_not_reached();
}
[[noreturn]] ~InterfaceInstance(void) { g_assert_not_reached(); }
diff --git a/gi/object.cpp b/gi/object.cpp
index 0effe95f..d1880966 100644
--- a/gi/object.cpp
+++ b/gi/object.cpp
@@ -1516,8 +1516,9 @@ void ObjectInstance::prepare_shutdown(void) {
std::mem_fn(&ObjectInstance::release_native_object));
}
-ObjectInstance::ObjectInstance(JSContext* cx, JS::HandleObject object)
- : GIWrapperInstance(cx, object),
+ObjectInstance::ObjectInstance(ObjectPrototype* prototype,
+ JS::HandleObject object)
+ : GIWrapperInstance(prototype, object),
m_wrapper_finalized(false),
m_gobj_disposed(false),
m_gobj_finalized(false),
@@ -2553,6 +2554,15 @@ bool ObjectPrototype::get_parent_constructor(
return true;
}
+void ObjectPrototype::set_interfaces(GType* interface_gtypes,
+ uint32_t n_interface_gtypes) {
+ if (interface_gtypes) {
+ for (uint32_t n = 0; n < n_interface_gtypes; n++) {
+ m_interface_gtypes.push_back(interface_gtypes[n]);
+ }
+ }
+}
+
/*
* ObjectPrototype::define_class:
* @in_object: Object where the constructor is stored, typically a repo object.
@@ -2575,11 +2585,7 @@ bool ObjectPrototype::define_class(
if (!priv)
return false;
- if (interface_gtypes) {
- for (uint32_t n = 0; n < n_interface_gtypes; n++) {
- priv->m_interface_gtypes.push_back(interface_gtypes[n]);
- }
- }
+ priv->set_interfaces(interface_gtypes, n_interface_gtypes);
JS::RootedObject parent_constructor(context);
if (!priv->get_parent_constructor(context, &parent_constructor))
@@ -2672,11 +2678,17 @@ ObjectInstance* ObjectInstance::new_for_gobject(JSContext* cx, GObject* gobj) {
return nullptr;
JS::RootedObject obj(
- cx, JS_NewObjectWithGivenProto(cx, JS_GetClass(proto), proto));
+ cx, JS_NewObjectWithGivenProto(cx, &ObjectBase::klass, proto));
if (!obj)
return nullptr;
- ObjectInstance* priv = ObjectInstance::new_for_js_object(cx, obj);
+ ObjectPrototype* prototype = resolve_prototype(cx, proto);
+ if (!prototype)
+ return nullptr;
+
+ ObjectInstance* priv = new ObjectInstance(prototype, obj);
+
+ JS_SetPrivate(obj, priv);
g_object_ref_sink(gobj);
priv->associate_js_gobject(cx, obj, gobj);
diff --git a/gi/object.h b/gi/object.h
index 8bd88957..886f13ea 100644
--- a/gi/object.h
+++ b/gi/object.h
@@ -236,6 +236,7 @@ class ObjectPrototype
const char* name, bool* resolved);
public:
+ void set_interfaces(GType* interface_gtypes, uint32_t n_interface_gtypes);
void set_type_qdata(void);
GJS_JSAPI_RETURN_CONVENTION
GParamSpec* find_param_spec_from_id(JSContext* cx, JS::HandleString key);
@@ -310,7 +311,7 @@ class ObjectInstance : public GIWrapperInstance<ObjectBase, ObjectPrototype,
/* Constructors */
private:
- ObjectInstance(JSContext* cx, JS::HandleObject obj);
+ ObjectInstance(ObjectPrototype* prototype, JS::HandleObject obj);
~ObjectInstance();
GJS_JSAPI_RETURN_CONVENTION
diff --git a/gi/private.cpp b/gi/private.cpp
index 4a07afb1..029ff08b 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"
@@ -147,6 +148,7 @@ GJS_JSAPI_RETURN_CONVENTION
static bool get_interface_gtypes(JSContext* cx, JS::HandleObject interfaces,
uint32_t n_interfaces, GType* iface_types) {
for (uint32_t ix = 0; ix < n_interfaces; ix++) {
+ // printf("%i find gtypes of\n", ix);
JS::RootedValue iface_val(cx);
if (!JS_GetElement(cx, interfaces, ix, &iface_val))
return false;
@@ -168,24 +170,37 @@ static bool get_interface_gtypes(JSContext* cx, JS::HandleObject interfaces,
ix);
return false;
}
-
+ // printf("found %s\n", g_type_name(iface_type));
iface_types[ix] = iface_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);
+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 +213,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 +232,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,26 +267,52 @@ 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) {
+ // printf("adding ifaxe....\n");
static GInterfaceInfo interface_vtable{nullptr, nullptr, nullptr};
g_type_add_interface_static(instance_type, interface_type,
&interface_vtable);
}
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 +334,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 +354,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 +367,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 +410,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 +534,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/union.cpp b/gi/union.cpp
index a91225d4..dca23592 100644
--- a/gi/union.cpp
+++ b/gi/union.cpp
@@ -29,8 +29,8 @@ UnionPrototype::UnionPrototype(GIUnionInfo* info, GType gtype)
UnionPrototype::~UnionPrototype(void) { GJS_DEC_COUNTER(union_prototype); }
-UnionInstance::UnionInstance(JSContext* cx, JS::HandleObject obj)
- : GIWrapperInstance(cx, obj) {
+UnionInstance::UnionInstance(UnionPrototype* prototype, JS::HandleObject obj)
+ : GIWrapperInstance(prototype, obj) {
GJS_INC_COUNTER(union_instance);
}
diff --git a/gi/union.h b/gi/union.h
index 269cf156..1f4b1864 100644
--- a/gi/union.h
+++ b/gi/union.h
@@ -65,6 +65,7 @@ class UnionInstance
friend class GIWrapperInstance<UnionBase, UnionPrototype, UnionInstance>;
friend class GIWrapperBase<UnionBase, UnionPrototype, UnionInstance>;
+ explicit UnionInstance(UnionPrototype* prototype, JS::HandleObject obj);
explicit UnionInstance(JSContext* cx, JS::HandleObject obj);
~UnionInstance(void);
diff --git a/gi/wrapperutils.h b/gi/wrapperutils.h
index 777fb72c..cbfb1884 100644
--- a/gi/wrapperutils.h
+++ b/gi/wrapperutils.h
@@ -10,6 +10,7 @@
#include <stdint.h>
+#include <new>
#include <string>
#include <girepository.h>
@@ -297,6 +298,45 @@ class GIWrapperBase : public CWrapperPointerOps<Base> {
}
protected:
+ /**
+ * GIWrapperBase::resolve_prototype:
+ */
+ [[nodiscard]] static Prototype* resolve_prototype(JSContext* cx,
+ JS::HandleObject 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);
+ }
+
/*
* GIWrapperBase::resolve:
*
@@ -429,14 +469,14 @@ class GIWrapperBase : public CWrapperPointerOps<Base> {
JS::RootedObject proto(cx);
if (!JS_GetPrototype(cx, obj, &proto))
return false;
- if (JS_GetClass(proto) != &Base::klass) {
- gjs_throw(cx, "Tried to construct an object without a GType");
+
+ Prototype* prototype = resolve_prototype(cx, proto);
+ if (!prototype)
return false;
- }
args.rval().setUndefined();
- Instance* priv = Instance::new_for_js_object(cx, obj);
+ Instance* priv = Instance::new_for_js_object(prototype, obj);
{
std::string fullName = priv->format_name();
@@ -641,6 +681,9 @@ class GIWrapperBase : public CWrapperPointerOps<Base> {
template <class Base, class Prototype, class Instance,
typename Info = GIObjectInfo>
class GIWrapperPrototype : public Base {
+ using GjsAutoPrototype =
+ GjsAutoPointer<Prototype, void, g_atomic_rc_box_release>;
+
protected:
// m_info may be null in the case of JS-defined types, or internal types
// not exposed through introspection, such as GLocalFile. Not all subclasses
@@ -798,6 +841,22 @@ class GIWrapperPrototype : public Base {
cx, constructor, m_gtype, m_info);
}
+ GJS_JSAPI_RETURN_CONVENTION
+ static Prototype* create_prototype(Info* info, GType gtype) {
+ g_assert(gtype != G_TYPE_INVALID);
+
+ // We have to keep the Prototype in an arcbox because some of its
+ // members are needed in some Instance destructors, e.g. m_gtype to
+ // figure out how to free the Instance's m_ptr, and m_info to figure out
+ // how many bytes to free if it is allocated directly. Storing a
+ // refcount on the prototype is cheaper than storing pointers to m_info
+ // and m_gtype on each instance.
+ Prototype* priv = g_atomic_rc_box_new0(Prototype);
+ new (priv) Prototype(info, gtype);
+
+ return priv;
+ }
+
public:
/**
* GIWrapperPrototype::create_class:
@@ -828,17 +887,8 @@ class GIWrapperPrototype : public Base {
JS::MutableHandleObject constructor,
JS::MutableHandleObject prototype) {
g_assert(in_object);
- g_assert(gtype != G_TYPE_INVALID);
- // We have to keep the Prototype in an arcbox because some of its
- // members are needed in some Instance destructors, e.g. m_gtype to
- // figure out how to free the Instance's m_ptr, and m_info to figure out
- // how many bytes to free if it is allocated directly. Storing a
- // refcount on the prototype is cheaper than storing pointers to m_info
- // and m_gtype on each instance.
- GjsAutoPointer<Prototype, void, g_atomic_rc_box_release> priv =
- g_atomic_rc_box_new0(Prototype);
- new (priv) Prototype(info, gtype);
+ GjsAutoPrototype priv = create_prototype(info, gtype);
if (!priv->init(cx))
return nullptr;
@@ -873,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
/*
@@ -954,11 +1043,12 @@ class GIWrapperInstance : public Base {
protected:
GjsSmartPointer<Wrapped> m_ptr;
- explicit GIWrapperInstance(JSContext* cx, JS::HandleObject obj)
- : Base(Prototype::for_js_prototype(cx, obj)), m_ptr(nullptr) {
+ explicit GIWrapperInstance(Prototype* prototype, JS::HandleObject obj)
+ : Base(prototype), m_ptr(nullptr) {
Base::m_proto->acquire();
Base::GIWrapperBase::debug_lifecycle(obj, "Instance constructor");
}
+
~GIWrapperInstance(void) { Base::m_proto->release(); }
public:
@@ -971,7 +1061,8 @@ class GIWrapperInstance : public Base {
[[nodiscard]] static Instance* new_for_js_object(JSContext* cx,
JS::HandleObject obj) {
g_assert(!JS_GetPrivate(obj));
- auto* priv = new Instance(cx, obj);
+ Prototype* prototype = Prototype::for_js_prototype(cx, obj);
+ auto* priv = new Instance(prototype, obj);
// Init the private variable before we do anything else. If a garbage
// collection happens when calling the constructor, then this object
@@ -981,6 +1072,16 @@ class GIWrapperInstance : public Base {
return priv;
}
+ [[nodiscard]] static Instance* new_for_js_object(Prototype* prototype,
+ JS::HandleObject obj) {
+ g_assert(!JS_GetPrivate(obj));
+ auto* priv = new Instance(prototype, obj);
+
+ JS_SetPrivate(obj, priv);
+
+ return priv;
+ }
+
// Method to get an existing Instance
/*
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") \
diff --git a/installed-tests/js/testGObjectClass.js b/installed-tests/js/testGObjectClass.js
index 41e7a8e9..0551c633 100644
--- a/installed-tests/js/testGObjectClass.js
+++ b/installed-tests/js/testGObjectClass.js
@@ -1371,3 +1371,61 @@ describe('GObject class with int64 properties', function () {
expect(instance.int64).toBe(GLib.MAXINT32 + 1);
});
});
+
+class MyStaticRegisteredObject extends GObject.Object {
+}
+
+MyStaticRegisteredObject.register();
+
+class MyStaticRegisteredInterface extends GObject.Interface {
+ anInterfaceMethod() {}
+}
+
+MyStaticRegisteredInterface.register();
+
+describe('GObject class with decorator', function () {
+ it('throws an error when not used with a GObject-derived class', function () {
+ class Foo { }
+ class Bar extends Foo { }
+ expect(() => Bar.register()).toThrow();
+ });
+});
+
+describe('GObject creation using base classes without registered GType', function () {
+ it('fails when trying to instantiate a class that inherits from a GObject type', function () {
+ const BadInheritance = class extends GObject.Object { };
+ const BadDerivedInheritance = class extends Derived { };
+
+ expect(() => new BadInheritance()).toThrowError(
+ /Tried to construct an object without a GType/
+ );
+ expect(() => new BadDerivedInheritance()).toThrowError(
+ /Tried to construct an object without a GType/
+ );
+ });
+
+ it('fails when trying to register a GObject class that inherits from a non-GObject type', function () {
+ class BadInheritance extends GObject.Object { }
+ class BadInheritanceDerived extends BadInheritance { }
+ expect(() => BadInheritanceDerived.register()).toThrowError(
+ /Object 0x[a-f0-9]+ is not a subclass of GObject_Object, it's a Object/
+ );
+ });
+});
+
+describe('GObject class registered with registerType', function () {
+ class SubObject extends MyStaticRegisteredObject {
+ }
+
+ SubObject.register();
+
+ it('extends class registered with registerClass', function () {
+ expect(() => new SubObject()).not.toThrow();
+
+ const instance = new SubObject();
+
+ expect(instance instanceof SubObject).toBeTrue();
+ expect(instance instanceof GObject.Object).toBeTrue();
+ expect(instance instanceof MyStaticRegisteredObject).toBeTrue();
+ });
+});
diff --git a/modules/core/_common.js b/modules/core/_common.js
index edc70215..0df2e16e 100644
--- a/modules/core/_common.js
+++ b/modules/core/_common.js
@@ -3,11 +3,13 @@
// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
// SPDX-FileCopyrightText: 2020 Philip Chimento <philip chimento gmail com>
-/* exported _checkAccessors */
+/* exported _checkAccessors, _assignChildrenAtInit */
// This is a helper module in which to put code that is common between the
// legacy GObject.Class system and the new GObject.registerClass system.
+var _assignChildrenAtInit = Symbol('GTK Widget assign children to properties at init');
+
function _generateAccessors(pspec, propdesc, GObject) {
const {name, flags} = pspec;
const readable = flags & GObject.ParamFlags.READABLE;
@@ -57,7 +59,7 @@ function _generateAccessors(pspec, propdesc, GObject) {
return propdesc;
}
-function _checkAccessors(proto, pspec, GObject) {
+function _checkAccessors(proto, pspec, GObject, sAccessorMapping) {
const {name, flags} = pspec;
if (flags & GObject.ParamFlags.CONSTRUCT_ONLY)
return;
@@ -79,7 +81,7 @@ function _checkAccessors(proto, pspec, GObject) {
const readable = flags & GObject.ParamFlags.READABLE;
const writable = flags & GObject.ParamFlags.WRITABLE;
if (!propdesc || (readable && !propdesc.get) || (writable && !propdesc.set))
- propdesc = _generateAccessors(pspec, propdesc, GObject);
+ propdesc = _generateAccessors(pspec, propdesc, GObject, sAccessorMapping);
if (!dashPropdesc)
Object.defineProperty(proto, name, propdesc);
@@ -89,4 +91,12 @@ function _checkAccessors(proto, pspec, GObject) {
if (!camelPropdesc)
Object.defineProperty(proto, camelName, propdesc);
}
+
+ proto[sAccessorMapping] = proto[sAccessorMapping] ?? {};
+ if (nameIsCompound) {
+ proto[sAccessorMapping][underscoreName] = propdesc;
+ proto[sAccessorMapping][camelName] = propdesc;
+ } else {
+ proto[sAccessorMapping][name] = propdesc;
+ }
}
diff --git a/modules/core/overrides/GObject.js b/modules/core/overrides/GObject.js
index 6bfaf144..3c1aa303 100644
--- a/modules/core/overrides/GObject.js
+++ b/modules/core/overrides/GObject.js
@@ -5,7 +5,7 @@
const Gi = imports._gi;
const {GjsPrivate, GLib} = imports.gi;
-const {_checkAccessors} = imports._common;
+const {_checkAccessors, _assignChildrenAtInit} = imports._common;
const Legacy = imports._legacy;
let GObject;
@@ -23,7 +23,39 @@ var _gtkCssName = Symbol('GTK widget CSS name');
var _gtkInternalChildren = Symbol('GTK widget template internal children');
var _gtkTemplate = Symbol('GTK widget template');
+const _accessorMapping = Symbol('GObject accessor mapping for class fields');
+
+function bindPropertyFields() {
+ const accessorMap = this.constructor.prototype[_accessorMapping];
+
+ if (!accessorMap)
+ return;
+
+ Object.entries(accessorMap).forEach(([key, accessor]) => {
+ const descriptor = Object.getOwnPropertyDescriptor(this, key);
+
+ if (descriptor && 'value' in descriptor) {
+ const value = descriptor.value;
+
+ Object.defineProperty(this, key, accessor);
+ // Don't override the default value with undefined
+ if (value !== undefined)
+ this[key] = value;
+ }
+ });
+}
+
function registerClass(...args) {
+ const newClass = _wrapClassWithGObjectOptions(...args);
+
+ // Only create interface generics for registerClass(...)
+ if (GObject.Interface.isPrototypeOf(newClass))
+ _createInterfaceGenerics(newClass);
+
+ return newClass;
+}
+
+function _wrapClassWithGObjectOptions(...args) {
let klass = args[0];
if (args.length === 2) {
// The two-argument form is the convenient syntax without ESnext
@@ -42,40 +74,194 @@ function registerClass(...args) {
// standard, this function can be used directly as a decorator.
let metaInfo = args[0];
klass = args[1];
- if ('GTypeName' in metaInfo)
- klass[GTypeName] = metaInfo.GTypeName;
- if ('GTypeFlags' in metaInfo)
- klass[GTypeFlags] = metaInfo.GTypeFlags;
- if ('Implements' in metaInfo)
- klass[interfaces] = metaInfo.Implements;
- if ('Properties' in metaInfo)
- klass[properties] = metaInfo.Properties;
- if ('Signals' in metaInfo)
- klass[signals] = metaInfo.Signals;
- if ('Requires' in metaInfo)
- klass[requires] = metaInfo.Requires;
- if ('CssName' in metaInfo)
- klass[_gtkCssName] = metaInfo.CssName;
- if ('Template' in metaInfo)
- klass[_gtkTemplate] = metaInfo.Template;
- if ('Children' in metaInfo)
- klass[_gtkChildren] = metaInfo.Children;
- if ('InternalChildren' in metaInfo)
- klass[_gtkInternalChildren] = metaInfo.InternalChildren;
+
+ _mapMetaInfoToClass(klass, metaInfo);
+ }
+
+ _assertDerivesFromGObject(klass, 'registerClass');
+
+ Object.defineProperty(klass, _assignChildrenAtInit, {
+ value: true,
+ enumerable: false,
+ configurable: false,
+ writable: false,
+ });
+
+ if ('_classInit' in klass) {
+ return klass._classInit(klass);
+ } else {
+ // Lang.Class compatibility.
+ return _resolveLegacyClassFunction(klass, '_classInit').call(klass, klass);
}
+}
+
+function _mapMetaInfoToClass(klass, metaInfo) {
+ if ('GTypeName' in metaInfo)
+ klass[GTypeName] = metaInfo.GTypeName;
+ if ('GTypeFlags' in metaInfo)
+ klass[GTypeFlags] = metaInfo.GTypeFlags;
+ if ('Implements' in metaInfo)
+ klass[interfaces] = metaInfo.Implements;
+ if ('Properties' in metaInfo)
+ klass[properties] = metaInfo.Properties;
+ if ('Signals' in metaInfo)
+ klass[signals] = metaInfo.Signals;
+ if ('Requires' in metaInfo)
+ klass[requires] = metaInfo.Requires;
+ if ('CssName' in metaInfo)
+ klass[_gtkCssName] = metaInfo.CssName;
+ if ('Template' in metaInfo)
+ klass[_gtkTemplate] = metaInfo.Template;
+ if ('Children' in metaInfo)
+ klass[_gtkChildren] = metaInfo.Children;
+ if ('InternalChildren' in metaInfo)
+ klass[_gtkInternalChildren] = metaInfo.InternalChildren;
+}
- if (!(klass.prototype instanceof GObject.Object) &&
- !(klass.prototype instanceof GObject.Interface)) {
- throw new TypeError('GObject.registerClass() used with invalid base ' +
- `class (is ${Object.getPrototypeOf(klass).name})`);
+function _assertDerivesFromGObject(klass, functionName) {
+ if (!GObject.Object.prototype.isPrototypeOf(klass.prototype) &&
+ !GObject.Interface.prototype.isPrototypeOf(klass.prototype)) {
+ throw new TypeError(`GObject.${functionName}() used with invalid base ` +
+ `class (is ${Object.getPrototypeOf(klass).name ?? klass})`);
}
+}
+function _resolveLegacyClassFunction(klass, func) {
// Find the "least derived" class with a _classInit static function; there
// definitely is one, since this class must inherit from GObject
let initclass = klass;
- while (typeof initclass._classInit === 'undefined')
+ while (typeof initclass[func] === 'undefined')
initclass = Object.getPrototypeOf(initclass.prototype).constructor;
- return initclass._classInit(klass);
+ return initclass[func];
+}
+
+function _hookupVFuncs(prototype, gobject_prototype, gtype) {
+ Object.getOwnPropertyNames(prototype)
+ .filter(name => name.startsWith('vfunc_') || name.startsWith('on_'))
+ .forEach(name => {
+ let descr = Object.getOwnPropertyDescriptor(prototype, name);
+ if (typeof descr.value !== 'function')
+ return;
+
+ let func = prototype[name];
+
+ if (name.startsWith('vfunc_')) {
+ gobject_prototype[Gi.hook_up_vfunc_symbol](name.slice(6), func);
+ } else if (name.startsWith('on_')) {
+ let id = GObject.signal_lookup(name.slice(3).replace('_', '-'),
+ gtype);
+ if (id !== 0) {
+ GObject.signal_override_class_closure(id, gtype, function (...argArray) {
+ let emitter = argArray.shift();
+
+ return func.apply(emitter, argArray);
+ });
+ }
+ }
+ });
+}
+
+function _defineGType(klass, giPrototype, registeredType) {
+ const config = {
+ enumerable: false,
+ configurable: false,
+ };
+
+ /**
+ * class Example {
+ * // The JS object for this class' ObjectPrototype
+ * static [Gi.gobject_prototype_symbol] = ...
+ * static get $gtype () {
+ * return ...;
+ * }
+ * static set $gtype (value) {}
+ * }
+ *
+ * // Equal to the same property on the constructor
+ * Example.prototype[Gi.gobject_prototype_symbol] = ...
+ */
+
+ Object.defineProperties(klass, {
+ $gtype: {
+ ...config,
+ set() {
+ // Setting $gtype is a no-op.
+ },
+ get() {
+ return registeredType;
+ },
+ },
+ });
+
+ Object.defineProperty(klass.prototype, Gi.gobject_prototype_symbol, {
+ ...config,
+ writable: false,
+ value: giPrototype,
+ });
+}
+
+function GObjectToString() {
+ if (!this.constructor)
+ return Object.prototype.toString.call(this);
+
+ const isInstance = this !== this.constructor.prototype;
+
+ let out = '';
+ if (isInstance)
+ out += '[object instance wrapper';
+ else
+ out += '[GObject prototype of';
+
+
+ try {
+ let gtype = this.constructor.$gtype;
+
+ out += ` GType:${GObject.type_name(gtype)}`;
+ } catch {
+ out += ' GType:unknown';
+ }
+
+ try {
+ out += ` jsobj@${imports.system.addressOf(this)}`;
+ } catch { }
+
+ try {
+ if (isInstance)
+ out += ` native@${imports.system.addressOfGObject(this)}`;
+ } catch { }
+
+ out += ']';
+
+ return out;
+}
+
+function _checkPropertiesArray(klass, propertiesArray) {
+ propertiesArray.forEach(pspec => _checkAccessors(klass.prototype, pspec, GObject, _accessorMapping));
+}
+
+function getOwnProperty(obj, prop) {
+ return obj.hasOwnProperty(prop) ? obj[prop] : undefined;
+}
+
+function _createInterfaceGenerics(klass) {
+ Object.getOwnPropertyNames(klass.prototype)
+ .filter(key => key !== 'constructor')
+ .concat(Object.getOwnPropertySymbols(klass.prototype))
+ .forEach(key => {
+ let descr = Object.getOwnPropertyDescriptor(klass.prototype, key);
+
+ // Create wrappers on the interface object so that generics work (e.g.
+ // SomeInterface.some_function(this, blah) instead of
+ // SomeInterface.prototype.some_function.call(this, blah)
+ if (typeof descr.value === 'function') {
+ let interfaceProto = klass.prototype; // capture in closure
+ klass[key] = function (thisObj, ...args) {
+ return interfaceProto[key].call(thisObj, ...args);
+ };
+ }
+
+ Object.defineProperty(klass.prototype, key, descr);
+ });
}
// Some common functions between GObject.Class and GObject.Interface
@@ -158,21 +344,38 @@ function _createGTypeName(klass) {
}
function _propertiesAsArray(klass) {
- let propertiesArray = [];
- if (klass.hasOwnProperty(properties)) {
- for (let prop in klass[properties])
- propertiesArray.push(klass[properties][prop]);
- }
- return propertiesArray;
+ let propertiesObject = getOwnProperty(klass, properties) ?? [];
+ return [...Object.values(propertiesObject)];
+}
+
+function _copyInterfacePrototypeDescriptors(targetPrototype, sourceInterface) {
+ Object.entries(Object.getOwnPropertyDescriptors(sourceInterface))
+ .filter(([key, descriptor]) =>
+ // Don't attempt to copy the constructor or toString implementations
+ !['constructor', 'toString'].includes(key) &&
+ // Ignore properties starting with __
+ (
+ typeof key !== 'string' || !key.startsWith('__')
+ ) &&
+ // Don't override an implementation on the target
+ !targetPrototype.hasOwnProperty(key) &&
+ descriptor &&
+ // Only copy if the descriptor has a getter, is a function, or is enumerable.
+ (
+ typeof descriptor.value === 'function' || descriptor.get || descriptor.enumerable
+ )
+ )
+ .forEach(([key, descriptor]) => {
+ Object.defineProperty(targetPrototype, key, descriptor);
+ });
}
-function _copyAllDescriptors(target, source, filter) {
- Object.getOwnPropertyNames(source)
- .filter(key => !['prototype', 'constructor'].concat(filter).includes(key))
- .concat(Object.getOwnPropertySymbols(source))
- .forEach(key => {
- let descriptor = Object.getOwnPropertyDescriptor(source, key);
- Object.defineProperty(target, key, descriptor);
+function _copyInterfacePrototypes(targetPrototype, sourceInterfaces) {
+ // Reverse the interface array to give the last required interface precedence over the first.
+ const gobjectInterfaces = [...sourceInterfaces].reverse();
+
+ gobjectInterfaces.forEach(sourceInterface => {
+ _copyInterfacePrototypeDescriptors(targetPrototype, sourceInterface.prototype);
});
}
@@ -222,6 +425,12 @@ function _checkInterface(iface, proto) {
}
}
+function _checkInterfaces(prototype, gobjectInterfaces) {
+ gobjectInterfaces.forEach(interface => {
+ _checkInterface(interface, prototype);
+ });
+}
+
function _init() {
GObject = this;
@@ -236,7 +445,7 @@ function _init() {
GObject.gtypeNameBasedOnJSPath = false;
- _makeDummyClass(GObject, 'VoidType', 'NONE', 'void', function () {});
+ _makeDummyClass(GObject, 'VoidType', 'NONE', 'void', function () { });
_makeDummyClass(GObject, 'Char', 'CHAR', 'gchar', Number);
_makeDummyClass(GObject, 'UChar', 'UCHAR', 'guchar', Number);
_makeDummyClass(GObject, 'Unichar', 'UNICHAR', 'gint', String);
@@ -431,6 +640,31 @@ function _init() {
GObject.registerClass = registerClass;
+ GObject.Object.register = function register() {
+ // Ensure the class derives from GObject.Object or
+ // GObject.Interface
+ _assertDerivesFromGObject(this, 'Object.register');
+
+ this._classInit.call(this, this);
+ };
+
+ function implements(iface) {
+ if (iface.$gtype)
+ return GObject.type_is_a(this, iface.$gtype);
+ return false;
+ }
+
+ GObject.Object.implements = implements;
+
+ GObject.Interface.register = function register() {
+ // Ensure the class derives from GObject.Interface
+ _assertDerivesFromGObject(this, 'Interface.register');
+
+ GObject.Object.register.call(this);
+ };
+
+ GObject.Object.prototype.bindPropertyFields = bindPropertyFields;
+
GObject.Object._classInit = function (klass) {
let gtypename = _createGTypeName(klass);
let gflags = klass.hasOwnProperty(GTypeFlags) ? klass[GTypeFlags] : 0;
@@ -439,56 +673,45 @@ function _init() {
let parent = Object.getPrototypeOf(klass);
let gobjectSignals = klass.hasOwnProperty(signals) ? klass[signals] : [];
- propertiesArray.forEach(pspec => _checkAccessors(klass.prototype, pspec, GObject));
+ _checkPropertiesArray(klass, propertiesArray);
- let newClass = Gi.register_type(parent.prototype, gtypename, gflags,
- gobjectInterfaces, propertiesArray);
- Object.setPrototypeOf(newClass, parent);
+ // Default to the GObject-specific prototype, fallback on the JS prototype.
+ const parentPrototype = parent.prototype[Gi.gobject_prototype_symbol] ?? parent.prototype;
- _createSignals(newClass.$gtype, gobjectSignals);
+ const [giPrototype, registeredType] = Gi.register_type_with_class(
+ klass,
+ parentPrototype,
+ gtypename,
+ gflags,
+ gobjectInterfaces,
+ propertiesArray
+ );
- _copyAllDescriptors(newClass, klass);
- gobjectInterfaces.forEach(iface =>
- _copyAllDescriptors(newClass.prototype, iface.prototype,
- ['toString']));
- _copyAllDescriptors(newClass.prototype, klass.prototype);
+ _defineGType(klass, giPrototype, registeredType);
+ _createSignals(klass.$gtype, gobjectSignals);
+ _copyInterfacePrototypes(klass.prototype, gobjectInterfaces);
- Object.getOwnPropertyNames(newClass.prototype)
- .filter(name => name.startsWith('vfunc_') || name.startsWith('on_'))
- .forEach(name => {
- let descr = Object.getOwnPropertyDescriptor(newClass.prototype, name);
- if (typeof descr.value !== 'function')
- return;
+ if (!klass.prototype.hasOwnProperty('toString'))
+ klass.prototype.toString = GObjectToString;
- let func = newClass.prototype[name];
+ _hookupVFuncs(klass.prototype, klass.prototype[Gi.gobject_prototype_symbol], klass.$gtype);
- if (name.startsWith('vfunc_')) {
- newClass.prototype[Gi.hook_up_vfunc_symbol](name.slice(6), func);
- } else if (name.startsWith('on_')) {
- let id = GObject.signal_lookup(name.slice(3).replace('_', '-'),
- newClass.$gtype);
- if (id !== 0) {
- GObject.signal_override_class_closure(id, newClass.$gtype, function (...argArray) {
- let emitter = argArray.shift();
+ _checkInterfaces(klass.prototype, gobjectInterfaces);
- return func.apply(emitter, argArray);
- });
- }
- }
- });
+ // Lang.Class parent classes don't support static inheritance
+ if (!('implements' in klass))
+ klass.implements = implements;
- gobjectInterfaces.forEach(iface =>
- _checkInterface(iface, newClass.prototype));
+ return klass;
+ };
- // For backwards compatibility only. Use instanceof instead.
- newClass.implements = function (iface) {
- if (iface.$gtype)
- return GObject.type_is_a(newClass.$gtype, iface.$gtype);
- return false;
- };
+ function interfaceInstanceOf(instance) {
+ if (GObject.Interface.prototype.isPrototypeOf(this.prototype))
+ return GObject.type_is_a(instance, this);
- return newClass;
- };
+
+ return false;
+ }
GObject.Interface._classInit = function (klass) {
let gtypename = _createGTypeName(klass);
@@ -496,33 +719,18 @@ function _init() {
let props = _propertiesAsArray(klass);
let gobjectSignals = klass.hasOwnProperty(signals) ? klass[signals] : [];
- let newInterface = Gi.register_interface(gtypename, gobjectInterfaces,
+ const [giPrototype, registeredType] = Gi.register_interface_with_class(klass, gtypename,
gobjectInterfaces,
props);
- _createSignals(newInterface.$gtype, gobjectSignals);
-
- _copyAllDescriptors(newInterface, klass);
-
- Object.getOwnPropertyNames(klass.prototype)
- .filter(key => key !== 'constructor')
- .concat(Object.getOwnPropertySymbols(klass.prototype))
- .forEach(key => {
- let descr = Object.getOwnPropertyDescriptor(klass.prototype, key);
+ _defineGType(klass, giPrototype, registeredType);
+ _createSignals(klass.$gtype, gobjectSignals);
- // Create wrappers on the interface object so that generics work (e.g.
- // SomeInterface.some_function(this, blah) instead of
- // SomeInterface.prototype.some_function.call(this, blah)
- if (typeof descr.value === 'function') {
- let interfaceProto = klass.prototype; // capture in closure
- newInterface[key] = function (thisObj, ...args) {
- return interfaceProto[key].call(thisObj, ...args);
- };
- }
- Object.defineProperty(newInterface.prototype, key, descr);
+ Object.defineProperty(klass, Symbol.hasInstance, {
+ value: interfaceInstanceOf,
});
- return newInterface;
+ return klass;
};
/**
diff --git a/modules/core/overrides/Gtk.js b/modules/core/overrides/Gtk.js
index 77649a73..f64f82a7 100644
--- a/modules/core/overrides/Gtk.js
+++ b/modules/core/overrides/Gtk.js
@@ -4,10 +4,45 @@
const Legacy = imports._legacy;
const {Gio, GjsPrivate, GObject} = imports.gi;
+const {_assignChildrenAtInit} = imports._common;
let Gtk;
let BuilderScope;
+const _hasTemplate = Symbol('GTK Widget has template');
+
+function hasTemplate(constructor) {
+ return constructor.hasOwnProperty(_hasTemplate) &&
+ constructor[_hasTemplate];
+}
+
+function setHasTemplate(klass) {
+ Object.defineProperty(klass, _hasTemplate, {
+ value: true,
+ enumerable: false,
+ configurable: false,
+ });
+}
+
+function assignChildrenDuringInit(constructor) {
+ return constructor.hasOwnProperty(_assignChildrenAtInit) &&
+ constructor[_assignChildrenAtInit];
+}
+
+function assignChildren(instance, constructor) {
+ let children = constructor[Gtk.children] || [];
+ for (let child of children) {
+ instance[child.replace(/-/g, '_')] =
+ instance.get_template_child(constructor, child);
+ }
+
+ let internalChildren = constructor[Gtk.internalChildren] || [];
+ for (let child of internalChildren) {
+ instance[`_${child.replace(/-/g, '_')}`] =
+ instance.get_template_child(constructor, child);
+ }
+}
+
function _init() {
Gtk = this;
@@ -17,6 +52,63 @@ function _init() {
Gtk.template = GObject.__gtkTemplate__;
let {GtkWidgetClass} = Legacy.defineGtkLegacyObjects(GObject, Gtk);
+
+ Gtk.assignChildren = assignChildren;
+
+ Gtk.Widget.get_template_children = function get_template_children() {
+ let children = [this.constructor[Gtk.children] || []];
+ let map = {};
+ for (let child of children)
+ map[child.replace(/-/g, '_')] = map.get_template_child(constructor, child);
+ };
+
+ const _get_template_child = Gtk.Widget.prototype.get_template_child;
+
+ Gtk.Widget.prototype.get_template_child = function get_template_child(constructorOrName, name) {
+ if (typeof constructorOrName === 'string')
+ return _get_template_child.call(this, this.constructor, constructorOrName);
+
+
+ return _get_template_child.call(this, constructorOrName, name);
+ };
+
+ const _set_template = Gtk.Widget.set_template;
+ Gtk.Widget.set_template = function set_template(contents) {
+ _set_template.call(this, contents);
+
+ setHasTemplate(this);
+ };
+
+ const _set_template_from_resource = Gtk.Widget.set_template_from_resource;
+ Gtk.Widget.set_template_from_resource = function set_template_from_resource(resource) {
+ _set_template_from_resource.call(this, resource);
+
+ setHasTemplate(this);
+ };
+
+ Gtk.Widget.set_template_from_uri = function set_template_from_uri(template) {
+ if (template.startsWith('resource:///')) {
+ this.set_template_from_resource(template.slice(11));
+ } else if (template.startsWith('file:///')) {
+ let file = Gio.File.new_for_uri(template);
+ let [, contents] = file.load_contents(null);
+ this.set_template(contents);
+ } else {
+ throw new Error(`Invalid template Uri: ${template}`);
+ }
+ };
+
+ Gtk.Widget.register = function register() {
+ Object.defineProperty(this, _assignChildrenAtInit, {
+ value: false,
+ writable: false,
+ enumerable: false,
+ configurable: false,
+ });
+
+ GObject.Object.register.call(this);
+ };
+
Gtk.Widget.prototype.__metaclass__ = GtkWidgetClass;
if (Gtk.Container && Gtk.Container.prototype.child_set_property) {
@@ -28,7 +120,7 @@ function _init() {
Gtk.Widget.prototype._init = function (params) {
let wrapper = this;
- if (wrapper.constructor[Gtk.template]) {
+ if (hasTemplate(wrapper.constructor)) {
if (!BuilderScope) {
Gtk.Widget.set_connect_func.call(wrapper.constructor,
(builder, obj, signalName, handlerName, connectObj, flags) => {
@@ -46,53 +138,34 @@ function _init() {
wrapper = GObject.Object.prototype._init.call(wrapper, params) ?? wrapper;
- if (wrapper.constructor[Gtk.template]) {
- let children = wrapper.constructor[Gtk.children] || [];
- for (let child of children) {
- wrapper[child.replace(/-/g, '_')] =
- wrapper.get_template_child(wrapper.constructor, child);
- }
-
- let internalChildren = wrapper.constructor[Gtk.internalChildren] || [];
- for (let child of internalChildren) {
- wrapper[`_${child.replace(/-/g, '_')}`] =
- wrapper.get_template_child(wrapper.constructor, child);
- }
- }
+ if (hasTemplate(wrapper.constructor) && assignChildrenDuringInit(wrapper.constructor))
+ assignChildren(this, wrapper.constructor);
return wrapper;
};
+ Gtk.Widget.prototype._instance_init = function () {
+ if (hasTemplate(this.constructor))
+ this.init_template();
+ };
+
Gtk.Widget._classInit = function (klass) {
let template = klass[Gtk.template];
let cssName = klass[Gtk.cssName];
let children = klass[Gtk.children];
let internalChildren = klass[Gtk.internalChildren];
- if (template) {
- klass.prototype._instance_init = function () {
- this.init_template();
- };
- }
-
klass = GObject.Object._classInit(klass);
if (cssName)
Gtk.Widget.set_css_name.call(klass, cssName);
if (template) {
- if (typeof template === 'string') {
- if (template.startsWith('resource:///')) {
- Gtk.Widget.set_template_from_resource.call(klass,
- template.slice(11));
- } else if (template.startsWith('file:///')) {
- let file = Gio.File.new_for_uri(template);
- let [, contents] = file.load_contents(null);
- Gtk.Widget.set_template.call(klass, contents);
- }
- } else {
+ if (typeof template === 'string' &&
+ (template.startsWith('resource:///') || template.startsWith('file:///')))
+ Gtk.Widget.set_template_from_uri.call(klass, template);
+ else
Gtk.Widget.set_template.call(klass, template);
- }
if (BuilderScope)
Gtk.Widget.set_template_scope.call(klass, new BuilderScope());
@@ -100,17 +173,35 @@ function _init() {
if (children) {
children.forEach(child =>
- Gtk.Widget.bind_template_child_full.call(klass, child, false, 0));
+ Gtk.Widget.bind_template_child_internal.call(klass, child));
}
if (internalChildren) {
internalChildren.forEach(child =>
- Gtk.Widget.bind_template_child_full.call(klass, child, true, 0));
+ Gtk.Widget.bind_template_child.call(klass, child));
}
return klass;
};
+ const _bind_template_child_full = Gtk.Widget.bind_template_child_full;
+
+ Gtk.Widget.bind_template_child_full = function (name, isInternal = false) {
+ _bind_template_child_full.call(this, name, isInternal, 0);
+ };
+
+ Gtk.Widget.bind_template_child = function (name) {
+ _bind_template_child_full.call(this, name, false, 0);
+ };
+
+ Gtk.Widget.bind_template_child_internal = function (name) {
+ _bind_template_child_full.call(this, name, true, 0);
+ };
+
+ Gtk.Widget.bind_template_child_internal = function (name) {
+ _bind_template_child_full.call(this, name, true, 0);
+ };
+
if (Gtk.Widget.prototype.get_first_child) {
Gtk.Widget.prototype[Symbol.iterator] = function* () {
for (let c = this.get_first_child(); c; c = c.get_next_sibling())
diff --git a/modules/script/_legacy.js b/modules/script/_legacy.js
index 135d2517..faf83913 100644
--- a/modules/script/_legacy.js
+++ b/modules/script/_legacy.js
@@ -192,27 +192,29 @@ Class.prototype._copyPropertyDescriptor = function (params, propertyObj, key) {
Class.prototype._init = function (params) {
let className = params.Name;
- let propertyObj = { };
+ let propertyObj = {};
let interfaces = params.Implements || [];
interfaces.forEach(iface => {
Object.getOwnPropertyNames(iface.prototype)
- .filter(name => !name.startsWith('__') && name !== 'constructor')
- .filter(name => !(name in this.prototype))
- .forEach(name => {
- let descriptor = Object.getOwnPropertyDescriptor(iface.prototype,
- name);
- // writable and enumerable are inherited, see note above
- descriptor.configurable = false;
- propertyObj[name] = descriptor;
- });
+ .filter(name => !name.startsWith('__') && name !== 'constructor')
+ .filter(name => !(name in this.prototype))
+ .forEach(name => {
+ let descriptor = Object.getOwnPropertyDescriptor(iface.prototype,
+ name);
+ if (descriptor) {
+ // writable and enumerable are inherited, see note above
+ descriptor.configurable = false;
+ propertyObj[name] = descriptor;
+ }
+ });
});
Object.getOwnPropertyNames(params)
- .filter(name =>
- ['Name', 'Extends', 'Abstract', 'Implements'].indexOf(name) === -1)
- .concat(Object.getOwnPropertySymbols(params))
- .forEach(this._copyPropertyDescriptor.bind(this, params, propertyObj));
+ .filter(name =>
+ ['Name', 'Extends', 'Abstract', 'Implements'].indexOf(name) === -1)
+ .concat(Object.getOwnPropertySymbols(params))
+ .forEach(this._copyPropertyDescriptor.bind(this, params, propertyObj));
Object.defineProperties(this.prototype, propertyObj);
Object.defineProperties(this.prototype, {
@@ -251,18 +253,18 @@ function _getMetaInterface(params) {
}
return null;
})
- .reduce((best, candidate) => {
- // This function reduces to the "most derived" meta interface in the list.
- if (best === null)
- return candidate;
- if (candidate === null)
- return best;
- for (let sup = candidate; sup; sup = sup.__super__) {
- if (sup === best)
+ .reduce((best, candidate) => {
+ // This function reduces to the "most derived" meta interface in the list.
+ if (best === null)
return candidate;
- }
- return best;
- }, null);
+ if (candidate === null)
+ return best;
+ for (let sup = candidate; sup; sup = sup.__super__) {
+ if (sup === best)
+ return candidate;
+ }
+ return best;
+ }, null);
// If we reach this point and we don't know the meta-interface, then it's
// most likely because there were only pure-C interfaces listed in Requires
@@ -347,20 +349,16 @@ Interface.prototype._check = function (proto) {
// but is not preferred because it will be the C name. The last option
// is just so that we print something if there is garbage in Requires.
required.prototype.__name__ || required.name || required);
- if (unfulfilledReqs.length > 0) {
- throw new Error(`The following interfaces must be implemented before ${
- this.prototype.__name__}: ${unfulfilledReqs.join(', ')}`);
- }
+ if (unfulfilledReqs.length > 0)
+ throw new Error(`The following interfaces must be implemented before ${this.prototype.__name__}:
${unfulfilledReqs.join(', ')}`);
+
// Check that this interface's required methods are implemented
let unimplementedFns = Object.getOwnPropertyNames(this.prototype)
- .filter(p => this.prototype[p] === Interface.UNIMPLEMENTED)
- .filter(p => !(p in proto) || proto[p] === Interface.UNIMPLEMENTED);
- if (unimplementedFns.length > 0) {
- throw new Error(`The following members of ${
- this.prototype.__name__} are not implemented yet: ${
- unimplementedFns.join(', ')}`);
- }
+ .filter(p => this.prototype[p] === Interface.UNIMPLEMENTED)
+ .filter(p => !(p in proto) || proto[p] === Interface.UNIMPLEMENTED);
+ if (unimplementedFns.length > 0)
+ throw new Error(`The following members of ${this.prototype.__name__} are not implemented yet:
${unimplementedFns.join(', ')}`);
};
Interface.prototype.toString = function () {
@@ -372,25 +370,25 @@ Interface.prototype._init = function (params) {
let propertyObj = {};
Object.getOwnPropertyNames(params)
- .filter(name => ['Name', 'Requires'].indexOf(name) === -1)
- .forEach(name => {
- let descriptor = Object.getOwnPropertyDescriptor(params, name);
-
- // Create wrappers on the interface object so that generics work (e.g.
- // SomeInterface.some_function(this, blah) instead of
- // SomeInterface.prototype.some_function.call(this, blah)
- if (typeof descriptor.value === 'function') {
- let interfaceProto = this.prototype; // capture in closure
- this[name] = function (thisObj, ...args) {
- return interfaceProto[name].call(thisObj, ...args);
- };
- }
+ .filter(name => ['Name', 'Requires'].indexOf(name) === -1)
+ .forEach(name => {
+ let descriptor = Object.getOwnPropertyDescriptor(params, name);
+
+ // Create wrappers on the interface object so that generics work (e.g.
+ // SomeInterface.some_function(this, blah) instead of
+ // SomeInterface.prototype.some_function.call(this, blah)
+ if (typeof descriptor.value === 'function') {
+ let interfaceProto = this.prototype; // capture in closure
+ this[name] = function (thisObj, ...args) {
+ return interfaceProto[name].call(thisObj, ...args);
+ };
+ }
- // writable and enumerable are inherited, see note in Class._init()
- descriptor.configurable = false;
+ // writable and enumerable are inherited, see note in Class._init()
+ descriptor.configurable = false;
- propertyObj[name] = descriptor;
- });
+ propertyObj[name] = descriptor;
+ });
Object.defineProperties(this.prototype, propertyObj);
Object.defineProperties(this.prototype, {
@@ -647,6 +645,8 @@ function defineGObjectLegacyObjects(GObject) {
}
function defineGtkLegacyObjects(GObject, Gtk) {
+ const {_assignChildrenAtInit} = imports._common;
+
const GtkWidgetClass = new Class({
Name: 'GtkWidgetClass',
Extends: GObject.Class,
@@ -687,6 +687,13 @@ function defineGtkLegacyObjects(GObject, Gtk) {
this[Gtk.children] = children;
this[Gtk.internalChildren] = internalChildren;
+ Object.defineProperty(this, _assignChildrenAtInit, {
+ value: true,
+ writable: false,
+ enumerable: false,
+ configurable: false,
+ });
+
if (children) {
for (let i = 0; i < children.length; i++)
Gtk.Widget.bind_template_child_full.call(this, children[i], false, 0);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]