diff options
-rw-r--r-- | installed-tests/js/testGObjectClass.js | 58 | ||||
-rw-r--r-- | modules/core/_common.js | 28 | ||||
-rw-r--r-- | modules/core/overrides/GObject.js | 32 | ||||
-rw-r--r-- | modules/core/overrides/Gtk.js | 89 | ||||
-rw-r--r-- | modules/script/_legacy.js | 9 |
5 files changed, 200 insertions, 16 deletions
diff --git a/installed-tests/js/testGObjectClass.js b/installed-tests/js/testGObjectClass.js index af3f760a..c93104b8 100644 --- a/installed-tests/js/testGObjectClass.js +++ b/installed-tests/js/testGObjectClass.js @@ -1642,3 +1642,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 4e361e16..780483b9 100644 --- a/modules/core/_common.js +++ b/modules/core/_common.js @@ -59,7 +59,7 @@ function _generateAccessors(pspec, propdesc, GObject) { return propdesc; } -function _checkAccessors(proto, pspec, GObject) { +function _checkAccessors(proto, pspec, GObject, {generateAccessors = true, accessorMappingSymbol = null} = {}) { const {name, flags} = pspec; if (flags & GObject.ParamFlags.CONSTRUCT_ONLY) return; @@ -83,13 +83,25 @@ function _checkAccessors(proto, pspec, GObject) { if (!propdesc || (readable && !propdesc.get) || (writable && !propdesc.set)) propdesc = _generateAccessors(pspec, propdesc, GObject); - if (!dashPropdesc) - Object.defineProperty(proto, name, propdesc); - if (nameIsCompound) { - if (!underscorePropdesc) - Object.defineProperty(proto, underscoreName, propdesc); - if (!camelPropdesc) - Object.defineProperty(proto, camelName, propdesc); + if (generateAccessors) { + if (!dashPropdesc) + Object.defineProperty(proto, name, propdesc); + if (nameIsCompound) { + if (!underscorePropdesc) + Object.defineProperty(proto, underscoreName, propdesc); + if (!camelPropdesc) + Object.defineProperty(proto, camelName, propdesc); + } + } + + if (accessorMappingSymbol) { + proto[accessorMappingSymbol] = proto[accessorMappingSymbol] ?? {}; + if (nameIsCompound) { + proto[accessorMappingSymbol][underscoreName] = propdesc; + proto[accessorMappingSymbol][camelName] = propdesc; + } else { + proto[accessorMappingSymbol][name] = propdesc; + } } } diff --git a/modules/core/overrides/GObject.js b/modules/core/overrides/GObject.js index f7817e80..1b5fccdc 100644 --- a/modules/core/overrides/GObject.js +++ b/modules/core/overrides/GObject.js @@ -23,6 +23,8 @@ 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 _mapWidgetDefinitionToClass(klass, metaInfo) { if ('CssName' in metaInfo) klass[_gtkCssName] = metaInfo.CssName; @@ -351,12 +353,12 @@ function _checkInterface(iface, proto) { } } -function _checkProperties(klass) { +function _checkProperties(klass, {generateAccessors}) { if (!klass.hasOwnProperty(properties)) return; for (let pspec of Object.values(klass[properties])) - _checkAccessors(klass.prototype, pspec, GObject); + _checkAccessors(klass.prototype, pspec, GObject, {generateAccessors, accessorMappingSymbol: _accessorMapping}); } function _init() { @@ -612,6 +614,17 @@ function _init() { }); definePublicProperties(GObject.Object, { + register(classDefinition = {}) { + // Ensure the class derives from GObject.Object or + // GObject.Interface + _assertDerivesFromGObject(this, [GObject.Object], 'Object.register'); + + _mapTypeDefinition(this, classDefinition); + _mapClassDefinition(this, classDefinition); + _checkProperties(this, {generateAccessors: false}); + + this[_registerType](); + }, implements(iface) { if (iface.$gtype) return GObject.type_is_a(this, iface.$gtype); @@ -662,7 +675,7 @@ function _init() { }); GObject.Object._classInit = function (klass) { - _checkProperties(klass); + _checkProperties(klass, {generateAccessors: true}); if (_registerType in klass) klass[_registerType](); @@ -680,6 +693,19 @@ function _init() { return false; } + definePublicProperties(GObject.Interface, { + register(classDefinition = {}) { + // Ensure the class derives from GObject.Interface + _assertDerivesFromGObject(this, [GObject.Interface], 'Interface.register'); + + _mapTypeDefinition(this, classDefinition); + _mapInterfaceDefinition(this, classDefinition); + _checkProperties(this, {generateAccessors: false}); + + this[_registerType](); + }, + }); + definePrivateProperties(GObject.Interface, { [_registerType]() { let klass = this; diff --git a/modules/core/overrides/Gtk.js b/modules/core/overrides/Gtk.js index 90deefb7..3dac25b5 100644 --- a/modules/core/overrides/Gtk.js +++ b/modules/core/overrides/Gtk.js @@ -4,11 +4,48 @@ const Legacy = imports._legacy; const {Gio, GjsPrivate, GObject} = imports.gi; -const {_registerType, definePrivateProperties} = imports._common; +const {_registerType, definePublicProperties, definePrivateProperties} = imports._common; let Gtk; let BuilderScope; +const _hasTemplateSymbol = Symbol('GTK Widget has template'); +const _defineChildrenAtInitSymbol = Symbol('GTK Widget assign children to properties at init'); + +function _hasTemplate(constructor) { + return constructor.hasOwnProperty(_hasTemplateSymbol) && + constructor[_hasTemplateSymbol]; +} + +function _setHasTemplate(klass) { + definePrivateProperties(klass, { + [_hasTemplateSymbol]: true, + }); +} + +function _shouldDefineChildrenDuringInit(constructor) { + return constructor.hasOwnProperty(_defineChildrenAtInitSymbol) && + constructor[_defineChildrenAtInitSymbol]; +} + +function _mapWidgetDefinitionToClass(klass, classDefinition) { + if ('CssName' in classDefinition) + klass[Gtk.cssName] = classDefinition.CssName; + if ('Template' in classDefinition) + klass[Gtk.template] = classDefinition.Template; + if ('Children' in classDefinition) + klass[Gtk.children] = classDefinition.Children; + if ('InternalChildren' in classDefinition) + klass[Gtk.internalChildren] = classDefinition.InternalChildren; +} + +function _assertDerivesFromWidget(klass, functionName) { + if (!Gtk.Widget.prototype.isPrototypeOf(klass.prototype)) { + throw new TypeError(`Gtk.${functionName}() used with invalid base ` + + `class (is ${Object.getPrototypeOf(klass).name ?? klass})`); + } +} + function defineChildren(instance, constructor, target = instance) { let children = constructor[Gtk.children] || []; for (let child of children) { @@ -31,7 +68,47 @@ function _init() { Gtk.internalChildren = GObject.__gtkInternalChildren__; Gtk.template = GObject.__gtkTemplate__; - let {GtkWidgetClass} = Legacy.defineGtkLegacyObjects(GObject, Gtk); + let {GtkWidgetClass} = Legacy.defineGtkLegacyObjects(GObject, Gtk, _defineChildrenAtInitSymbol); + + // Gtk.Widget instance additions + definePublicProperties(Gtk.Widget.prototype, { + _instance_init() { + if (_hasTemplate(this.constructor)) + this.init_template(); + }, + }); + + // Gtk.Widget static overrides + const {set_template, set_template_from_resource} = Gtk.Widget; + + Object.assign(Gtk.Widget, { + set_template(contents) { + set_template.call(this, contents); + + _setHasTemplate(this); + }, + set_template_from_resource(resource) { + set_template_from_resource.call(this, resource); + + _setHasTemplate(this); + }, + }); + + // Gtk.Widget static additions + definePublicProperties(Gtk.Widget, { + register(classDefinition) { + _assertDerivesFromWidget(this, 'Widget.register()'); + + _mapWidgetDefinitionToClass(this, classDefinition); + + definePrivateProperties(this, { + [_defineChildrenAtInitSymbol]: false, + }); + + GObject.Object.register.call(this, classDefinition); + }, + }); + Gtk.Widget.prototype.__metaclass__ = GtkWidgetClass; if (Gtk.Container && Gtk.Container.prototype.child_set_property) { @@ -43,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) => { @@ -61,13 +138,17 @@ function _init() { wrapper = GObject.Object.prototype._init.call(wrapper, params) ?? wrapper; - if (wrapper.constructor[Gtk.template]) + if (_hasTemplate(wrapper.constructor) && _shouldDefineChildrenDuringInit(wrapper.constructor)) defineChildren(this, wrapper.constructor); return wrapper; }; Gtk.Widget._classInit = function (klass) { + definePrivateProperties(klass, { + [_defineChildrenAtInitSymbol]: true, + }); + return GObject.Object._classInit(klass); }; diff --git a/modules/script/_legacy.js b/modules/script/_legacy.js index 135d2517..efd2e314 100644 --- a/modules/script/_legacy.js +++ b/modules/script/_legacy.js @@ -646,7 +646,7 @@ function defineGObjectLegacyObjects(GObject) { return {GObjectMeta, GObjectInterface}; } -function defineGtkLegacyObjects(GObject, Gtk) { +function defineGtkLegacyObjects(GObject, Gtk, defineChildrenAtInitSymbol) { 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, defineChildrenAtInitSymbol, { + 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); |