[gjs/ewlsh/fix-closures: 1/2] Support GObject.Closure boxed type and invoke()




commit fe3a9841c61a0ff7ba8c86fe5b1139a35a2818bd
Author: Evan Welsh <contact evanwelsh com>
Date:   Sat Jan 15 12:04:43 2022 -0800

    Support GObject.Closure boxed type and invoke()

 gi/arg-cache.cpp                         |  14 ++++
 gi/arg.cpp                               |  29 ++++---
 gi/boxed.cpp                             |  10 +--
 gi/private.cpp                           | 114 ++++++++++++++++++++++++++++
 installed-tests/js/.eslintrc.yml         |   3 +
 installed-tests/js/dataUtils.js          | 115 ++++++++++++++++++++++++++++
 installed-tests/js/meson.build           |   3 +-
 installed-tests/js/testGIMarshalling.js  |  14 ++--
 installed-tests/js/testGObject.js        |   2 +-
 installed-tests/js/testGObjectClosure.js |  56 ++++++++++++++
 installed-tests/js/testGObjectValue.js   | 125 +++----------------------------
 modules/core/overrides/GObject.js        |  32 ++++++++
 12 files changed, 379 insertions(+), 138 deletions(-)
---
diff --git a/gi/arg-cache.cpp b/gi/arg-cache.cpp
index 2fda580c..4079bf46 100644
--- a/gi/arg-cache.cpp
+++ b/gi/arg-cache.cpp
@@ -1166,6 +1166,20 @@ bool GClosureInTransferNone::in(JSContext* cx, GjsFunctionCallState* state,
     if (value.isNull())
         return NullableIn::in(cx, state, arg, value);
 
+    if (value.isObject()) {
+        JS::RootedObject obj(cx, &value.toObject());
+        GType gtype;
+
+        if (!gjs_gtype_get_actual_gtype(cx, obj, &gtype))
+            return false;
+
+        if (gtype == G_TYPE_CLOSURE) {
+            gjs_arg_set(arg, BoxedBase::to_c_ptr<Gjs::Closure>(cx, obj));
+            state->ignore_release.insert(arg);
+            return true;
+        }
+    }
+
     if (!(JS_TypeOfValue(cx, value) == JSTYPE_FUNCTION))
         return report_typeof_mismatch(cx, m_arg_name, value,
                                       ExpectedType::FUNCTION);
diff --git a/gi/arg.cpp b/gi/arg.cpp
index 99446bf7..9d49e6b6 100644
--- a/gi/arg.cpp
+++ b/gi/arg.cpp
@@ -1346,16 +1346,27 @@ static bool value_to_interface_gi_argument(
 
             } else if (g_type_is_a(gtype, G_TYPE_BOXED)) {
                 if (g_type_is_a(gtype, G_TYPE_CLOSURE)) {
-                    GClosure* closure = Gjs::Closure::create_marshaled(
-                        cx, JS_GetObjectFunction(obj), "boxed");
-                    // GI doesn't know about floating GClosure references. We
-                    // guess that if this is a return value going from JS::Value
-                    // to GArgument, it's intended to be passed to a C API that
-                    // will consume the floating reference.
-                    if (arg_type != GJS_ARGUMENT_RETURN_VALUE) {
-                        g_closure_ref(closure);
-                        g_closure_sink(closure);
+                    if (JS_ObjectIsFunction(obj)) {
+                        GClosure* closure = Gjs::Closure::create_marshaled(
+                            cx, JS_GetObjectFunction(obj), "boxed");
+                        // GI doesn't know about floating GClosure references.
+                        // We guess that if this is a return value going from
+                        // JS::Value to GArgument, it's intended to be passed to
+                        // a C API that will consume the floating reference.
+                        if (arg_type != GJS_ARGUMENT_RETURN_VALUE) {
+                            g_closure_ref(closure);
+                            g_closure_sink(closure);
+                        }
+
+                        gjs_arg_set(arg, closure);
+                        return true;
                     }
+
+                    Gjs::Closure* closure =
+                        BoxedBase::to_c_ptr<Gjs::Closure>(cx, obj);
+                    if (!closure)
+                        return false;
+
                     gjs_arg_set(arg, closure);
                     return true;
                 }
diff --git a/gi/boxed.cpp b/gi/boxed.cpp
index 92af1366..f5e9efde 100644
--- a/gi/boxed.cpp
+++ b/gi/boxed.cpp
@@ -323,18 +323,18 @@ bool BoxedInstance::constructor_impl(JSContext* context, JS::HandleObject obj,
         }
     }
 
-    if (gtype() == G_TYPE_VARIANT) {
+    if (gtype() == G_TYPE_VARIANT || gtype() == G_TYPE_CLOSURE) {
         /* Short-circuit construction for GVariants by calling into the JS packing
            function */
         const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
         if (!boxed_invoke_constructor(context, obj, atoms.new_internal(), args))
             return false;
 
-        // The return value of GLib.Variant.new_internal() gets its own
-        // BoxedInstance, and the one we're setting up in this constructor is
-        // discarded.
+        // The return values of GLib.Variant.new_internal() and
+        // GObject.Closure.new_internal() gets their own BoxedInstance,
+        // and the one we're setting up in this constructor is discarded.
         debug_lifecycle(
-            "Boxed construction delegated to GVariant constructor, "
+            "Boxed construction delegated to JavaScript constructor, "
             "boxed object discarded");
 
         return true;
diff --git a/gi/private.cpp b/gi/private.cpp
index 4a07afb1..d1f68258 100644
--- a/gi/private.cpp
+++ b/gi/private.cpp
@@ -18,6 +18,7 @@
 #include <js/Utility.h>  // for UniqueChars
 #include <jsapi.h>       // for JS_GetElement
 
+#include "gi/boxed.h"
 #include "gi/gobject.h"
 #include "gi/gtype.h"
 #include "gi/interface.h"
@@ -238,6 +239,117 @@ static inline void gjs_add_interface(GType instance_type,
                                 &interface_vtable);
 }
 
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_value_from_closure(JSContext* cx, Gjs::Closure* closure,
+                                   JS::MutableHandleValue value) {
+    GjsAutoStructInfo info =
+        g_irepository_find_by_gtype(nullptr, G_TYPE_CLOSURE);
+    g_assert(info);
+
+    JS::RootedObject boxed(cx, BoxedInstance::new_for_c_struct(
+                                   cx, info, closure, BoxedInstance::NoCopy()));
+    if (!boxed)
+        return false;
+
+    value.setObject(*boxed);
+    return true;
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_create_closure(JSContext* cx, unsigned argc, JS::Value* vp) {
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+
+    JS::RootedObject callable(cx);
+
+    if (!gjs_parse_call_args(cx, "create_closure", args, "o", "callable",
+                             &callable))
+        return false;
+
+    if (!JS_ObjectIsFunction(callable)) {
+        gjs_throw(cx, "create_closure() expects a callable function");
+        return false;
+    }
+
+    JS::RootedFunction func(cx, JS_GetObjectFunction(callable));
+
+    Gjs::Closure* closure =
+        Gjs::Closure::create_marshaled(cx, func, "custom callback");
+    if (closure == nullptr)
+        return false;
+
+    return gjs_value_from_closure(cx, closure, args.rval());
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_invoke_closure(JSContext* cx, unsigned argc, JS::Value* vp) {
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+
+    JS::RootedObject closure(cx);
+    JS::RootedObject this_object(cx);
+    JS::RootedObject params(cx);
+    JS::RootedObject return_type(cx);
+
+    if (!gjs_parse_call_args(cx, "invoke_closure", args, "o?oo?o", "closure",
+                             &closure, "this_object", &this_object, "params",
+                             &params, "return_type", &return_type))
+        return false;
+
+    GType gtype;
+
+    if (!gjs_gtype_get_actual_gtype(cx, closure, &gtype))
+        return false;
+
+    if (gtype != G_TYPE_CLOSURE) {
+        gjs_throw(cx, "Expected closure.");
+        return false;
+    }
+
+    Gjs::Closure* gjs_closure = BoxedBase::to_c_ptr<Gjs::Closure>(cx, closure);
+    if (closure == nullptr)
+        return false;
+
+    bool isArray;
+    if (!JS::IsArrayObject(cx, params, &isArray))
+        return false;
+    if (!isArray) {
+        gjs_throw(cx, "No array.");
+        return false;
+    }
+
+    uint32_t length;
+    if (!JS::GetArrayLength(cx, params, &length))
+        return false;
+    JS::RootedValue elem(cx);
+    AutoGValueVector param_values;
+    param_values.reserve(length);
+    for (uint32_t i = 0; i < length; i++) {
+        if (!JS_GetElement(cx, params, i, &elem))
+            return false;
+        Gjs::AutoGValue& value = param_values.emplace_back();
+        if (!gjs_value_to_g_value(cx, elem, &value))
+            return false;
+    }
+
+    if (return_type) {
+        GValue return_value = {0};
+        GType return_gtype;
+        if (!gjs_gtype_get_actual_gtype(cx, return_type, &return_gtype))
+            return false;
+        if (return_gtype == G_TYPE_INVALID) {
+            gjs_throw(cx, "Invalid gtype.");
+            return false;
+        }
+        g_value_init(&return_value, return_gtype);
+        g_closure_invoke(gjs_closure, &return_value, param_values.size(),
+                         param_values.data(), nullptr);
+        return gjs_value_from_g_value(cx, args.rval(), &return_value);
+    }
+    g_closure_invoke(gjs_closure, nullptr, param_values.size(),
+                     param_values.data(), nullptr);
+    args.rval().setNull();
+    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);
@@ -409,6 +521,8 @@ static JSFunctionSpec private_module_funcs[] = {
           GJS_MODULE_PROP_FLAGS),
     JS_FN("register_type", gjs_register_type, 4, GJS_MODULE_PROP_FLAGS),
     JS_FN("signal_new", gjs_signal_new, 6, GJS_MODULE_PROP_FLAGS),
+    JS_FN("create_closure", gjs_create_closure, 1, GJS_MODULE_PROP_FLAGS),
+    JS_FN("invoke_closure", gjs_invoke_closure, 3, GJS_MODULE_PROP_FLAGS),
     JS_FS_END,
 };
 
diff --git a/installed-tests/js/.eslintrc.yml b/installed-tests/js/.eslintrc.yml
index bbf09ab2..74eefc1b 100644
--- a/installed-tests/js/.eslintrc.yml
+++ b/installed-tests/js/.eslintrc.yml
@@ -32,12 +32,15 @@ globals:
 overrides:
   - files:
       - matchers.js
+      - dataUtils.js
       - testAsync.js
       - testCairoModule.js
       - testConsole.js
       - testESModules.js
       - testEncoding.js
       - testGLibLogWriter.js
+      - testGObjectClosure.js
+      - testGObjectValue.js
       - modules/importmeta.js
       - modules/exports.js
       - modules/say.js
diff --git a/installed-tests/js/dataUtils.js b/installed-tests/js/dataUtils.js
new file mode 100644
index 00000000..f6ad6be5
--- /dev/null
+++ b/installed-tests/js/dataUtils.js
@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
+// SPDX-FileCopyrightText: 2020 Marco Trevisan <marco trevisan canonical com>
+
+const {GLib, GObject, GIMarshallingTests, Regress} = imports.gi;
+
+export const SIGNED_TYPES = ['schar', 'int', 'int64', 'long'];
+export const UNSIGNED_TYPES = ['char', 'uchar', 'uint', 'uint64', 'ulong'];
+export const FLOATING_TYPES = ['double', 'float'];
+export const NUMERIC_TYPES = [...SIGNED_TYPES, ...UNSIGNED_TYPES, ...FLOATING_TYPES];
+export const SPECIFIC_TYPES = ['gtype', 'boolean', 'string', 'param', 'variant', 'boxed', 'gvalue'];
+export const INSTANCED_TYPES = ['object', 'instance'];
+
+export const ALL_TYPES = [...NUMERIC_TYPES, ...SPECIFIC_TYPES, ...INSTANCED_TYPES];
+
+export function getDefaultContentByType(type, createNestedObject = true) {
+    if (SIGNED_TYPES.includes(type))
+        return -((Math.random() * 100 | 0) + 1);
+    if (UNSIGNED_TYPES.includes(type))
+        return -getDefaultContentByType('int') + 2;
+    if (FLOATING_TYPES.includes(type))
+        return getDefaultContentByType('uint') + 0.5;
+    if (type === 'string')
+        return `Hello GValue! ${getDefaultContentByType('uint')}`;
+    if (type === 'boolean')
+        return !!(getDefaultContentByType('int') % 2);
+    if (type === 'gtype')
+        return getGType(ALL_TYPES[Math.random() * ALL_TYPES.length | 0]);
+
+    if (type === 'boxed' || type === 'boxed-struct') {
+        return new GIMarshallingTests.BoxedStruct({
+            long_: getDefaultContentByType('long'),
+            // string_: getDefaultContentByType('string'), not supported
+        });
+    }
+    if (type === 'object') {
+        const props = ALL_TYPES.filter(e =>
+            (e !== 'object' || createNestedObject) &&
+            e !== 'boxed' &&
+            e !== 'gtype' &&
+            e !== 'instance' &&
+            e !== 'param' &&
+            // Include string when gobject-introspection!268 is merged
+            e !== 'string' &&
+            e !== 'schar').concat([
+            'boxed-struct',
+        ]).reduce((ac, a) => ({
+            ...ac, [`some-${a}`]: getDefaultContentByType(a, false),
+        }), {});
+
+        return new GIMarshallingTests.PropertiesObject(props);
+    }
+    if (type === 'param') {
+        return GObject.ParamSpec.string('test-param', '', getDefaultContentByType('string'),
+            GObject.ParamFlags.READABLE, '');
+    }
+    if (type === 'variant') {
+        return new GLib.Variant('a{sv}', {
+            pasta: new GLib.Variant('s', 'Carbonara (con guanciale)'),
+            pizza: new GLib.Variant('s', 'Verace'),
+            randomString: new GLib.Variant('s', getDefaultContentByType('string')),
+        });
+    }
+    if (type === 'gvalue') {
+        const value = new GObject.Value();
+        const valueType = NUMERIC_TYPES[Math.random() * NUMERIC_TYPES.length | 0];
+        value.init(getGType(valueType));
+        setContent(value, valueType, getDefaultContentByType(valueType));
+        return value;
+    }
+    if (type === 'instance')
+        return new Regress.TestFundamentalSubObject(getDefaultContentByType('string'));
+
+
+    throw new Error(`No default content set for type ${type}`);
+}
+
+export function getGType(type) {
+    if (type === 'schar')
+        return GObject.TYPE_CHAR;
+
+    if (type === 'boxed' || type === 'gvalue' || type === 'instance')
+        return getDefaultContentByType(type).constructor.$gtype;
+
+    return GObject[`TYPE_${type.toUpperCase()}`];
+}
+
+export function getContent(gvalue, type) {
+    if (type === 'gvalue')
+        type = 'boxed';
+
+    if (type === 'instance') {
+        pending('https://gitlab.gnome.org/GNOME/gobject-introspection/-/merge_requests/268');
+        return GIMarshallingTests.gvalue_round_trip(gvalue);
+    }
+
+    return gvalue[`get_${type}`]();
+}
+
+export function setContent(gvalue, type, content) {
+    if (type === 'gvalue')
+        type = 'boxed';
+
+    if (type === 'instance')
+        pending('https://gitlab.gnome.org/GNOME/gjs/-/issues/402');
+
+    return gvalue[`set_${type}`](content);
+}
+
+export function skipUnsupported(type) {
+    if (type === 'boxed')
+        pending('https://gitlab.gnome.org/GNOME/gjs/-/issues/402');
+
+    if (type === 'gvalue')
+        pending('https://gitlab.gnome.org/GNOME/gjs/-/issues/272');
+}
diff --git a/installed-tests/js/meson.build b/installed-tests/js/meson.build
index 2f007351..63990caa 100644
--- a/installed-tests/js/meson.build
+++ b/installed-tests/js/meson.build
@@ -124,7 +124,6 @@ jasmine_tests = [
     'GObject',
     'GObjectClass',
     'GObjectInterface',
-    'GObjectValue',
     'GTypeClass',
     'Importer',
     'Introspection',
@@ -236,6 +235,8 @@ modules_tests = [
     'ESModules',
     'Encoding',
     'GLibLogWriter',
+    'GObjectClosure',
+    'GObjectValue',
 ]
 if build_cairo
     modules_tests += 'CairoModule'
diff --git a/installed-tests/js/testGIMarshalling.js b/installed-tests/js/testGIMarshalling.js
index 7d524919..06ee19e5 100644
--- a/installed-tests/js/testGIMarshalling.js
+++ b/installed-tests/js/testGIMarshalling.js
@@ -79,9 +79,6 @@ function testTransferMarshalling(root, value, inoutValue, options = {}) {
             in: {
                 omit: true,  // this case is not in the test suite
             },
-            inout: {
-                skip: 'https://gitlab.gnome.org/GNOME/gobject-introspection/issues/192',
-            },
         };
         Object.assign(fullOptions, options.full);
         testSimpleMarshalling(`${root}_full`, value, inoutValue, fullOptions);
@@ -896,12 +893,11 @@ describe('Callback', function () {
     describe('GClosure', function () {
         testInParameter('gclosure', () => 42);
 
-        xit('marshals a GClosure as a return value', function () {
-            // Currently a GObject.Closure instance is returned, upon which it's
-            // not possible to call invoke() because that method takes a bare
-            // pointer as an argument.
-            expect(GIMarshallingTests.gclosure_return()()).toEqual(42);
-        }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/80');
+        it('marshals a GClosure as a return value', function () {
+            const closure = GIMarshallingTests.gclosure_return();
+            expect(closure instanceof GObject.Closure).toBeTruthy();
+            expect(closure.invoke(GObject.TYPE_INT, [])).toEqual(42);
+        });
     });
 
     it('marshals a return value', function () {
diff --git a/installed-tests/js/testGObject.js b/installed-tests/js/testGObject.js
index d6aafbbc..b41e6a26 100644
--- a/installed-tests/js/testGObject.js
+++ b/installed-tests/js/testGObject.js
@@ -62,7 +62,7 @@ describe('GObject overrides', function () {
 });
 
 describe('GObject should', function () {
-    const types = ['gpointer', 'GBoxed', 'GParam', 'GInterface', 'GObject', 'GVariant'];
+    const types = ['gpointer', 'GBoxed', 'GParam', 'GInterface', 'GObject', 'GVariant', 'GClosure'];
 
     types.forEach(type => {
         it(`be able to create a GType object for ${type}`, function () {
diff --git a/installed-tests/js/testGObjectClosure.js b/installed-tests/js/testGObjectClosure.js
new file mode 100644
index 00000000..4cd3e6c2
--- /dev/null
+++ b/installed-tests/js/testGObjectClosure.js
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
+// SPDX-FileCopyrightText: 2022 Evan Welsh <contact evanwelsh com>
+
+import GObject from 'gi://GObject';
+
+import {
+    ALL_TYPES,
+    getDefaultContentByType,
+    getGType,
+    skipUnsupported
+} from './dataUtils.js';
+
+describe('GObject closure (GClosure)', function () {
+    let spyFn;
+    let closure;
+    beforeEach(function () {
+        spyFn = jasmine.createSpy();
+        closure = new GObject.Closure(spyFn);
+    });
+
+    it('is an instanceof GObject.Closure', function () {
+        expect(closure instanceof GObject.Closure).toBeTruthy();
+    });
+
+    ALL_TYPES.forEach(type => {
+        const gtype = getGType(type);
+
+        it(`can return ${type}`, function () {
+            let randomContent = getDefaultContentByType(type);
+
+            skipUnsupported(type);
+            spyFn.and.returnValue(randomContent);
+            expect(closure.invoke(gtype, [])).toEqual(randomContent);
+        });
+    });
+
+    it('can be invalidated', function () {
+        spyFn.and.returnValue(13);
+        expect(closure.invoke(GObject.TYPE_INT, [])).toBe(13);
+        closure.invalidate();
+        expect(closure.invoke(null, [])).toBe(null);
+    });
+
+    it('can be called with parameters', function () {
+        const plusClosure = new GObject.Closure((a, b) => {
+            return a + b;
+        });
+
+        expect(plusClosure.invoke(GObject.TYPE_INT, [5, 6])).toBe(11);
+        expect(plusClosure.invoke(GObject.TYPE_STRING, ['hello', ', world'])).toBe('hello, world');
+    });
+
+    afterEach(function () {
+        closure = null;
+    });
+});
diff --git a/installed-tests/js/testGObjectValue.js b/installed-tests/js/testGObjectValue.js
index 461c1ba2..ab7484ef 100644
--- a/installed-tests/js/testGObjectValue.js
+++ b/installed-tests/js/testGObjectValue.js
@@ -1,15 +1,18 @@
 // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
 // SPDX-FileCopyrightText: 2020 Marco Trevisan <marco trevisan canonical com>
 
-const {GLib, GObject, GIMarshallingTests, Regress} = imports.gi;
-
-const SIGNED_TYPES = ['schar', 'int', 'int64', 'long'];
-const UNSIGNED_TYPES = ['char', 'uchar', 'uint', 'uint64', 'ulong'];
-const FLOATING_TYPES = ['double', 'float'];
-const NUMERIC_TYPES = [...SIGNED_TYPES, ...UNSIGNED_TYPES, ...FLOATING_TYPES];
-const SPECIFIC_TYPES = ['gtype', 'boolean', 'string', 'param', 'variant', 'boxed', 'gvalue'];
-const INSTANCED_TYPES = ['object', 'instance'];
-const ALL_TYPES = [...NUMERIC_TYPES, ...SPECIFIC_TYPES, ...INSTANCED_TYPES];
+import {
+    ALL_TYPES,
+    FLOATING_TYPES,
+    getContent,
+    getDefaultContentByType,
+    getGType,
+    INSTANCED_TYPES,
+    setContent,
+    skipUnsupported
+} from './dataUtils.js';
+
+const {GObject, GIMarshallingTests} = imports.gi;
 
 describe('GObject value (GValue)', function () {
     let v;
@@ -17,110 +20,6 @@ describe('GObject value (GValue)', function () {
         v = new GObject.Value();
     });
 
-    function getDefaultContentByType(type) {
-        if (SIGNED_TYPES.includes(type))
-            return -((Math.random() * 100 | 0) + 1);
-        if (UNSIGNED_TYPES.includes(type))
-            return -getDefaultContentByType('int') + 2;
-        if (FLOATING_TYPES.includes(type))
-            return getDefaultContentByType('uint') + 0.5;
-        if (type === 'string')
-            return `Hello GValue! ${getDefaultContentByType('uint')}`;
-        if (type === 'boolean')
-            return !!(getDefaultContentByType('int') % 2);
-        if (type === 'gtype')
-            return getGType(ALL_TYPES[Math.random() * ALL_TYPES.length | 0]);
-
-        if (type === 'boxed' || type === 'boxed-struct') {
-            return new GIMarshallingTests.BoxedStruct({
-                long_: getDefaultContentByType('long'),
-                // string_: getDefaultContentByType('string'), not supported
-            });
-        }
-        if (type === 'object') {
-            const wasCreatingObject = this.creatingObject;
-            this.creatingObject = true;
-            const props = ALL_TYPES.filter(e =>
-                (e !== 'object' || !wasCreatingObject) &&
-                e !== 'boxed' &&
-                e !== 'gtype' &&
-                e !== 'instance' &&
-                e !== 'param' &&
-                // Include string when gobject-introspection!268 is merged
-                e !== 'string' &&
-                e !== 'schar').concat([
-                'boxed-struct',
-            ]).reduce((ac, a) => ({
-                ...ac, [`some-${a}`]: getDefaultContentByType(a),
-            }), {});
-            delete this.creatingObject;
-            return new GIMarshallingTests.PropertiesObject(props);
-        }
-        if (type === 'param') {
-            return GObject.ParamSpec.string('test-param', '', getDefaultContentByType('string'),
-                GObject.ParamFlags.READABLE, '');
-        }
-        if (type === 'variant') {
-            return new GLib.Variant('a{sv}', {
-                pasta: new GLib.Variant('s', 'Carbonara (con guanciale)'),
-                pizza: new GLib.Variant('s', 'Verace'),
-                randomString: new GLib.Variant('s', getDefaultContentByType('string')),
-            });
-        }
-        if (type === 'gvalue') {
-            const value = new GObject.Value();
-            const valueType = NUMERIC_TYPES[Math.random() * NUMERIC_TYPES.length | 0];
-            value.init(getGType(valueType));
-            setContent(value, valueType, getDefaultContentByType(valueType));
-            return value;
-        }
-        if (type === 'instance')
-            return new Regress.TestFundamentalSubObject(getDefaultContentByType('string'));
-
-
-        throw new Error(`No default content set for type ${type}`);
-    }
-
-    function getGType(type) {
-        if (type === 'schar')
-            return GObject.TYPE_CHAR;
-
-        if (type === 'boxed' || type === 'gvalue' || type === 'instance')
-            return getDefaultContentByType(type).constructor.$gtype;
-
-        return GObject[`TYPE_${type.toUpperCase()}`];
-    }
-
-    function getContent(gvalue, type) {
-        if (type === 'gvalue')
-            type = 'boxed';
-
-        if (type === 'instance') {
-            pending('https://gitlab.gnome.org/GNOME/gobject-introspection/-/merge_requests/268');
-            return GIMarshallingTests.gvalue_round_trip(gvalue);
-        }
-
-        return gvalue[`get_${type}`]();
-    }
-
-    function setContent(gvalue, type, content) {
-        if (type === 'gvalue')
-            type = 'boxed';
-
-        if (type === 'instance')
-            pending('https://gitlab.gnome.org/GNOME/gjs/-/issues/402');
-
-        return gvalue[`set_${type}`](content);
-    }
-
-    function skipUnsupported(type) {
-        if (type === 'boxed')
-            pending('https://gitlab.gnome.org/GNOME/gjs/-/issues/402');
-
-        if (type === 'gvalue')
-            pending('https://gitlab.gnome.org/GNOME/gjs/-/issues/272');
-    }
-
     ALL_TYPES.forEach(type => {
         const gtype = getGType(type);
         it(`initializes ${type}`, function () {
diff --git a/modules/core/overrides/GObject.js b/modules/core/overrides/GObject.js
index 6bfaf144..d672921b 100644
--- a/modules/core/overrides/GObject.js
+++ b/modules/core/overrides/GObject.js
@@ -753,4 +753,36 @@ function _init() {
         throw new Error('GObject.signal_handlers_disconnect_by_data() is not \
 introspectable. Use GObject.signal_handlers_disconnect_by_func() instead.');
     };
+
+    GObject.Closure._new_internal = function (callable) {
+        'sensitive';
+
+        return Gi.create_closure(callable);
+    };
+
+    GObject.Closure.new_simple = function () {
+        throw new Error('GObject.Closure.new_simple() is not introspectable. \
+Use new GObject.Closure() instead.');
+    };
+
+    GObject.Closure.new_object = function () {
+        throw new Error('GObject.Closure.new_object() is not introspectable. \
+Use new GObject.Closure() instead.');
+    };
+
+    const invoke_closure = Gi.invoke_closure;
+
+    /**
+     * @param {GType | null} [return_type] the GType of the return value or null if the closure returns void
+     * @param {any[]} [parameters] a list of values to pass to the closure
+     * @returns {any}
+     */
+    GObject.Closure.prototype.invoke = function (return_type = null, parameters = []) {
+        'hide source';
+
+        if (return_type === null)
+            return invoke_closure(this, null, parameters, return_type);
+
+        return invoke_closure(this, null, parameters, return_type);
+    };
 }


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