[gjs/ewlsh/wrap-es-class] overrides: Support class fields in GObject.registerClass
- From: Evan Welsh <ewlsh src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gjs/ewlsh/wrap-es-class] overrides: Support class fields in GObject.registerClass
- Date: Sun, 9 Jan 2022 19:54:12 +0000 (UTC)
commit a1f9b512960e737e9788f4367a066e98cd91793f
Author: Evan Welsh <contact evanwelsh com>
Date: Sat Jan 8 22:44:07 2022 -0800
overrides: Support class fields in GObject.registerClass
gi/private.cpp | 185 +++++++++++++++---
gi/wrapperutils.h | 71 ++++++-
gjs/atoms.h | 1 +
installed-tests/js/testGtk3.js | 17 +-
installed-tests/js/testGtk4.js | 17 +-
modules/core/_common.js | 25 ++-
modules/core/overrides/GObject.js | 401 ++++++++++++++++++++++++++++----------
modules/core/overrides/Gtk.js | 112 ++++++-----
8 files changed, 620 insertions(+), 209 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") \
diff --git a/installed-tests/js/testGtk3.js b/installed-tests/js/testGtk3.js
index 83ca8247..92a19858 100644
--- a/installed-tests/js/testGtk3.js
+++ b/installed-tests/js/testGtk3.js
@@ -54,18 +54,15 @@ const MyComplexGtkSubclass = GObject.registerClass({
boundCallback(widget) {
widget.callbackBoundTo = this;
}
-});
-// Sadly, putting this in the body of the class will prevent calling
-// get_template_child, since MyComplexGtkSubclass will be bound to the ES6
-// class name without the GObject goodies in it
-MyComplexGtkSubclass.prototype.testChildrenExist = function () {
- this._internalLabel = this.get_template_child(MyComplexGtkSubclass, 'label-child');
- expect(this._internalLabel).toEqual(jasmine.anything());
+ testChildrenExist() {
+ this._internalLabel = this.get_template_child(MyComplexGtkSubclass, 'label-child');
+ expect(this._internalLabel).toEqual(jasmine.anything());
- expect(this.label_child2).toEqual(jasmine.anything());
- expect(this._internal_label_child).toEqual(jasmine.anything());
-};
+ expect(this.label_child2).toEqual(jasmine.anything());
+ expect(this._internal_label_child).toEqual(jasmine.anything());
+ }
+});
const MyComplexGtkSubclassFromResource = GObject.registerClass({
Template: 'resource:///org/gjs/jsunit/complex3.ui',
diff --git a/installed-tests/js/testGtk4.js b/installed-tests/js/testGtk4.js
index d7fc0028..c3444715 100644
--- a/installed-tests/js/testGtk4.js
+++ b/installed-tests/js/testGtk4.js
@@ -50,18 +50,15 @@ const MyComplexGtkSubclass = GObject.registerClass({
boundCallback(widget) {
widget.callbackBoundTo = this;
}
-});
-// Sadly, putting this in the body of the class will prevent calling
-// get_template_child, since MyComplexGtkSubclass will be bound to the ES6
-// class name without the GObject goodies in it
-MyComplexGtkSubclass.prototype.testChildrenExist = function () {
- this._internalLabel = this.get_template_child(MyComplexGtkSubclass, 'label-child');
- expect(this._internalLabel).toEqual(jasmine.anything());
+ testChildrenExist() {
+ this._internalLabel = this.get_template_child(MyComplexGtkSubclass, 'label-child');
+ expect(this._internalLabel).toEqual(jasmine.anything());
- expect(this.label_child2).toEqual(jasmine.anything());
- expect(this._internal_label_child).toEqual(jasmine.anything());
-};
+ expect(this.label_child2).toEqual(jasmine.anything());
+ expect(this._internal_label_child).toEqual(jasmine.anything());
+ }
+});
const MyComplexGtkSubclassFromResource = GObject.registerClass({
Template: 'resource:///org/gjs/jsunit/complex4.ui',
diff --git a/modules/core/_common.js b/modules/core/_common.js
index edc70215..4e361e16 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, _registerType, definePublicProperties, definePrivateProperties */
// 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 _registerType = Symbol('GObject register type hook');
+
function _generateAccessors(pspec, propdesc, GObject) {
const {name, flags} = pspec;
const readable = flags & GObject.ParamFlags.READABLE;
@@ -90,3 +92,24 @@ function _checkAccessors(proto, pspec, GObject) {
Object.defineProperty(proto, camelName, propdesc);
}
}
+
+function definePublicProperties(object, values) {
+ Object.defineProperties(object, Object.getOwnPropertyDescriptors(values));
+}
+
+function definePrivateProperties(object, values) {
+ const propertyKeys = [...Object.getOwnPropertyNames(values), ...Object.getOwnPropertySymbols(values)];
+
+ const privateDescriptors = Object.fromEntries(propertyKeys.map(key => {
+ const descriptor = Object.getOwnPropertyDescriptor(values, key);
+
+ return [key, {
+ ...descriptor,
+ ...'value' in descriptor ? {writable: false} : {},
+ configurable: false,
+ enumerable: false,
+ }];
+ }));
+
+ Object.defineProperties(object, privateDescriptors);
+}
diff --git a/modules/core/overrides/GObject.js b/modules/core/overrides/GObject.js
index 7357b059..f7817e80 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, _registerType, definePublicProperties, definePrivateProperties} = imports._common;
const Legacy = imports._legacy;
let GObject;
@@ -23,6 +23,38 @@ var _gtkCssName = Symbol('GTK widget CSS name');
var _gtkInternalChildren = Symbol('GTK widget template internal children');
var _gtkTemplate = Symbol('GTK widget template');
+function _mapWidgetDefinitionToClass(klass, metaInfo) {
+ 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;
+}
+
+function _mapTypeDefinition(klass, metaInfo) {
+ if ('GTypeName' in metaInfo)
+ klass[GTypeName] = metaInfo.GTypeName;
+ if ('GTypeFlags' in metaInfo)
+ klass[GTypeFlags] = metaInfo.GTypeFlags;
+ if ('Properties' in metaInfo)
+ klass[properties] = metaInfo.Properties;
+ if ('Signals' in metaInfo)
+ klass[signals] = metaInfo.Signals;
+}
+
+function _mapClassDefinition(klass, metaInfo) {
+ if ('Implements' in metaInfo)
+ klass[interfaces] = metaInfo.Implements;
+}
+
+function _mapInterfaceDefinition(klass, metaInfo) {
+ if ('Requires' in metaInfo)
+ klass[requires] = metaInfo.Requires;
+}
+
function registerClass(...args) {
let klass = args[0];
if (args.length === 2) {
@@ -42,40 +74,125 @@ 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;
+
+ _mapTypeDefinition(klass, metaInfo);
+ _mapClassDefinition(klass, metaInfo);
+ _mapInterfaceDefinition(klass, metaInfo);
+ _mapWidgetDefinitionToClass(klass, metaInfo);
+ }
+
+ _assertDerivesFromGObject(klass, [GObject.Object, GObject.Interface], 'registerClass');
+
+ if ('_classInit' in klass) {
+ klass = klass._classInit(klass);
+ } else {
+ // Lang.Class compatibility.
+ klass = _resolveLegacyClassFunction(klass, '_classInit')?.(klass);
}
- if (!(klass.prototype instanceof GObject.Object) &&
- !(klass.prototype instanceof GObject.Interface)) {
- throw new TypeError('GObject.registerClass() used with invalid base ' +
+ return klass;
+}
+
+function _assertDerivesFromGObject(klass, parents, functionName) {
+ if (parents.every(parent => !(klass.prototype instanceof parent))) {
+ throw new TypeError(`GObject.${functionName}() used with invalid base ` +
`class (is ${Object.getPrototypeOf(klass).name})`);
}
+}
+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 _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
@@ -166,16 +283,26 @@ function _propertiesAsArray(klass) {
return propertiesArray;
}
-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);
-
- if (descriptor)
- Object.defineProperty(target, key, descriptor);
- });
+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 _interfacePresent(required, klass) {
@@ -224,6 +351,14 @@ function _checkInterface(iface, proto) {
}
}
+function _checkProperties(klass) {
+ if (!klass.hasOwnProperty(properties))
+ return;
+
+ for (let pspec of Object.values(klass[properties]))
+ _checkAccessors(klass.prototype, pspec, GObject);
+}
+
function _init() {
GObject = this;
@@ -238,7 +373,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);
@@ -433,98 +568,150 @@ function _init() {
GObject.registerClass = registerClass;
- GObject.Object._classInit = function (klass) {
- let gtypename = _createGTypeName(klass);
- let gflags = klass.hasOwnProperty(GTypeFlags) ? klass[GTypeFlags] : 0;
- let gobjectInterfaces = klass.hasOwnProperty(interfaces) ? klass[interfaces] : [];
- let propertiesArray = _propertiesAsArray(klass);
- let parent = Object.getPrototypeOf(klass);
- let gobjectSignals = klass.hasOwnProperty(signals) ? klass[signals] : [];
+ const {toString} = GObject.Object.prototype;
- propertiesArray.forEach(pspec => _checkAccessors(klass.prototype, pspec, GObject));
+ Object.assign(GObject.Object.prototype, {
+ toString() {
+ if (!this.constructor)
+ return Object.prototype.toString.call(this);
- let newClass = Gi.register_type(parent.prototype, gtypename, gflags,
- gobjectInterfaces, propertiesArray);
- Object.setPrototypeOf(newClass, parent);
+ // Use the native toString if this is not a custom class.
+ if (!this.constructor.prototype[Gi.gobject_prototype_symbol])
+ return toString.call(this);
- _createSignals(newClass.$gtype, gobjectSignals);
+ const isInstance = this !== this.constructor.prototype;
- _copyAllDescriptors(newClass, klass);
- gobjectInterfaces.forEach(iface =>
- _copyAllDescriptors(newClass.prototype, iface.prototype,
- ['toString']));
- _copyAllDescriptors(newClass.prototype, klass.prototype);
+ let out = '';
+ if (isInstance)
+ out += '[object instance wrapper';
+ else
+ out += '[GObject prototype of';
- 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;
- let func = newClass.prototype[name];
+ try {
+ let gtype = this.constructor.$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();
-
- return func.apply(emitter, argArray);
- });
- }
+ out += ` GType:${GObject.type_name(gtype)}`;
+ } catch {
+ out += ' GType:unknown';
}
- });
- gobjectInterfaces.forEach(iface =>
- _checkInterface(iface, newClass.prototype));
+ try {
+ out += ` jsobj@${imports.system.addressOf(this)}`;
+ } catch { }
+
+ try {
+ if (isInstance)
+ out += ` native@${imports.system.addressOfGObject(this)}`;
+ } catch { }
- // For backwards compatibility only. Use instanceof instead.
- newClass.implements = function (iface) {
+ out += ']';
+
+ return out;
+ },
+ });
+
+ definePublicProperties(GObject.Object, {
+ implements(iface) {
if (iface.$gtype)
- return GObject.type_is_a(newClass.$gtype, iface.$gtype);
+ return GObject.type_is_a(this, iface.$gtype);
return false;
- };
+ },
+ });
+
+ definePrivateProperties(GObject.Object, {
+ [_registerType]() {
+ let klass = this;
+
+ let gtypename = _createGTypeName(klass);
+ let gflags = klass.hasOwnProperty(GTypeFlags) ? klass[GTypeFlags] : 0;
+ let gobjectInterfaces = klass.hasOwnProperty(interfaces) ? klass[interfaces] : [];
+ let propertiesArray = _propertiesAsArray(klass);
+ let parent = Object.getPrototypeOf(klass);
+ let gobjectSignals = klass.hasOwnProperty(signals) ? klass[signals] : [];
+
+ // Default to the GObject-specific prototype, fallback on the JS prototype for GI native classes.
+ const parentPrototype = parent.prototype[Gi.gobject_prototype_symbol] ?? parent.prototype;
+
+ const [giPrototype, registeredType] = Gi.register_type_with_class(
+ klass,
+ parentPrototype,
+ gtypename,
+ gflags,
+ gobjectInterfaces,
+ propertiesArray
+ );
+
+ _defineGType(klass, giPrototype, registeredType);
+ _createSignals(klass.$gtype, gobjectSignals);
+
+ // Reverse the interface array to give the last required interface precedence over the first.
+ const requiredInterfaces = [...gobjectInterfaces].reverse();
+ requiredInterfaces.forEach(iface =>
+ _copyInterfacePrototypeDescriptors(klass.prototype, iface.prototype));
+
+ _hookupVFuncs(klass.prototype, klass.prototype[Gi.gobject_prototype_symbol], klass.$gtype);
+
+ gobjectInterfaces.forEach(iface =>
+ _checkInterface(iface, klass.prototype));
+
+ // Lang.Class parent classes don't support static inheritance
+ if (!('implements' in klass))
+ klass.implements = GObject.Object.implements;
+ },
+ });
+
+ GObject.Object._classInit = function (klass) {
+ _checkProperties(klass);
+
+ if (_registerType in klass)
+ klass[_registerType]();
+ else
+ _resolveLegacyClassFunction(klass, _registerType).call(klass);
- return newClass;
+ return klass;
};
- GObject.Interface._classInit = function (klass) {
- let gtypename = _createGTypeName(klass);
- let gobjectInterfaces = klass.hasOwnProperty(requires) ? klass[requires] : [];
- let props = _propertiesAsArray(klass);
- let gobjectSignals = klass.hasOwnProperty(signals) ? klass[signals] : [];
+ function interfaceInstanceOf(instance) {
+ if (GObject.Interface.prototype.isPrototypeOf(this.prototype))
+ return GObject.type_is_a(instance, this);
- let newInterface = Gi.register_interface(gtypename, gobjectInterfaces,
- props);
- _createSignals(newInterface.$gtype, gobjectSignals);
+ return false;
+ }
- _copyAllDescriptors(newInterface, klass);
+ definePrivateProperties(GObject.Interface, {
+ [_registerType]() {
+ let klass = this;
- Object.getOwnPropertyNames(klass.prototype)
- .filter(key => key !== 'constructor')
- .concat(Object.getOwnPropertySymbols(klass.prototype))
- .forEach(key => {
- let descr = Object.getOwnPropertyDescriptor(klass.prototype, key);
+ let gtypename = _createGTypeName(klass);
+ let gobjectInterfaces = klass.hasOwnProperty(requires) ? klass[requires] : [];
+ let props = _propertiesAsArray(klass);
+ let gobjectSignals = klass.hasOwnProperty(signals) ? klass[signals] : [];
- // 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);
- };
- }
+ const [giPrototype, registeredType] = Gi.register_interface_with_class(klass, gtypename,
gobjectInterfaces,
+ props);
- Object.defineProperty(newInterface.prototype, key, descr);
- });
+ _defineGType(klass, giPrototype, registeredType);
+ _createSignals(klass.$gtype, gobjectSignals);
+
+ Object.defineProperty(klass, Symbol.hasInstance, {
+ value: interfaceInstanceOf,
+ });
+
+ return klass;
+ },
+ });
+
+ GObject.Interface._classInit = function (klass) {
+ if (_registerType in klass)
+ klass[_registerType]();
+ else
+ _resolveLegacyClassFunction(klass, _registerType).call(klass);
+
+ _createInterfaceGenerics(klass);
- return newInterface;
+ return klass;
};
/**
diff --git a/modules/core/overrides/Gtk.js b/modules/core/overrides/Gtk.js
index 77649a73..90deefb7 100644
--- a/modules/core/overrides/Gtk.js
+++ b/modules/core/overrides/Gtk.js
@@ -4,10 +4,25 @@
const Legacy = imports._legacy;
const {Gio, GjsPrivate, GObject} = imports.gi;
+const {_registerType, definePrivateProperties} = imports._common;
let Gtk;
let BuilderScope;
+function defineChildren(instance, constructor, target = instance) {
+ let children = constructor[Gtk.children] || [];
+ for (let child of children) {
+ target[child.replace(/-/g, '_')] =
+ instance.get_template_child(constructor, child);
+ }
+
+ let internalChildren = constructor[Gtk.internalChildren] || [];
+ for (let child of internalChildren) {
+ target[`_${child.replace(/-/g, '_')}`] =
+ instance.get_template_child(constructor, child);
+ }
+}
+
function _init() {
Gtk = this;
@@ -46,70 +61,65 @@ 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 (wrapper.constructor[Gtk.template])
+ defineChildren(this, wrapper.constructor);
return wrapper;
};
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();
- };
- }
+ return GObject.Object._classInit(klass);
+ };
- klass = GObject.Object._classInit(klass);
+ definePrivateProperties(Gtk.Widget, {
+ [_registerType]() {
+ let klass = this;
- if (cssName)
- Gtk.Widget.set_css_name.call(klass, cssName);
+ let template = klass[Gtk.template];
+ let cssName = klass[Gtk.cssName];
+ let children = klass[Gtk.children];
+ let internalChildren = klass[Gtk.internalChildren];
- 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 {
- Gtk.Widget.set_template.call(klass, template);
+ if (template) {
+ klass.prototype._instance_init = function () {
+ this.init_template();
+ };
}
- if (BuilderScope)
- Gtk.Widget.set_template_scope.call(klass, new BuilderScope());
- }
+ GObject.Object[_registerType].call(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 {
+ Gtk.Widget.set_template.call(klass, template);
+ }
- if (children) {
- children.forEach(child =>
- Gtk.Widget.bind_template_child_full.call(klass, child, false, 0));
- }
+ if (BuilderScope)
+ Gtk.Widget.set_template_scope.call(klass, new BuilderScope());
+ }
- if (internalChildren) {
- internalChildren.forEach(child =>
- Gtk.Widget.bind_template_child_full.call(klass, child, true, 0));
- }
+ if (children) {
+ children.forEach(child =>
+ Gtk.Widget.bind_template_child_full.call(klass, child, false, 0));
+ }
- return klass;
- };
+ if (internalChildren) {
+ internalChildren.forEach(child =>
+ Gtk.Widget.bind_template_child_full.call(klass, child, true, 0));
+ }
+ },
+ });
if (Gtk.Widget.prototype.get_first_child) {
Gtk.Widget.prototype[Symbol.iterator] = function* () {
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]