[gjs/ewlsh/enumerable-interfaces] gi: Add enumeration hook for Interface prototypes




commit 72a4eda2ad5607b104b213c22e14279d67d057f8
Author: Evan Welsh <contact evanwelsh com>
Date:   Sun Jan 9 11:41:20 2022 -0800

    gi: Add enumeration hook for Interface prototypes
    
    This commit allows utilities in JavaScript to enumerate the methods
    available on a given interface.

 gi/interface.cpp                           | 60 +++++++++++++++++++++++++++++-
 gi/interface.h                             |  5 +++
 installed-tests/js/testGObjectInterface.js | 33 +++++++++++++++-
 3 files changed, 96 insertions(+), 2 deletions(-)
---
diff --git a/gi/interface.cpp b/gi/interface.cpp
index 925097e8..7c01403b 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,60 @@ InterfacePrototype::~InterfacePrototype(void) {
     GJS_DEC_COUNTER(interface);
 }
 
+static bool append_inferface_properties(JSContext* cx,
+                                        JS::MutableHandleIdVector properties,
+                                        GIInterfaceInfo* iface_info) {
+    int n_methods = g_interface_info_get_n_methods(iface_info);
+    if (!properties.reserve(properties.length() + n_methods)) {
+        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);
+        }
+    }
+
+    return true;
+}
+
+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;
+        }
+
+        if (!append_inferface_properties(cx, properties, iface_info))
+            return false;
+    }
+
+    g_free(interfaces);
+
+    if (!info())
+        return true;
+
+    return append_inferface_properties(cx, properties, info());
+}
+
 // See GIWrapperBase::resolve().
 bool InterfacePrototype::resolve_impl(JSContext* context, JS::HandleObject obj,
                                       JS::HandleId id, bool* resolved) {
@@ -111,7 +169,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..9630b478 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
diff --git a/installed-tests/js/testGObjectInterface.js b/installed-tests/js/testGObjectInterface.js
index cbeaa4f9..cd30479d 100644
--- a/installed-tests/js/testGObjectInterface.js
+++ b/installed-tests/js/testGObjectInterface.js
@@ -148,7 +148,7 @@ describe('GObject interface', function () {
             },
         }, class BadObject extends GObject.Object {});
         expect(() => new BadObject().requiredG())
-           .toThrowError(GObject.NotImplementedError);
+            .toThrowError(GObject.NotImplementedError);
     });
 
     it("doesn't have to have its optional function implemented", function () {
@@ -311,6 +311,37 @@ describe('GObject interface', function () {
             originalDup = Gio.File.prototype.dup;
         });
 
+        it('toString is enumerable and defined', function () {
+            expect(Object.getOwnPropertyNames(Gio.File.prototype)).toContain('toString');
+            expect(Gio.File.prototype.toString).toBeDefined();
+        });
+
+
+        it('method properties are enumerated', function () {
+            const expectedMethods = [
+                'copy_attributes',
+                'copy_async',
+                'create_async',
+                'create_readwrite_async',
+                'delete_async',
+                'enumerate_children',
+            ];
+
+            const methods = Object.getOwnPropertyNames(Gio.File.prototype);
+
+            for (const method of expectedMethods)
+                expect(methods).toContain(method);
+        });
+
+        it('method properties are defined', function () {
+            const methods = Object.getOwnPropertyNames(Gio.File.prototype);
+
+            for (const method of methods) {
+                expect(Gio.File.prototype[method]).toBeDefined();
+                expect(Gio.File.prototype[method]).toBeInstanceOf(Function);
+            }
+        });
+
         it('overrides are inherited by implementing classes', function () {
             spyOn(Gio.File.prototype, 'dup');
 


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