[gjs/ewlsh/register-type] Implement GObject.registerType




commit 544daa6a7f9a000d9a33517b567b2131c31de6bc
Author: Evan Welsh <contact evanwelsh com>
Date:   Wed Mar 31 19:41:19 2021 -0700

    Implement GObject.registerType

 gi/cwrapper.cpp                   |  4 ++-
 gi/gtype.cpp                      |  3 ++
 gi/object.cpp                     | 26 +++++++++++++--
 gi/private.cpp                    |  4 +++
 gi/wrapperutils.h                 | 35 ++++++++++++++++++--
 gjs/atoms.h                       |  2 ++
 modules/core/overrides/GObject.js | 68 ++++++++++++++++++++++++++++++++++++++-
 7 files changed, 135 insertions(+), 7 deletions(-)
---
diff --git a/gi/cwrapper.cpp b/gi/cwrapper.cpp
index f5fbff8a..a9fd6557 100644
--- a/gi/cwrapper.cpp
+++ b/gi/cwrapper.cpp
@@ -24,5 +24,7 @@ bool gjs_wrapper_define_gtype_prop(JSContext* cx, JS::HandleObject constructor,
 
     const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
     return JS_DefinePropertyById(cx, constructor, atoms.gtype(), gtype_obj,
-                                 JSPROP_PERMANENT);
+                                 JSPROP_PERMANENT) &&
+           JS_DefinePropertyById(cx, constructor, atoms.gobject_type(),
+                                 gtype_obj, JSPROP_PERMANENT);
 }
diff --git a/gi/gtype.cpp b/gi/gtype.cpp
index 7ccb8099..aa2d665a 100644
--- a/gi/gtype.cpp
+++ b/gi/gtype.cpp
@@ -129,6 +129,9 @@ class GTypeObj : public CWrapper<GTypeObj, void> {
         // property on that and hope it's a GType wrapper object
         if (!JS_GetPropertyById(cx, object, atoms.gtype(), &v_gtype))
             return false;
+        if (!v_gtype.isObject() &&
+            !JS_GetPropertyById(cx, object, atoms.gobject_type(), &v_gtype))
+            return false;
         if (!v_gtype.isObject()) {
             // OK, so we're not a class. But maybe we're an instance. Check for
             // "constructor" and recurse on that.
diff --git a/gi/object.cpp b/gi/object.cpp
index 39ce7f5f..294d77d3 100644
--- a/gi/object.cpp
+++ b/gi/object.cpp
@@ -1622,6 +1622,10 @@ bool ObjectInstance::constructor_impl(JSContext* context,
     if (!JS_HasOwnPropertyById(context, rooted_target, gjs->atoms().gtype(),
                                &has_gtype))
         return false;
+    if (!has_gtype &&
+        !JS_HasOwnPropertyById(context, rooted_target,
+                               gjs->atoms().gobject_type(), &has_gtype))
+        return false;
 
     if (!has_gtype) {
         gjs_throw(context,
@@ -1631,9 +1635,25 @@ bool ObjectInstance::constructor_impl(JSContext* context,
         return false;
     }
 
-    return gjs_object_require_property(context, object, "GObject instance",
-                                       gjs->atoms().init(), &initer) &&
-           gjs->call_function(object, initer, argv, argv.rval());
+    // If our instance is an instance of &klass it is an instance of a native
+    // class.
+    if (JS_InstanceOf(context, object, &klass, nullptr)) {
+        return gjs_object_require_property(context, object, "GObject instance",
+                                           gjs->atoms().init(), &initer) &&
+               gjs->call_function(object, initer, argv, argv.rval());
+        // Otherwise it is a JS-based class and we should call the constructor
+        // directly.
+    } else {
+        // TODO: This needs error guards.
+        ObjectBase* priv = ObjectBase::for_js(context, object);
+
+        if (!priv->check_is_instance(context, "initialize"))
+            return false;
+
+        JS::RootedObject obj(context, object);
+
+        return priv->to_instance()->init_impl(context, argv, &obj);
+    }
 }
 
 void ObjectInstance::trace_impl(JSTracer* tracer) {
diff --git a/gi/private.cpp b/gi/private.cpp
index 935295bb..0cdfbb81 100644
--- a/gi/private.cpp
+++ b/gi/private.cpp
@@ -413,6 +413,10 @@ static JSFunctionSpec module_funcs[] = {
 };
 
 static JSPropertySpec module_props[] = {
+    JS_PSG("gobject_prototype_symbol",
+           symbol_getter<&GjsAtoms::gobject_prototype>, GJS_MODULE_PROP_FLAGS),
+    JS_PSG("gobject_type_symbol", symbol_getter<&GjsAtoms::gobject_type>,
+           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 0febd1bc..9ad32474 100644
--- a/gi/wrapperutils.h
+++ b/gi/wrapperutils.h
@@ -429,8 +429,23 @@ 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");
+
+        JS::RootedValue gproto(cx);
+
+        bool has_property = false;
+
+        auto gjs_cx = GjsContextPrivate::from_cx(cx);
+        auto atoms = gjs_cx->atoms();
+        if (!JS_HasOwnPropertyById(cx, proto, atoms.gobject_prototype(),
+                                   &has_property))
+            return false;
+
+        if (JS_GetClass(proto) != &Base::klass &&
+            (!has_property ||
+             !JS_GetPropertyById(cx, proto, atoms.gobject_prototype(),
+                                 &gproto) ||
+             !gproto.isObject())) {
+            gjs_throw(cx, "Tried to construct an object without a GType!");
             return false;
         }
 
@@ -896,6 +911,22 @@ class GIWrapperPrototype : public Base {
                                                      JS::HandleObject wrapper) {
         JS::RootedObject proto(cx);
         JS_GetPrototype(cx, wrapper, &proto);
+
+        if (JS_GetClass(proto) != &Base::klass) {
+            JS::RootedValue gproto(cx);
+
+            auto priv = GjsContextPrivate::from_cx(cx);
+            auto atoms = priv->atoms();
+
+            if (JS_GetPropertyById(cx, proto, atoms.gobject_prototype(),
+                                   &gproto) &&
+                gproto.isObject()) {
+                proto.set(&gproto.toObject());
+            }
+
+            // TODO(ewlsh): Handle assertions with errors instead.
+        }
+
         Base* retval = Base::for_js(cx, proto);
         g_assert(retval);
         return retval->to_prototype();
diff --git a/gjs/atoms.h b/gjs/atoms.h
index 25c9904d..e33a66e0 100644
--- a/gjs/atoms.h
+++ b/gjs/atoms.h
@@ -71,6 +71,8 @@ class JSTracer;
     macro(y, "y")
 
 #define FOR_EACH_SYMBOL_ATOM(macro) \
+    macro(gobject_type, "__GObject__type") \
+    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/modules/core/overrides/GObject.js b/modules/core/overrides/GObject.js
index 8058a2ec..8aecf13b 100644
--- a/modules/core/overrides/GObject.js
+++ b/modules/core/overrides/GObject.js
@@ -78,6 +78,60 @@ function registerClass(...args) {
     return initclass._classInit(klass);
 }
 
+function registerType(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] : [];
+
+    propertiesArray.forEach(pspec => _checkAccessors(klass.prototype, pspec, GObject));
+
+    let registered_type = Gi.register_type(parent[Gi.gobject_prototype_symbol] ?? parent.prototype, 
gtypename, gflags,
+        gobjectInterfaces, propertiesArray);
+
+    // TODO: Construct a better "wrapper" than the registered prototype
+    // TODO: Avoid discarding a constructed constructor
+    klass[Gi.gobject_prototype_symbol] = registered_type.prototype;
+    klass.prototype[Gi.gobject_prototype_symbol] = registered_type.prototype;
+    klass[Gi.gobject_type_symbol] = registered_type[Gi.gobject_type_symbol];
+    // klass.prototype[Gi.gobject_type_symbol] = registered_type[Gi.gobject_type_symbol];
+
+    _createSignals(klass[Gi.gobject_type_symbol], gobjectSignals);
+
+    gobjectInterfaces.forEach(iface => _copyAllDescriptorsNoOverwrite(klass.prototype, iface.prototype));
+
+    Object.getOwnPropertyNames(klass.prototype)
+    .filter(name => name.startsWith('vfunc_') || name.startsWith('on_'))
+    .forEach(name => {
+        let descr = Object.getOwnPropertyDescriptor(klass.prototype, name);
+        if (typeof descr.value !== 'function')
+            return;
+
+        let func = klass.prototype[name];
+
+        if (name.startsWith('vfunc_')) {
+            klass[Gi.gobject_prototype_symbol][Gi.hook_up_vfunc_symbol](name.slice(6), func);
+        } else if (name.startsWith('on_')) {
+            let id = GObject.signal_lookup(name.slice(3).replace('_', '-'),
+                klass[Gi.gobject_type_symbol]);
+            if (id !== 0) {
+                GObject.signal_override_class_closure(id, klass[Gi.gobject_type_symbol], function 
(...argArray) {
+                    let emitter = argArray.shift();
+
+                    return func.apply(emitter, argArray);
+                });
+            }
+        }
+    });
+
+    gobjectInterfaces.forEach(iface =>
+        _checkInterface(iface, klass.prototype));
+
+    return registered_type[Gi.gobject_type_symbol];
+}
+
 // Some common functions between GObject.Class and GObject.Interface
 
 function _createSignals(gtype, sigs) {
@@ -176,6 +230,17 @@ function _copyAllDescriptors(target, source, filter) {
     });
 }
 
+function _copyAllDescriptorsNoOverwrite(target, source) {
+    [...Object.getOwnPropertyNames(source), ...Object.getOwnPropertySymbols(source)]
+    .forEach(key => {
+        if (!target[key]) {
+            let descriptor = Object.getOwnPropertyDescriptor(source, key);
+
+            Object.defineProperty(target, key, descriptor);
+        }
+    });
+}
+
 function _interfacePresent(required, klass) {
     if (!klass[interfaces])
         return false;
@@ -430,6 +495,7 @@ function _init() {
     };
 
     GObject.registerClass = registerClass;
+    GObject.registerType = registerType;
 
     GObject.Object._classInit = function (klass) {
         let gtypename = _createGTypeName(klass);
@@ -441,7 +507,7 @@ function _init() {
 
         propertiesArray.forEach(pspec => _checkAccessors(klass.prototype, pspec, GObject));
 
-        let newClass = Gi.register_type(parent.prototype, gtypename, gflags,
+        let newClass = Gi.register_type(parent[Gi.gobject_prototype_symbol] ?? parent.prototype, gtypename, 
gflags,
             gobjectInterfaces, propertiesArray);
         Object.setPrototypeOf(newClass, parent);
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]