[gjs/ewlsh/register-type] Support class fields




commit 8f941ef163267674942bb20b3d865817f11c8b2e
Author: Evan Welsh <contact evanwelsh com>
Date:   Thu Dec 30 06:00:35 2021 -0800

    Support class fields

 gi/boxed.cpp                           |   4 +-
 gi/boxed.h                             |   2 +-
 gi/cwrapper.cpp                        |   2 +
 gi/cwrapper.h                          |   6 +-
 gi/fundamental.cpp                     |   5 +-
 gi/fundamental.h                       |   3 +-
 gi/gerror.cpp                          |   4 +-
 gi/gerror.h                            |   2 +-
 gi/gtype.cpp                           |   5 +-
 gi/interface.cpp                       | 100 +++++++-
 gi/interface.h                         |  10 +-
 gi/object.cpp                          |  32 ++-
 gi/object.h                            |   3 +-
 gi/private.cpp                         | 189 +++++++++++---
 gi/union.cpp                           |   4 +-
 gi/union.h                             |   1 +
 gi/wrapperutils.h                      | 135 ++++++++--
 gjs/atoms.h                            |   1 +
 installed-tests/js/testGObjectClass.js |  63 +++++
 modules/core/_common.js                |  12 +-
 modules/core/overrides/GObject.js      | 441 ++++++++++++++++++++++++---------
 modules/core/overrides/Gtk.js          | 206 +++++++++++----
 modules/script/_legacy.js              | 112 +++++----
 23 files changed, 1038 insertions(+), 304 deletions(-)
---
diff --git a/gi/boxed.cpp b/gi/boxed.cpp
index 531544df..92af1366 100644
--- a/gi/boxed.cpp
+++ b/gi/boxed.cpp
@@ -39,8 +39,8 @@
 #include "gjs/mem-private.h"
 #include "util/log.h"
 
-BoxedInstance::BoxedInstance(JSContext* cx, JS::HandleObject obj)
-    : GIWrapperInstance(cx, obj),
+BoxedInstance::BoxedInstance(BoxedPrototype* prototype, JS::HandleObject obj)
+    : GIWrapperInstance(prototype, obj),
       m_allocated_directly(false),
       m_owning_ptr(false) {
     GJS_INC_COUNTER(boxed_instance);
diff --git a/gi/boxed.h b/gi/boxed.h
index fc053a6f..29a0d9d6 100644
--- a/gi/boxed.h
+++ b/gi/boxed.h
@@ -164,7 +164,7 @@ class BoxedInstance
     bool m_owning_ptr : 1;  // if set, the JS wrapper owns the C memory referred
                             // to by m_ptr.
 
-    explicit BoxedInstance(JSContext* cx, JS::HandleObject obj);
+    explicit BoxedInstance(BoxedPrototype* prototype, JS::HandleObject obj);
     ~BoxedInstance(void);
 
     // Don't set GIWrapperBase::m_ptr directly. Instead, use one of these
diff --git a/gi/cwrapper.cpp b/gi/cwrapper.cpp
index f5fbff8a..b38cf0a8 100644
--- a/gi/cwrapper.cpp
+++ b/gi/cwrapper.cpp
@@ -23,6 +23,8 @@ bool gjs_wrapper_define_gtype_prop(JSContext* cx, JS::HandleObject constructor,
         return false;
 
     const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
+
+    // Define the public-facing $gtype property.
     return JS_DefinePropertyById(cx, constructor, atoms.gtype(), gtype_obj,
                                  JSPROP_PERMANENT);
 }
diff --git a/gi/cwrapper.h b/gi/cwrapper.h
index 436eefd2..d5fbd6f9 100644
--- a/gi/cwrapper.h
+++ b/gi/cwrapper.h
@@ -290,8 +290,10 @@ class CWrapper : public CWrapperPointerOps<Base, Wrapped> {
      * CWrapper::define_gtype_prop:
      *
      * This function can be used as the finishInit member of class_ops. It
-     * defines a '$gtype' property on the constructor. If you use it, you must
-     * implement a gtype() static method that returns the GType to define.
+     * defines two properties on the constructor. One using the internal
+     * gtype symbol and a public-facing property, '$gtype'. If you use it,
+     * you must implement a gtype() static method that returns the GType to
+     * define.
      */
     GJS_JSAPI_RETURN_CONVENTION
     static bool define_gtype_prop(JSContext* cx, JS::HandleObject ctor,
diff --git a/gi/fundamental.cpp b/gi/fundamental.cpp
index 72b69259..b776fdc1 100644
--- a/gi/fundamental.cpp
+++ b/gi/fundamental.cpp
@@ -33,8 +33,9 @@ namespace JS {
 class CallArgs;
 }
 
-FundamentalInstance::FundamentalInstance(JSContext* cx, JS::HandleObject obj)
-    : GIWrapperInstance(cx, obj) {
+FundamentalInstance::FundamentalInstance(FundamentalPrototype* prototype,
+                                         JS::HandleObject obj)
+    : GIWrapperInstance(prototype, obj) {
     GJS_INC_COUNTER(fundamental_instance);
 }
 
diff --git a/gi/fundamental.h b/gi/fundamental.h
index 7d2f8de0..3d7caef6 100644
--- a/gi/fundamental.h
+++ b/gi/fundamental.h
@@ -144,7 +144,8 @@ class FundamentalInstance
     friend class GIWrapperBase<FundamentalBase, FundamentalPrototype,
                                FundamentalInstance>;
 
-    explicit FundamentalInstance(JSContext* cx, JS::HandleObject obj);
+    explicit FundamentalInstance(FundamentalPrototype* prototype,
+                                 JS::HandleObject obj);
     ~FundamentalInstance(void);
 
     // Helper methods
diff --git a/gi/gerror.cpp b/gi/gerror.cpp
index 2e62cddb..737de4ce 100644
--- a/gi/gerror.cpp
+++ b/gi/gerror.cpp
@@ -43,8 +43,8 @@ ErrorPrototype::ErrorPrototype(GIEnumInfo* info, GType gtype)
 
 ErrorPrototype::~ErrorPrototype(void) { GJS_DEC_COUNTER(gerror_prototype); }
 
-ErrorInstance::ErrorInstance(JSContext* cx, JS::HandleObject obj)
-    : GIWrapperInstance(cx, obj) {
+ErrorInstance::ErrorInstance(ErrorPrototype* prototype, JS::HandleObject obj)
+    : GIWrapperInstance(prototype, obj) {
     GJS_INC_COUNTER(gerror_instance);
 }
 
diff --git a/gi/gerror.h b/gi/gerror.h
index 8841a994..c2224889 100644
--- a/gi/gerror.h
+++ b/gi/gerror.h
@@ -128,7 +128,7 @@ class ErrorInstance : public GIWrapperInstance<ErrorBase, ErrorPrototype,
                                    GError>;
     friend class GIWrapperBase<ErrorBase, ErrorPrototype, ErrorInstance>;
 
-    explicit ErrorInstance(JSContext* cx, JS::HandleObject obj);
+    explicit ErrorInstance(ErrorPrototype* prototype, JS::HandleObject obj);
     ~ErrorInstance(void);
 
  public:
diff --git a/gi/gtype.cpp b/gi/gtype.cpp
index 7ccb8099..c7f67353 100644
--- a/gi/gtype.cpp
+++ b/gi/gtype.cpp
@@ -125,8 +125,9 @@ class GTypeObj : public CWrapper<GTypeObj, void> {
 
         JS::RootedValue v_gtype(cx);
 
-        // OK, we don't have a GType wrapper object -- grab the "$gtype"
-        // property on that and hope it's a GType wrapper object
+        // If the object is not a GType wrapper object, grab the gtype
+        // symbol property, this symbol should always point to a GType
+        // wrapper object.
         if (!JS_GetPropertyById(cx, object, atoms.gtype(), &v_gtype))
             return false;
         if (!v_gtype.isObject()) {
diff --git a/gi/interface.cpp b/gi/interface.cpp
index 925097e8..c66a6199 100644
--- a/gi/interface.cpp
+++ b/gi/interface.cpp
@@ -8,8 +8,12 @@
 #include <girepository.h>
 
 #include <js/Class.h>
+#include <js/Id.h>  // for JSID_VOID, PropertyKey, jsid
 #include <js/TypeDecls.h>
 #include <js/Utility.h>  // for UniqueChars
+#include <jsapi.h>       // for JS_ReportOutOfMemory
+
+#include <utility>  // for forward
 
 #include "gi/function.h"
 #include "gi/interface.h"
@@ -31,6 +35,100 @@ InterfacePrototype::~InterfacePrototype(void) {
     GJS_DEC_COUNTER(interface);
 }
 
+bool InterfacePrototype::new_enumerate_impl(
+    JSContext* cx, JS::HandleObject obj [[maybe_unused]],
+    JS::MutableHandleIdVector properties,
+    bool only_enumerable [[maybe_unused]]) {
+    unsigned n_interfaces;
+    GType* interfaces = g_type_interfaces(gtype(), &n_interfaces);
+
+    for (unsigned k = 0; k < n_interfaces; k++) {
+        GjsAutoInterfaceInfo iface_info =
+            g_irepository_find_by_gtype(nullptr, interfaces[k]);
+
+        if (!iface_info) {
+            continue;
+        }
+
+        int n_methods = g_interface_info_get_n_methods(iface_info);
+        int n_properties = g_interface_info_get_n_properties(iface_info);
+        if (!properties.reserve(properties.length() + n_methods +
+                                n_properties)) {
+            JS_ReportOutOfMemory(cx);
+            return false;
+        }
+
+        // Methods
+        for (int i = 0; i < n_methods; i++) {
+            GjsAutoFunctionInfo meth_info =
+                g_interface_info_get_method(iface_info, i);
+            GIFunctionInfoFlags flags = g_function_info_get_flags(meth_info);
+
+            if (flags & GI_FUNCTION_IS_METHOD) {
+                const char* name = meth_info.name();
+                jsid id = gjs_intern_string_to_id(cx, name);
+                if (id == JSID_VOID)
+                    return false;
+                properties.infallibleAppend(id);
+            }
+        }
+
+        // Properties
+        for (int i = 0; i < n_properties; i++) {
+            GjsAutoPropertyInfo prop_info =
+                g_interface_info_get_property(iface_info, i);
+
+            GjsAutoChar js_name = gjs_hyphen_to_underscore(prop_info.name());
+
+            jsid id = gjs_intern_string_to_id(cx, js_name);
+            if (id == JSID_VOID)
+                return false;
+            properties.infallibleAppend(id);
+        }
+    }
+
+    g_free(interfaces);
+
+    if (info()) {
+        int n_methods = g_interface_info_get_n_methods(info());
+        int n_properties = g_interface_info_get_n_properties(info());
+        if (!properties.reserve(properties.length() + n_methods +
+                                n_properties)) {
+            JS_ReportOutOfMemory(cx);
+            return false;
+        }
+
+        // Methods
+        for (int i = 0; i < n_methods; i++) {
+            GjsAutoFunctionInfo meth_info =
+                g_interface_info_get_method(info(), i);
+            GIFunctionInfoFlags flags = g_function_info_get_flags(meth_info);
+
+            if (flags & GI_FUNCTION_IS_METHOD) {
+                const char* name = meth_info.name();
+                jsid id = gjs_intern_string_to_id(cx, name);
+                if (id == JSID_VOID)
+                    return false;
+                properties.infallibleAppend(id);
+            }
+        }
+
+        // Properties
+        for (int i = 0; i < n_properties; i++) {
+            GjsAutoPropertyInfo prop_info =
+                g_interface_info_get_property(info(), i);
+
+            GjsAutoChar js_name = gjs_hyphen_to_underscore(prop_info.name());
+            jsid id = gjs_intern_string_to_id(cx, js_name);
+            if (id == JSID_VOID)
+                return false;
+            properties.infallibleAppend(id);
+        }
+    }
+
+    return true;
+}
+
 // See GIWrapperBase::resolve().
 bool InterfacePrototype::resolve_impl(JSContext* context, JS::HandleObject obj,
                                       JS::HandleId id, bool* resolved) {
@@ -111,7 +209,7 @@ const struct JSClassOps InterfaceBase::class_ops = {
     nullptr,  // addProperty
     nullptr,  // deleteProperty
     nullptr,  // enumerate
-    nullptr,  // newEnumerate
+    &InterfaceBase::new_enumerate,
     &InterfaceBase::resolve,
     nullptr,  // mayResolve
     &InterfaceBase::finalize,
diff --git a/gi/interface.h b/gi/interface.h
index 490b7093..a6d14425 100644
--- a/gi/interface.h
+++ b/gi/interface.h
@@ -92,6 +92,11 @@ class InterfacePrototype
     bool resolve_impl(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
                       bool* resolved);
 
+    GJS_JSAPI_RETURN_CONVENTION
+    bool new_enumerate_impl(JSContext* cx, JS::HandleObject obj,
+                            JS::MutableHandleIdVector properties,
+                            bool only_enumerable);
+
     // JS methods
 
     GJS_JSAPI_RETURN_CONVENTION
@@ -106,8 +111,9 @@ class InterfaceInstance
     friend class GIWrapperBase<InterfaceBase, InterfacePrototype,
                                InterfaceInstance>;
 
-    [[noreturn]] InterfaceInstance(JSContext* cx, JS::HandleObject obj)
-        : GIWrapperInstance(cx, obj) {
+    [[noreturn]] InterfaceInstance(InterfacePrototype* prototype,
+                                   JS::HandleObject obj)
+        : GIWrapperInstance(prototype, obj) {
         g_assert_not_reached();
     }
     [[noreturn]] ~InterfaceInstance(void) { g_assert_not_reached(); }
diff --git a/gi/object.cpp b/gi/object.cpp
index 0effe95f..e73fd76d 100644
--- a/gi/object.cpp
+++ b/gi/object.cpp
@@ -770,6 +770,7 @@ bool ObjectPrototype::resolve_no_info(JSContext* cx, JS::HandleObject obj,
                                       JS::HandleId id, bool* resolved,
                                       const char* name,
                                       ResolveWhat resolve_props) {
+    //   printf("resolve %s no info\n", name);
     guint n_interfaces;
     guint i;
 
@@ -809,6 +810,7 @@ bool ObjectPrototype::resolve_no_info(JSContext* cx, JS::HandleObject obj,
 
     for (i = 0; i < n_interfaces; i++) {
         GIInterfaceInfo* iface_info = interfaces[i];
+
         GjsAutoFunctionInfo method_info =
             g_interface_info_find_method(iface_info, name);
         if (method_info) {
@@ -1516,8 +1518,9 @@ void ObjectInstance::prepare_shutdown(void) {
         std::mem_fn(&ObjectInstance::release_native_object));
 }
 
-ObjectInstance::ObjectInstance(JSContext* cx, JS::HandleObject object)
-    : GIWrapperInstance(cx, object),
+ObjectInstance::ObjectInstance(ObjectPrototype* prototype,
+                               JS::HandleObject object)
+    : GIWrapperInstance(prototype, object),
       m_wrapper_finalized(false),
       m_gobj_disposed(false),
       m_gobj_finalized(false),
@@ -2553,6 +2556,15 @@ bool ObjectPrototype::get_parent_constructor(
     return true;
 }
 
+void ObjectPrototype::set_interfaces(GType* interface_gtypes,
+                                     uint32_t n_interface_gtypes) {
+    if (interface_gtypes) {
+        for (uint32_t n = 0; n < n_interface_gtypes; n++) {
+            m_interface_gtypes.push_back(interface_gtypes[n]);
+        }
+    }
+}
+
 /*
  * ObjectPrototype::define_class:
  * @in_object: Object where the constructor is stored, typically a repo object.
@@ -2575,11 +2587,7 @@ bool ObjectPrototype::define_class(
     if (!priv)
         return false;
 
-    if (interface_gtypes) {
-        for (uint32_t n = 0; n < n_interface_gtypes; n++) {
-            priv->m_interface_gtypes.push_back(interface_gtypes[n]);
-        }
-    }
+    priv->set_interfaces(interface_gtypes, n_interface_gtypes);
 
     JS::RootedObject parent_constructor(context);
     if (!priv->get_parent_constructor(context, &parent_constructor))
@@ -2672,11 +2680,17 @@ ObjectInstance* ObjectInstance::new_for_gobject(JSContext* cx, GObject* gobj) {
         return nullptr;
 
     JS::RootedObject obj(
-        cx, JS_NewObjectWithGivenProto(cx, JS_GetClass(proto), proto));
+        cx, JS_NewObjectWithGivenProto(cx, &ObjectBase::klass, proto));
     if (!obj)
         return nullptr;
 
-    ObjectInstance* priv = ObjectInstance::new_for_js_object(cx, obj);
+    ObjectPrototype* prototype = resolve_prototype(cx, proto);
+    if (!prototype)
+        return nullptr;
+
+    ObjectInstance* priv = new ObjectInstance(prototype, obj);
+
+    JS_SetPrivate(obj, priv);
 
     g_object_ref_sink(gobj);
     priv->associate_js_gobject(cx, obj, gobj);
diff --git a/gi/object.h b/gi/object.h
index 8bd88957..886f13ea 100644
--- a/gi/object.h
+++ b/gi/object.h
@@ -236,6 +236,7 @@ class ObjectPrototype
                           const char* name, bool* resolved);
 
  public:
+    void set_interfaces(GType* interface_gtypes, uint32_t n_interface_gtypes);
     void set_type_qdata(void);
     GJS_JSAPI_RETURN_CONVENTION
     GParamSpec* find_param_spec_from_id(JSContext* cx, JS::HandleString key);
@@ -310,7 +311,7 @@ class ObjectInstance : public GIWrapperInstance<ObjectBase, ObjectPrototype,
     /* Constructors */
 
  private:
-    ObjectInstance(JSContext* cx, JS::HandleObject obj);
+    ObjectInstance(ObjectPrototype* prototype, JS::HandleObject obj);
     ~ObjectInstance();
 
     GJS_JSAPI_RETURN_CONVENTION
diff --git a/gi/private.cpp b/gi/private.cpp
index 4a07afb1..029ff08b 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"
@@ -147,6 +148,7 @@ GJS_JSAPI_RETURN_CONVENTION
 static bool get_interface_gtypes(JSContext* cx, JS::HandleObject interfaces,
                                  uint32_t n_interfaces, GType* iface_types) {
     for (uint32_t ix = 0; ix < n_interfaces; ix++) {
+        // printf("%i find gtypes of\n", ix);
         JS::RootedValue iface_val(cx);
         if (!JS_GetElement(cx, interfaces, ix, &iface_val))
             return false;
@@ -168,24 +170,37 @@ static bool get_interface_gtypes(JSContext* cx, JS::HandleObject interfaces,
                 ix);
             return false;
         }
-
+        // printf("found %s\n", g_type_name(iface_type));
         iface_types[ix] = iface_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);
+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 +213,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 +232,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,26 +267,52 @@ 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) {
+    //  printf("adding ifaxe....\n");
     static GInterfaceInfo interface_vtable{nullptr, nullptr, nullptr};
     g_type_add_interface_static(instance_type, interface_type,
                                 &interface_vtable);
 }
 
 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 +334,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 +354,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 +367,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 +410,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 +534,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/union.cpp b/gi/union.cpp
index a91225d4..dca23592 100644
--- a/gi/union.cpp
+++ b/gi/union.cpp
@@ -29,8 +29,8 @@ UnionPrototype::UnionPrototype(GIUnionInfo* info, GType gtype)
 
 UnionPrototype::~UnionPrototype(void) { GJS_DEC_COUNTER(union_prototype); }
 
-UnionInstance::UnionInstance(JSContext* cx, JS::HandleObject obj)
-    : GIWrapperInstance(cx, obj) {
+UnionInstance::UnionInstance(UnionPrototype* prototype, JS::HandleObject obj)
+    : GIWrapperInstance(prototype, obj) {
     GJS_INC_COUNTER(union_instance);
 }
 
diff --git a/gi/union.h b/gi/union.h
index 269cf156..1f4b1864 100644
--- a/gi/union.h
+++ b/gi/union.h
@@ -65,6 +65,7 @@ class UnionInstance
     friend class GIWrapperInstance<UnionBase, UnionPrototype, UnionInstance>;
     friend class GIWrapperBase<UnionBase, UnionPrototype, UnionInstance>;
 
+    explicit UnionInstance(UnionPrototype* prototype, JS::HandleObject obj);
     explicit UnionInstance(JSContext* cx, JS::HandleObject obj);
     ~UnionInstance(void);
 
diff --git a/gi/wrapperutils.h b/gi/wrapperutils.h
index 777fb72c..cbfb1884 100644
--- a/gi/wrapperutils.h
+++ b/gi/wrapperutils.h
@@ -10,6 +10,7 @@
 
 #include <stdint.h>
 
+#include <new>
 #include <string>
 
 #include <girepository.h>
@@ -297,6 +298,45 @@ class GIWrapperBase : public CWrapperPointerOps<Base> {
     }
 
  protected:
+    /**
+     * GIWrapperBase::resolve_prototype:
+     */
+    [[nodiscard]] static Prototype* resolve_prototype(JSContext* cx,
+                                                      JS::HandleObject proto) {
+        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;
+        }
+
+        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);
+    }
+
     /*
      * GIWrapperBase::resolve:
      *
@@ -429,14 +469,14 @@ 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");
+
+        Prototype* prototype = resolve_prototype(cx, proto);
+        if (!prototype)
             return false;
-        }
 
         args.rval().setUndefined();
 
-        Instance* priv = Instance::new_for_js_object(cx, obj);
+        Instance* priv = Instance::new_for_js_object(prototype, obj);
 
         {
             std::string fullName = priv->format_name();
@@ -641,6 +681,9 @@ class GIWrapperBase : public CWrapperPointerOps<Base> {
 template <class Base, class Prototype, class Instance,
           typename Info = GIObjectInfo>
 class GIWrapperPrototype : public Base {
+    using GjsAutoPrototype =
+        GjsAutoPointer<Prototype, void, g_atomic_rc_box_release>;
+
  protected:
     // m_info may be null in the case of JS-defined types, or internal types
     // not exposed through introspection, such as GLocalFile. Not all subclasses
@@ -798,6 +841,22 @@ class GIWrapperPrototype : public Base {
             cx, constructor, m_gtype, m_info);
     }
 
+    GJS_JSAPI_RETURN_CONVENTION
+    static Prototype* create_prototype(Info* info, GType gtype) {
+        g_assert(gtype != G_TYPE_INVALID);
+
+        // We have to keep the Prototype in an arcbox because some of its
+        // members are needed in some Instance destructors, e.g. m_gtype to
+        // figure out how to free the Instance's m_ptr, and m_info to figure out
+        // how many bytes to free if it is allocated directly. Storing a
+        // refcount on the prototype is cheaper than storing pointers to m_info
+        // and m_gtype on each instance.
+        Prototype* priv = g_atomic_rc_box_new0(Prototype);
+        new (priv) Prototype(info, gtype);
+
+        return priv;
+    }
+
  public:
     /**
      * GIWrapperPrototype::create_class:
@@ -828,17 +887,8 @@ class GIWrapperPrototype : public Base {
                                    JS::MutableHandleObject constructor,
                                    JS::MutableHandleObject prototype) {
         g_assert(in_object);
-        g_assert(gtype != G_TYPE_INVALID);
 
-        // We have to keep the Prototype in an arcbox because some of its
-        // members are needed in some Instance destructors, e.g. m_gtype to
-        // figure out how to free the Instance's m_ptr, and m_info to figure out
-        // how many bytes to free if it is allocated directly. Storing a
-        // refcount on the prototype is cheaper than storing pointers to m_info
-        // and m_gtype on each instance.
-        GjsAutoPointer<Prototype, void, g_atomic_rc_box_release> priv =
-            g_atomic_rc_box_new0(Prototype);
-        new (priv) Prototype(info, gtype);
+        GjsAutoPrototype priv = create_prototype(info, gtype);
         if (!priv->init(cx))
             return nullptr;
 
@@ -873,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
 
     /*
@@ -954,11 +1043,12 @@ class GIWrapperInstance : public Base {
  protected:
     GjsSmartPointer<Wrapped> m_ptr;
 
-    explicit GIWrapperInstance(JSContext* cx, JS::HandleObject obj)
-        : Base(Prototype::for_js_prototype(cx, obj)), m_ptr(nullptr) {
+    explicit GIWrapperInstance(Prototype* prototype, JS::HandleObject obj)
+        : Base(prototype), m_ptr(nullptr) {
         Base::m_proto->acquire();
         Base::GIWrapperBase::debug_lifecycle(obj, "Instance constructor");
     }
+
     ~GIWrapperInstance(void) { Base::m_proto->release(); }
 
  public:
@@ -971,7 +1061,8 @@ class GIWrapperInstance : public Base {
     [[nodiscard]] static Instance* new_for_js_object(JSContext* cx,
                                                      JS::HandleObject obj) {
         g_assert(!JS_GetPrivate(obj));
-        auto* priv = new Instance(cx, obj);
+        Prototype* prototype = Prototype::for_js_prototype(cx, obj);
+        auto* priv = new Instance(prototype, obj);
 
         // Init the private variable before we do anything else. If a garbage
         // collection happens when calling the constructor, then this object
@@ -981,6 +1072,16 @@ class GIWrapperInstance : public Base {
         return priv;
     }
 
+    [[nodiscard]] static Instance* new_for_js_object(Prototype* prototype,
+                                                     JS::HandleObject obj) {
+        g_assert(!JS_GetPrivate(obj));
+        auto* priv = new Instance(prototype, obj);
+
+        JS_SetPrivate(obj, priv);
+
+        return priv;
+    }
+
     // Method to get an existing Instance
 
     /*
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/testGObjectClass.js b/installed-tests/js/testGObjectClass.js
index 41e7a8e9..eee57eb3 100644
--- a/installed-tests/js/testGObjectClass.js
+++ b/installed-tests/js/testGObjectClass.js
@@ -1371,3 +1371,66 @@ 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 () {
+    let myInstance;
+    beforeEach(function () {
+        myInstance = new MyObject();
+    });
+
+    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 edc70215..ef06c3d4 100644
--- a/modules/core/_common.js
+++ b/modules/core/_common.js
@@ -57,7 +57,7 @@ function _generateAccessors(pspec, propdesc, GObject) {
     return propdesc;
 }
 
-function _checkAccessors(proto, pspec, GObject) {
+function _checkAccessors(proto, pspec, GObject, sAccessorMapping) {
     const {name, flags} = pspec;
     if (flags & GObject.ParamFlags.CONSTRUCT_ONLY)
         return;
@@ -79,7 +79,7 @@ function _checkAccessors(proto, pspec, GObject) {
     const readable = flags & GObject.ParamFlags.READABLE;
     const writable = flags & GObject.ParamFlags.WRITABLE;
     if (!propdesc || (readable && !propdesc.get) || (writable && !propdesc.set))
-        propdesc = _generateAccessors(pspec, propdesc, GObject);
+        propdesc = _generateAccessors(pspec, propdesc, GObject, sAccessorMapping);
 
     if (!dashPropdesc)
         Object.defineProperty(proto, name, propdesc);
@@ -89,4 +89,12 @@ function _checkAccessors(proto, pspec, GObject) {
         if (!camelPropdesc)
             Object.defineProperty(proto, camelName, propdesc);
     }
+
+    proto[sAccessorMapping] = proto[sAccessorMapping] ?? {};
+    if (nameIsCompound) {
+        proto[sAccessorMapping][underscoreName] = propdesc;
+        proto[sAccessorMapping][camelName] = propdesc;
+    } else {
+        proto[sAccessorMapping][name] = propdesc;
+    }
 }
diff --git a/modules/core/overrides/GObject.js b/modules/core/overrides/GObject.js
index 6bfaf144..92f01911 100644
--- a/modules/core/overrides/GObject.js
+++ b/modules/core/overrides/GObject.js
@@ -23,7 +23,33 @@ var _gtkCssName = Symbol('GTK widget CSS name');
 var _gtkInternalChildren = Symbol('GTK widget template internal children');
 var _gtkTemplate = Symbol('GTK widget template');
 
+const sAccessorMapping = Symbol('GObject accessor mapping for class fields');
+
+function bindPropertyFields() {
+    const accessorMap = this.constructor.prototype[sAccessorMapping];
+
+    if (!accessorMap)
+        return;
+
+    Object.entries(accessorMap).forEach(([key, accessor]) => {
+        const descriptor = Object.getOwnPropertyDescriptor(this, key);
+
+        if (descriptor && 'value' in descriptor) {
+            const value = descriptor.value;
+
+            Object.defineProperty(this, key, accessor);
+            // Don't override the default value with undefined
+            if (value !== undefined)
+                this[key] = value;
+        }
+    });
+}
+
 function registerClass(...args) {
+    return _wrapClassWithGObjectOptions(...args);
+}
+
+function _wrapClassWithGObjectOptions(...args) {
     let klass = args[0];
     if (args.length === 2) {
         // The two-argument form is the convenient syntax without ESnext
@@ -42,40 +68,254 @@ 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;
+
+        _mapMetaInfoToClass(klass, metaInfo);
     }
 
+    _assertDerivesFromGObject(klass, 'registerClass');
+
+    if ('register' in klass)
+        klass.register();
+    else
+        // Lang.Class compatibility.
+        _resolveLegacyClassFunction(klass, 'register').call(klass);
+
+    if ('_classInit' in klass)
+        return klass._classInit(klass);
+    else
+        // Lang.Class compatibility.
+        return _resolveLegacyClassFunction(klass, '_classInit').call(klass, klass);
+}
+
+function _mapMetaInfoToClass(klass, metaInfo) {
+    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;
+}
+
+function _assertDerivesFromGObject(klass, functionName) {
     if (!(klass.prototype instanceof GObject.Object) &&
         !(klass.prototype instanceof GObject.Interface)) {
-        throw new TypeError('GObject.registerClass() used with invalid base ' +
-            `class (is ${Object.getPrototypeOf(klass).name})`);
+        throw new TypeError(`GObject.${functionName}() used with invalid base ` +
+            `class (is ${Object.getPrototypeOf(klass).name ?? klass})`);
     }
+}
 
+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 GObjectToString() {
+    if (!this.constructor)
+        return Object.prototype.toString.call(this);
+
+    const isInstance = this !== this.constructor.prototype;
+
+    let out = '';
+    if (isInstance)
+        out += '[object instance wrapper';
+    else
+        out += '[GObject prototype of';
+
+
+    try {
+        let gtype = this.constructor.$gtype;
+
+        out += ` GType:${GObject.type_name(gtype)}`;
+    } catch {
+        out += ' GType:unknown';
+    }
+
+    try {
+        out += ` jsobj@${imports.system.addressOf(this)}`;
+    } catch { }
+
+    try {
+        if (isInstance)
+            out += ` native@${imports.system.addressOfGObject(this)}`;
+    } catch { }
+
+    out += ']';
+
+    return out;
+}
+
+function _checkPropertiesArray(klass, propertiesArray) {
+    propertiesArray.forEach(pspec => _checkAccessors(klass.prototype, pspec, GObject, sAccessorMapping));
+}
+
+function getOwnProperty(obj, prop) {
+    return obj.hasOwnProperty(prop) ? obj[prop] : undefined;
+}
+
+function _wrapClass(klass) {
+    // Ensure the class derives from GObject.Object or
+    // GObject.Interface
+    _assertDerivesFromGObject(klass, 'Object.register');
+
+    const gtypename = _createGTypeName(klass);
+    const gflags = getOwnProperty(klass, GTypeFlags) ?? 0;
+    const gobjectInterfaces = getOwnProperty(klass, interfaces) ?? [];
+    const propertiesArray = _propertiesAsArray(klass);
+    const gobjectSignals = getOwnProperty(klass, signals) ?? [];
+
+    const parent = Object.getPrototypeOf(klass);
+    // Default to the GObject-specific prototype, fallback on the JS prototype.
+    const parentPrototype = parent.prototype[Gi.gobject_prototype_symbol] ?? parent.prototype;
+
+    _checkPropertiesArray(klass, propertiesArray);
+
+    const [giPrototype, registeredType] = Gi.register_type_with_class(
+        klass,
+        parentPrototype,
+        gtypename,
+        gflags,
+        gobjectInterfaces,
+        propertiesArray
+    );
+
+    _defineGType(klass, giPrototype, registeredType);
+    _createSignals(klass.$gtype, gobjectSignals);
+
+    _copyInterfacePrototypes(klass.prototype, gobjectInterfaces);
+
+
+    if (!klass.prototype.hasOwnProperty('toString'))
+        klass.prototype.toString = GObjectToString;
+
+    _hookupVFuncs(klass.prototype, klass.prototype[Gi.gobject_prototype_symbol], klass.$gtype);
+
+    _checkInterfaces(klass.prototype, gobjectInterfaces);
+}
+
+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);
+        });
+}
+
+function _wrapInterface(klass) {
+    // Ensure the class derives from GObject.Object or
+    // GObject.Interface
+    _assertDerivesFromGObject(klass, 'Interface.register');
+
+    const gtypename = _createGTypeName(klass);
+    const gobjectInterfaces = getOwnProperty(klass, requires) ?? [];
+    const propertiesArray = _propertiesAsArray(klass);
+    const gobjectSignals = getOwnProperty(klass, signals) ?? [];
+
+    let [giPrototype, registeredType] = Gi.register_interface_with_class(klass, gtypename, 
gobjectInterfaces, propertiesArray);
+
+    _defineGType(klass, giPrototype, registeredType);
+    _createSignals(klass.$gtype, gobjectSignals);
+
+    Object.defineProperty(klass, Symbol.hasInstance, {
+        value(instance) {
+            return GObject.Interface.isPrototypeOf(this) && GObject.type_is_a(instance, this);
+        },
+    });
 }
 
 // Some common functions between GObject.Class and GObject.Interface
@@ -158,21 +398,38 @@ function _createGTypeName(klass) {
 }
 
 function _propertiesAsArray(klass) {
-    let propertiesArray = [];
-    if (klass.hasOwnProperty(properties)) {
-        for (let prop in klass[properties])
-            propertiesArray.push(klass[properties][prop]);
-    }
-    return propertiesArray;
+    let propertiesObject = getOwnProperty(klass, properties) ?? [];
+    return [...Object.values(propertiesObject)];
+}
+
+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 _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);
-        Object.defineProperty(target, key, descriptor);
+function _copyInterfacePrototypes(targetPrototype, sourceInterfaces) {
+    // Reverse the interface array to give the last required interface precedence over the first.
+    const gobjectInterfaces = [...sourceInterfaces].reverse();
+
+    gobjectInterfaces.forEach(sourceInterface => {
+        _copyInterfacePrototypeDescriptors(targetPrototype, sourceInterface.prototype);
     });
 }
 
@@ -222,6 +479,12 @@ function _checkInterface(iface, proto) {
     }
 }
 
+function _checkInterfaces(prototype, gobjectInterfaces) {
+    gobjectInterfaces.forEach(interface => {
+        _checkInterface(interface, prototype);
+    });
+}
+
 function _init() {
     GObject = this;
 
@@ -236,7 +499,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);
@@ -431,98 +694,36 @@ 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] : [];
-
-        propertiesArray.forEach(pspec => _checkAccessors(klass.prototype, pspec, GObject));
-
-        let newClass = Gi.register_type(parent.prototype, gtypename, gflags,
-            gobjectInterfaces, propertiesArray);
-        Object.setPrototypeOf(newClass, parent);
-
-        _createSignals(newClass.$gtype, gobjectSignals);
-
-        _copyAllDescriptors(newClass, klass);
-        gobjectInterfaces.forEach(iface =>
-            _copyAllDescriptors(newClass.prototype, iface.prototype,
-                ['toString']));
-        _copyAllDescriptors(newClass.prototype, klass.prototype);
-
-        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];
-
-            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);
-                    });
-                }
-            }
-        });
-
-        gobjectInterfaces.forEach(iface =>
-            _checkInterface(iface, newClass.prototype));
-
-        // For backwards compatibility only. Use instanceof instead.
-        newClass.implements = function (iface) {
-            if (iface.$gtype)
-                return GObject.type_is_a(newClass.$gtype, iface.$gtype);
-            return false;
-        };
+    GObject.Object.register = function register() {
+        _wrapClass(this);
+    };
 
-        return newClass;
+    GObject.Object._classInit = function (klass) {
+        // No-op
+        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] : [];
+    GObject.Object.implements = function implements(iface) {
+        if (iface.$gtype)
+            return GObject.type_is_a(this, iface.$gtype);
+        return false;
+    };
 
-        let newInterface = Gi.register_interface(gtypename, gobjectInterfaces,
-            props);
+    GObject.Interface.register = function register() {
+        _wrapInterface(this);
+    };
 
-        _createSignals(newInterface.$gtype, gobjectSignals);
 
-        _copyAllDescriptors(newInterface, klass);
 
-        Object.getOwnPropertyNames(klass.prototype)
-        .filter(key => key !== 'constructor')
-        .concat(Object.getOwnPropertySymbols(klass.prototype))
-        .forEach(key => {
-            let descr = Object.getOwnPropertyDescriptor(klass.prototype, key);
+    GObject.Object.prototype.bindPropertyFields = bindPropertyFields;
 
-            // 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);
-                };
-            }
+    GObject.Interface._classInit = function (klass) {
+        // Only create interface generics for registerClass(...)
+        if (GObject.Interface.isPrototypeOf(klass))
+            _createInterfaceGenerics(klass);
 
-            Object.defineProperty(newInterface.prototype, key, descr);
-        });
 
-        return newInterface;
+        return klass;
     };
 
     /**
diff --git a/modules/core/overrides/Gtk.js b/modules/core/overrides/Gtk.js
index 77649a73..341f6b68 100644
--- a/modules/core/overrides/Gtk.js
+++ b/modules/core/overrides/Gtk.js
@@ -8,6 +8,19 @@ const {Gio, GjsPrivate, GObject} = imports.gi;
 let Gtk;
 let BuilderScope;
 
+const sHasTemplate = Symbol('GTK Widget has template');
+const sAssignChildren = Symbol('GTK Widget assign children at construction');
+
+function hasTemplate(constructor) {
+    return constructor.hasOwnProperty(sHasTemplate) &&
+        constructor[sHasTemplate];
+}
+
+function assignChildrenDuringInit(constructor) {
+    return constructor.hasOwnProperty(sAssignChildren) &&
+        constructor[sAssignChildren];
+}
+
 function _init() {
     Gtk = this;
 
@@ -16,7 +29,82 @@ function _init() {
     Gtk.internalChildren = GObject.__gtkInternalChildren__;
     Gtk.template = GObject.__gtkTemplate__;
 
-    let {GtkWidgetClass} = Legacy.defineGtkLegacyObjects(GObject, Gtk);
+    let {GtkWidgetClass} = Legacy.defineGtkLegacyObjects(GObject, Gtk, {sAssignChildren});
+
+    function assignChildren(instance, constructor) {
+        let children = constructor[Gtk.children] || [];
+        for (let child of children) {
+            instance[child.replace(/-/g, '_')] =
+                instance.get_template_child(constructor, child);
+        }
+
+        let internalChildren = constructor[Gtk.internalChildren] || [];
+        for (let child of internalChildren) {
+            instance[`_${child.replace(/-/g, '_')}`] =
+                instance.get_template_child(constructor, child);
+        }
+    }
+
+    Gtk.assignChildren = assignChildren;
+
+    function setHasTemplate(klass) {
+        Object.defineProperty(klass, sHasTemplate, {
+            value: true,
+            enumerable: false,
+            configurable: false,
+        });
+    }
+
+    Gtk.Widget.get_template_children = function get_template_children() {
+        let children = [this.constructor[Gtk.children] || []];
+        let map = {};
+        for (let child of children)
+            map[child.replace(/-/g, '_')] = map.get_template_child(constructor, child);
+    };
+
+    const _get_template_child = Gtk.Widget.prototype.get_template_child;
+
+    /**
+     * @type {{
+     * <T extends Function>(constructorOrName: Function | string, name?: string) => T;
+     * <T extends Function>(constructor: Function, name: string) => T;
+     * <T extends Function>(name: string) => T;
+     * }}
+     */
+    Gtk.Widget.prototype.get_template_child = function get_template_child(constructorOrName, name) {
+        if (typeof constructorOrName === 'string')
+            return _get_template_child.call(this, this.constructor, constructorOrName);
+
+
+        return _get_template_child.call(this, constructorOrName, name);
+    };
+
+    const _set_template = Gtk.Widget.set_template;
+    Gtk.Widget.set_template = function set_template(contents) {
+        _set_template.call(this, contents);
+
+        setHasTemplate(this);
+    };
+
+    const _set_template_from_resource = Gtk.Widget.set_template_from_resource;
+    Gtk.Widget.set_template_from_resource = function set_template_from_resource(resource) {
+        _set_template_from_resource.call(this, resource);
+
+        setHasTemplate(this);
+    };
+
+    Gtk.Widget.set_template_from_uri = function set_template_from_uri(template) {
+        if (template.startsWith('resource:///')) {
+            this.set_template_from_resource(template.slice(11));
+        } else if (template.startsWith('file:///')) {
+            let file = Gio.File.new_for_uri(template);
+            let [, contents] = file.load_contents(null);
+            this.set_template(contents);
+        } else {
+            throw new Error(`Invalid template Uri: ${template}`);
+        }
+    };
+
     Gtk.Widget.prototype.__metaclass__ = GtkWidgetClass;
 
     if (Gtk.Container && Gtk.Container.prototype.child_set_property) {
@@ -28,88 +116,96 @@ function _init() {
     Gtk.Widget.prototype._init = function (params) {
         let wrapper = this;
 
-        if (wrapper.constructor[Gtk.template]) {
-            if (!BuilderScope) {
-                Gtk.Widget.set_connect_func.call(wrapper.constructor,
-                    (builder, obj, signalName, handlerName, connectObj, flags) => {
-                        const swapped = flags & GObject.ConnectFlags.SWAPPED;
-                        const closure = _createClosure(
-                            builder, wrapper, handlerName, swapped, connectObj);
-
-                        if (flags & GObject.ConnectFlags.AFTER)
-                            obj.connect_after(signalName, closure);
-                        else
-                            obj.connect(signalName, closure);
-                    });
-            }
+        if (hasTemplate(wrapper.constructor) && !BuilderScope) {
+            Gtk.Widget.set_connect_func.call(wrapper.constructor,
+                (builder, obj, signalName, handlerName, connectObj, flags) => {
+                    const swapped = flags & GObject.ConnectFlags.SWAPPED;
+                    const closure = _createClosure(
+                        builder, wrapper, handlerName, swapped, connectObj);
+
+                    if (flags & GObject.ConnectFlags.AFTER)
+                        obj.connect_after(signalName, closure);
+                    else
+                        obj.connect(signalName, closure);
+                });
         }
 
         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 (hasTemplate(wrapper.constructor) && assignChildrenDuringInit(wrapper.constructor))
+            assignChildren(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];
+        // GObject.registerClass auto-assigns any specified children
+        Object.defineProperty(klass, sAssignChildren, {
+            value: true,
+            enumerable: false,
+            configurable: false,
+        });
 
-        if (template) {
-            klass.prototype._instance_init = function () {
-                this.init_template();
-            };
-        }
+        // No-op
+        return klass;
+    };
+
+    /**
+     * @this {Gtk.Widget}
+     */
+    Gtk.Widget.prototype._instance_init = function () {
+        if (hasTemplate(this.constructor))
+            this.init_template();
+    };
 
-        klass = GObject.Object._classInit(klass);
+    Gtk.Widget.register = function register() {
+        GObject.Object.register.call(this);
+
+        let template = this[Gtk.template];
+        let cssName = this[Gtk.cssName];
+        let children = this[Gtk.children];
+        let internalChildren = this[Gtk.internalChildren];
 
         if (cssName)
-            Gtk.Widget.set_css_name.call(klass, cssName);
+            Gtk.Widget.set_css_name.call(this, 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 (typeof template === 'string' &&
+                (template.startsWith('resource:///') || template.startsWith('file:///')))
+                Gtk.Widget.set_template_from_uri.call(this, template);
+            else
+                Gtk.Widget.set_template.call(this, template);
+
 
             if (BuilderScope)
-                Gtk.Widget.set_template_scope.call(klass, new BuilderScope());
+                Gtk.Widget.set_template_scope.call(this, new BuilderScope());
         }
 
         if (children) {
             children.forEach(child =>
-                Gtk.Widget.bind_template_child_full.call(klass, child, false, 0));
+                Gtk.Widget.bind_template_child_internal.call(this, child));
         }
 
         if (internalChildren) {
             internalChildren.forEach(child =>
-                Gtk.Widget.bind_template_child_full.call(klass, child, true, 0));
+                Gtk.Widget.bind_template_child.call(this, child));
         }
+    };
 
-        return klass;
+    const _bind_template_child_full = Gtk.Widget.bind_template_child_full;
+    Gtk.Widget.bind_template_child_full = function (name, isInternal = false) {
+        _bind_template_child_full.call(this, name, isInternal, 0);
+    };
+    Gtk.Widget.bind_template_child = function (name) {
+        _bind_template_child_full.call(this, name, false, 0);
     };
+    Gtk.Widget.bind_template_child_internal = function (name) {
+        _bind_template_child_full.call(this, name, true, 0);
+    };
+    Gtk.Widget.bind_template_child_internal = function (name) {
+        _bind_template_child_full.call(this, name, true, 0);
+    };
+
 
     if (Gtk.Widget.prototype.get_first_child) {
         Gtk.Widget.prototype[Symbol.iterator] = function* () {
diff --git a/modules/script/_legacy.js b/modules/script/_legacy.js
index 135d2517..72b0dba5 100644
--- a/modules/script/_legacy.js
+++ b/modules/script/_legacy.js
@@ -192,27 +192,29 @@ Class.prototype._copyPropertyDescriptor = function (params, propertyObj, key) {
 Class.prototype._init = function (params) {
     let className = params.Name;
 
-    let propertyObj = { };
+    let propertyObj = {};
 
     let interfaces = params.Implements || [];
     interfaces.forEach(iface => {
         Object.getOwnPropertyNames(iface.prototype)
-        .filter(name => !name.startsWith('__') && name !== 'constructor')
-        .filter(name => !(name in this.prototype))
-        .forEach(name => {
-            let descriptor = Object.getOwnPropertyDescriptor(iface.prototype,
-                name);
-            // writable and enumerable are inherited, see note above
-            descriptor.configurable = false;
-            propertyObj[name] = descriptor;
-        });
+            .filter(name => !name.startsWith('__') && name !== 'constructor')
+            .filter(name => !(name in this.prototype))
+            .forEach(name => {
+                let descriptor = Object.getOwnPropertyDescriptor(iface.prototype,
+                    name);
+                if (descriptor) {
+                    // writable and enumerable are inherited, see note above
+                    descriptor.configurable = false;
+                    propertyObj[name] = descriptor;
+                }
+            });
     });
 
     Object.getOwnPropertyNames(params)
-    .filter(name =>
-        ['Name', 'Extends', 'Abstract', 'Implements'].indexOf(name) === -1)
-    .concat(Object.getOwnPropertySymbols(params))
-    .forEach(this._copyPropertyDescriptor.bind(this, params, propertyObj));
+        .filter(name =>
+            ['Name', 'Extends', 'Abstract', 'Implements'].indexOf(name) === -1)
+        .concat(Object.getOwnPropertySymbols(params))
+        .forEach(this._copyPropertyDescriptor.bind(this, params, propertyObj));
 
     Object.defineProperties(this.prototype, propertyObj);
     Object.defineProperties(this.prototype, {
@@ -251,18 +253,18 @@ function _getMetaInterface(params) {
         }
         return null;
     })
-    .reduce((best, candidate) => {
-        // This function reduces to the "most derived" meta interface in the list.
-        if (best === null)
-            return candidate;
-        if (candidate === null)
-            return best;
-        for (let sup = candidate; sup; sup = sup.__super__) {
-            if (sup === best)
+        .reduce((best, candidate) => {
+            // This function reduces to the "most derived" meta interface in the list.
+            if (best === null)
                 return candidate;
-        }
-        return best;
-    }, null);
+            if (candidate === null)
+                return best;
+            for (let sup = candidate; sup; sup = sup.__super__) {
+                if (sup === best)
+                    return candidate;
+            }
+            return best;
+        }, null);
 
     // If we reach this point and we don't know the meta-interface, then it's
     // most likely because there were only pure-C interfaces listed in Requires
@@ -347,20 +349,16 @@ Interface.prototype._check = function (proto) {
         // but is not preferred because it will be the C name. The last option
         // is just so that we print something if there is garbage in Requires.
         required.prototype.__name__ || required.name || required);
-    if (unfulfilledReqs.length > 0) {
-        throw new Error(`The following interfaces must be implemented before ${
-            this.prototype.__name__}: ${unfulfilledReqs.join(', ')}`);
-    }
+    if (unfulfilledReqs.length > 0)
+        throw new Error(`The following interfaces must be implemented before ${this.prototype.__name__}: 
${unfulfilledReqs.join(', ')}`);
+
 
     // Check that this interface's required methods are implemented
     let unimplementedFns = Object.getOwnPropertyNames(this.prototype)
-    .filter(p => this.prototype[p] === Interface.UNIMPLEMENTED)
-    .filter(p => !(p in proto) || proto[p] === Interface.UNIMPLEMENTED);
-    if (unimplementedFns.length > 0) {
-        throw new Error(`The following members of ${
-            this.prototype.__name__} are not implemented yet: ${
-            unimplementedFns.join(', ')}`);
-    }
+        .filter(p => this.prototype[p] === Interface.UNIMPLEMENTED)
+        .filter(p => !(p in proto) || proto[p] === Interface.UNIMPLEMENTED);
+    if (unimplementedFns.length > 0)
+        throw new Error(`The following members of ${this.prototype.__name__} are not implemented yet: 
${unimplementedFns.join(', ')}`);
 };
 
 Interface.prototype.toString = function () {
@@ -372,25 +370,25 @@ Interface.prototype._init = function (params) {
 
     let propertyObj = {};
     Object.getOwnPropertyNames(params)
-    .filter(name => ['Name', 'Requires'].indexOf(name) === -1)
-    .forEach(name => {
-        let descriptor = Object.getOwnPropertyDescriptor(params, name);
-
-        // 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 descriptor.value === 'function') {
-            let interfaceProto = this.prototype;  // capture in closure
-            this[name] = function (thisObj, ...args) {
-                return interfaceProto[name].call(thisObj, ...args);
-            };
-        }
+        .filter(name => ['Name', 'Requires'].indexOf(name) === -1)
+        .forEach(name => {
+            let descriptor = Object.getOwnPropertyDescriptor(params, name);
+
+            // 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 descriptor.value === 'function') {
+                let interfaceProto = this.prototype;  // capture in closure
+                this[name] = function (thisObj, ...args) {
+                    return interfaceProto[name].call(thisObj, ...args);
+                };
+            }
 
-        // writable and enumerable are inherited, see note in Class._init()
-        descriptor.configurable = false;
+            // writable and enumerable are inherited, see note in Class._init()
+            descriptor.configurable = false;
 
-        propertyObj[name] = descriptor;
-    });
+            propertyObj[name] = descriptor;
+        });
 
     Object.defineProperties(this.prototype, propertyObj);
     Object.defineProperties(this.prototype, {
@@ -646,7 +644,7 @@ function defineGObjectLegacyObjects(GObject) {
     return {GObjectMeta, GObjectInterface};
 }
 
-function defineGtkLegacyObjects(GObject, Gtk) {
+function defineGtkLegacyObjects(GObject, Gtk, {sAssignChildren}) {
     const GtkWidgetClass = new Class({
         Name: 'GtkWidgetClass',
         Extends: GObject.Class,
@@ -687,6 +685,12 @@ function defineGtkLegacyObjects(GObject, Gtk) {
             this[Gtk.children] = children;
             this[Gtk.internalChildren] = internalChildren;
 
+            Object.defineProperty(this, sAssignChildren, {
+                value: true,
+                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);


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