summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--installed-tests/js/testGObjectClass.js58
-rw-r--r--modules/core/_common.js28
-rw-r--r--modules/core/overrides/GObject.js32
-rw-r--r--modules/core/overrides/Gtk.js89
-rw-r--r--modules/script/_legacy.js9
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);