Skip to content

Support class fields and class references in GObject subclasses

Evan Welsh requested to merge ewlsh/wrap-es-class into master

Depends on !701 (merged)

Example

class MyObject {
    // Fields now work
    specialField = 5;

    doSomething() {
        console.log(this.specialField);
    }

    static new_special() {
        // Class self-references now work
        return new MyObject({ ... });
    }

};

GObject.registerClass(MyObject);

Background

This MR allows classes passed to GObject.registerClass to reference themselves and use class fields.

Instead of copying descriptors to a new, native class in registerClass, we can instead "wrap" the existing class constructor in the relevant GObject mechanics.

Because constructors chain and can return arbitrary objects, we rely on the GObject.Object constructor to return a native ObjectInstance which the child class can extend.

The native ObjectPrototype is stored in a private symbol on the JavaScript prototype and is used to resolve internally.

class MyObject extends GObject.Object {
    constructor(...params) {
        log("1");
        super(...params);
        log("5");
    }

    _init(...params) {
        log("2");
        super._init(...params);
        log("4");
    }

    _instance_init(...params) {
        log("3");  
    }
}
GObject.registerType(MyObject);
const myobj = new MyObject();

Lifecycle Hooks

Historically classes passed to GObject.registerClass have relied on the ability to override or replace the existing constructor, this creates issues with newer JS class features like fields because they are installed after parent constructors. In the below example myfield is initialized after the super() call so any GObject mechanics triggered in _init are overridden.

class MyObject extends GObject.Object {
    myfield;

    constructor(...params) {
        log("1");
        super(...params);
        log("5");
    }

    _init(...params) {
        log("2");
        super._init(...params);
        log("4");
    }

    _instance_init(...params) {
        log("3");  
    }
}

GObject.registerClass(MyObject);

const myobj = new MyObject();
  1. constructor is called
  2. super() triggers the parent constructor which calls _init (_init must chain still!)
  3. super._init triggers _instance_init
  4. _init continues
  5. constructor continues with a fully initialized object (fields applied)

Implementation Details

Before GObject.registerClass(TestObject)

class TestObject extends GObject.Object {

}

After GObject.registerClass(TestObject)

class TestObject extends GObject.Object {
    static get $gtype() { return {Native GType Object} }
}
TestObject.prototype[Gi.gobject_prototype_symbol] = {Native Prototype Object}

Fixes #331 (closed)

Edited by Evan Welsh

Merge request reports