Hello community,

here is the log from the commit of package gjs for openSUSE:Factory checked in 
at 2020-04-29 20:44:14
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/gjs (Old)
 and      /work/SRC/openSUSE:Factory/.gjs.new.2738 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "gjs"

Wed Apr 29 20:44:14 2020 rev:89 rq:798688 version:1.64.2

Changes:
--------
--- /work/SRC/openSUSE:Factory/gjs/gjs.changes  2020-04-23 18:29:09.343940591 
+0200
+++ /work/SRC/openSUSE:Factory/.gjs.new.2738/gjs.changes        2020-04-29 
20:44:15.427935921 +0200
@@ -1,0 +2,15 @@
+Tue Apr 28 08:56:01 UTC 2020 - Bjørn Lie <bjorn....@gmail.com>
+
+- Update to version 1.64.2:
+  + Closed bugs and merge requests:
+    - GList of int not correctly demarshalled on 64-bit big-endian.
+    - Fix template use in GTK4.
+    - Don't crash if a callback doesn't return an expected array of
+      values.
+    - Crash passing integer to strv in constructor.
+    - Skip some tests if GTK can't be initialised.
+  + Various backports:
+    - Fix gjs_log_exception() for InternalError.
+    - Fix signal match mechanism.
+
+-------------------------------------------------------------------

Old:
----
  gjs-1.64.1.tar.xz

New:
----
  gjs-1.64.2.tar.xz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ gjs.spec ++++++
--- /var/tmp/diff_new_pack.qFpaQA/_old  2020-04-29 20:44:16.403938466 +0200
+++ /var/tmp/diff_new_pack.qFpaQA/_new  2020-04-29 20:44:16.403938466 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           gjs
-Version:        1.64.1
+Version:        1.64.2
 Release:        0
 Summary:        JavaScript bindings based on gobject-introspection and Mozilla
 License:        MIT AND LGPL-2.0-or-later

++++++ gjs-1.64.1.tar.xz -> gjs-1.64.2.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gjs-1.64.1/NEWS new/gjs-1.64.2/NEWS
--- old/gjs-1.64.1/NEWS 2020-03-28 06:38:50.000000000 +0100
+++ new/gjs-1.64.2/NEWS 2020-04-28 04:53:25.000000000 +0200
@@ -1,3 +1,30 @@
+Version 1.64.2
+--------------
+
+- Closed bugs and merge requests:
+  * GList of int not correctly demarshalled on 64-bit big-endian [Philip
+    Chimento, Simon McVittie, #309, !417, !419]
+  * Fix template use in GTK4 [Florian Müllner, !420]
+  * Don't crash if a callback doesn't return an expected array of values [Marco
+    Trevisan, !405]
+  * Crash passing integer to strv in constructor [Evan Welsh, #315, !422]
+  * Skip some tests if GTK can't be initialised [Ross Burton, !421]
+
+- Various backports:
+  * Fix gjs_log_exception() for InternalError [Philip Chimento]
+  * Fix signal match mechanism [Philip Chimento]
+
+Version 1.58.7
+--------------
+
+- Various backports:
+  * Don't crash if a callback doesn't return an expected array of values [Marco
+    Trevisan]
+  * GList of int not correctly demarshalled on 64-bit big-endian [Philip
+    Chimento, Simon McVittie]
+  * Crash passing integer to strv in constructor [Evan Welsh]
+  * Ignore format-nonliteral warning [Marco Trevisan]
+
 Version 1.64.1
 --------------
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gjs-1.64.1/gi/arg.cpp new/gjs-1.64.2/gi/arg.cpp
--- old/gjs-1.64.1/gi/arg.cpp   2020-03-28 06:38:50.000000000 +0100
+++ new/gjs-1.64.2/gi/arg.cpp   2020-04-28 04:53:25.000000000 +0200
@@ -263,6 +263,118 @@
     return false;
 }
 
+/* FIXME: This should be added to gobject-introspection */
+GJS_USE
+static GITypeTag _g_type_info_get_storage_type(GITypeInfo* info) {
+    GITypeTag type_tag = g_type_info_get_tag(info);
+
+    if (type_tag == GI_TYPE_TAG_INTERFACE) {
+        GjsAutoBaseInfo interface = g_type_info_get_interface(info);
+        GIInfoType info_type = g_base_info_get_type(interface);
+        if (info_type == GI_INFO_TYPE_ENUM || info_type == GI_INFO_TYPE_FLAGS)
+            return g_enum_info_get_storage_type(interface);
+    }
+
+    return type_tag;
+}
+
+/* FIXME: This should be added to gobject-introspection */
+static void _g_type_info_argument_from_hash_pointer(GITypeInfo* info,
+                                                    void* hash_pointer,
+                                                    GIArgument* arg) {
+    GITypeTag type_tag = _g_type_info_get_storage_type(info);
+
+    switch (type_tag) {
+        case GI_TYPE_TAG_BOOLEAN:
+            arg->v_boolean = !!GPOINTER_TO_INT(hash_pointer);
+            break;
+        case GI_TYPE_TAG_INT8:
+            arg->v_int8 = (gint8)GPOINTER_TO_INT(hash_pointer);
+            break;
+        case GI_TYPE_TAG_UINT8:
+            arg->v_uint8 = (guint8)GPOINTER_TO_UINT(hash_pointer);
+            break;
+        case GI_TYPE_TAG_INT16:
+            arg->v_int16 = (gint16)GPOINTER_TO_INT(hash_pointer);
+            break;
+        case GI_TYPE_TAG_UINT16:
+            arg->v_uint16 = (guint16)GPOINTER_TO_UINT(hash_pointer);
+            break;
+        case GI_TYPE_TAG_INT32:
+            arg->v_int32 = (gint32)GPOINTER_TO_INT(hash_pointer);
+            break;
+        case GI_TYPE_TAG_UINT32:
+        case GI_TYPE_TAG_UNICHAR:
+            arg->v_uint32 = (guint32)GPOINTER_TO_UINT(hash_pointer);
+            break;
+        case GI_TYPE_TAG_GTYPE:
+            arg->v_size = GPOINTER_TO_SIZE(hash_pointer);
+            break;
+        case GI_TYPE_TAG_UTF8:
+        case GI_TYPE_TAG_FILENAME:
+        case GI_TYPE_TAG_INTERFACE:
+        case GI_TYPE_TAG_ARRAY:
+        case GI_TYPE_TAG_GLIST:
+        case GI_TYPE_TAG_GSLIST:
+        case GI_TYPE_TAG_GHASH:
+        case GI_TYPE_TAG_ERROR:
+            arg->v_pointer = hash_pointer;
+            break;
+        case GI_TYPE_TAG_INT64:
+        case GI_TYPE_TAG_UINT64:
+        case GI_TYPE_TAG_FLOAT:
+        case GI_TYPE_TAG_DOUBLE:
+        default:
+            g_critical("Unsupported type for pointer-stuffing: %s",
+                       g_type_tag_to_string(type_tag));
+            arg->v_pointer = hash_pointer;
+    }
+}
+
+/* FIXME: This should be added to gobject-introspection */
+GJS_USE
+static void* _g_type_info_hash_pointer_from_argument(GITypeInfo* info,
+                                                     GIArgument* arg) {
+    GITypeTag type_tag = _g_type_info_get_storage_type(info);
+
+    switch (type_tag) {
+        case GI_TYPE_TAG_BOOLEAN:
+            return GINT_TO_POINTER(arg->v_boolean);
+        case GI_TYPE_TAG_INT8:
+            return GINT_TO_POINTER(arg->v_int8);
+        case GI_TYPE_TAG_UINT8:
+            return GUINT_TO_POINTER(arg->v_uint8);
+        case GI_TYPE_TAG_INT16:
+            return GINT_TO_POINTER(arg->v_int16);
+        case GI_TYPE_TAG_UINT16:
+            return GUINT_TO_POINTER(arg->v_uint16);
+        case GI_TYPE_TAG_INT32:
+            return GINT_TO_POINTER(arg->v_int32);
+        case GI_TYPE_TAG_UINT32:
+        case GI_TYPE_TAG_UNICHAR:
+            return GUINT_TO_POINTER(arg->v_uint32);
+        case GI_TYPE_TAG_GTYPE:
+            return GSIZE_TO_POINTER(arg->v_size);
+        case GI_TYPE_TAG_UTF8:
+        case GI_TYPE_TAG_FILENAME:
+        case GI_TYPE_TAG_INTERFACE:
+        case GI_TYPE_TAG_ARRAY:
+        case GI_TYPE_TAG_GLIST:
+        case GI_TYPE_TAG_GSLIST:
+        case GI_TYPE_TAG_GHASH:
+        case GI_TYPE_TAG_ERROR:
+            return arg->v_pointer;
+        case GI_TYPE_TAG_INT64:
+        case GI_TYPE_TAG_UINT64:
+        case GI_TYPE_TAG_FLOAT:
+        case GI_TYPE_TAG_DOUBLE:
+        default:
+            g_critical("Unsupported type for pointer-stuffing: %s",
+                       g_type_tag_to_string(type_tag));
+            return arg->v_pointer;
+    }
+}
+
 GJS_JSAPI_RETURN_CONVENTION
 static bool
 gjs_array_to_g_list(JSContext   *context,
@@ -323,12 +435,15 @@
             return false;
         }
 
+        void* hash_pointer =
+            _g_type_info_hash_pointer_from_argument(param_info, &elem_arg);
+
         if (list_type == GI_TYPE_TAG_GLIST) {
             /* GList */
-            list = g_list_prepend(list, elem_arg.v_pointer);
+            list = g_list_prepend(list, hash_pointer);
         } else {
             /* GSList */
-            slist = g_slist_prepend(slist, elem_arg.v_pointer);
+            slist = g_slist_prepend(slist, hash_pointer);
         }
     }
 
@@ -567,8 +682,9 @@
             *heap_val = val_arg.v_double;
             val_ptr = heap_val;
         } else {
-            /* Other types are simply stuffed inside v_pointer */
-            val_ptr = val_arg.v_pointer;
+            // Other types are simply stuffed inside the pointer
+            val_ptr = _g_type_info_hash_pointer_from_argument(val_param_info,
+                                                              &val_arg);
         }
 
         g_hash_table_insert(result, key_ptr, val_ptr);
@@ -1101,22 +1217,13 @@
                                size_t length, GITransfer transfer,
                                GITypeInfo* param_info, void** arr_p) {
     enum { UNSIGNED=false, SIGNED=true };
-    GITypeTag element_type;
 
-    element_type = g_type_info_get_tag(param_info);
+    GITypeTag element_type = _g_type_info_get_storage_type(param_info);
 
     /* Special case for GValue "flat arrays" */
     if (is_gvalue_flat_array(param_info, element_type))
         return gjs_array_to_flat_gvalue_array(context, array_value, length, 
arr_p);
 
-    if (element_type == GI_TYPE_TAG_INTERFACE) {
-        GIBaseInfo *interface_info = g_type_info_get_interface(param_info);
-        GIInfoType info_type = g_base_info_get_type(interface_info);
-        if (info_type == GI_INFO_TYPE_ENUM || info_type == GI_INFO_TYPE_FLAGS)
-            element_type = g_enum_info_get_storage_type ((GIEnumInfo*) 
interface_info);
-        g_base_info_unref(interface_info);
-    }
-
     switch (element_type) {
     case GI_TYPE_TAG_UTF8:
         return gjs_array_to_strv (context, array_value, length, arr_p);
@@ -1201,21 +1308,8 @@
                          unsigned int  length,
                          GITypeInfo   *param_info)
 {
-    GITypeTag element_type;
     guint element_size;
-
-    element_type = g_type_info_get_tag(param_info);
-
-    if (element_type == GI_TYPE_TAG_INTERFACE) {
-        GIInterfaceInfo *interface_info = 
g_type_info_get_interface(param_info);
-        GIInfoType interface_type = g_base_info_get_type(interface_info);
-
-        if (interface_type == GI_INFO_TYPE_ENUM
-            || interface_type == GI_INFO_TYPE_FLAGS)
-            element_type = g_enum_info_get_storage_type((GIEnumInfo*) 
interface_info);
-
-        g_base_info_unref((GIBaseInfo*) interface_info);
-    }
+    GITypeTag element_type = _g_type_info_get_storage_type(param_info);
 
     switch (element_type) {
     case GI_TYPE_TAG_BOOLEAN:
@@ -2249,7 +2343,8 @@
     i = 0;
     if (list_tag == GI_TYPE_TAG_GLIST) {
         for ( ; list != NULL; list = list->next) {
-            arg.v_pointer = list->data;
+            _g_type_info_argument_from_hash_pointer(param_info, list->data,
+                                                    &arg);
             if (!elems.growBy(1)) {
                 JS_ReportOutOfMemory(context);
                 return false;
@@ -2262,7 +2357,8 @@
         }
     } else {
         for ( ; slist != NULL; slist = slist->next) {
-            arg.v_pointer = slist->data;
+            _g_type_info_argument_from_hash_pointer(param_info, slist->data,
+                                                    &arg);
             if (!elems.growBy(1)) {
                 JS_ReportOutOfMemory(context);
                 return false;
@@ -2653,8 +2749,11 @@
     JS::RootedString keystr(context);
 
     g_hash_table_iter_init(&iter, hash);
-    while (g_hash_table_iter_next
-           (&iter, &keyarg.v_pointer, &valarg.v_pointer)) {
+    void* key_pointer;
+    void* val_pointer;
+    while (g_hash_table_iter_next(&iter, &key_pointer, &val_pointer)) {
+        _g_type_info_argument_from_hash_pointer(key_param_info, key_pointer,
+                                                &keyarg);
         if (!gjs_value_from_g_argument(context, &keyjs,
                                        key_param_info, &keyarg,
                                        true))
@@ -2668,6 +2767,8 @@
         if (!keyutf8)
             return false;
 
+        _g_type_info_argument_from_hash_pointer(val_param_info, val_pointer,
+                                                &valarg);
         if (!gjs_value_from_g_argument(context, &valjs,
                                        val_param_info, &valarg,
                                        true))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gjs-1.64.1/gi/closure.cpp 
new/gjs-1.64.2/gi/closure.cpp
--- old/gjs-1.64.1/gi/closure.cpp       2020-03-28 06:38:50.000000000 +0100
+++ new/gjs-1.64.2/gi/closure.cpp       2020-04-28 04:53:25.000000000 +0200
@@ -281,8 +281,6 @@
         return;
 
     c->func.trace(tracer, "signal connection");
-    // update the saved address (for comparison only) in case GC moved it
-    closure->data = const_cast<void*>(c->func.debug_addr());
 }
 
 GClosure* gjs_closure_new(JSContext* context, JSFunction* callable,
@@ -290,16 +288,8 @@
                           bool root_function) {
     Closure *c;
 
-    // We store a bare pointer to the JSFunction in the GClosure's data field
-    // so that g_signal_handlers_block_matched() and friends can work. We are
-    // not supposed to store bare pointers to GC things, but in this particular
-    // case it will work because:
-    //   - we update the data field in gjs_closure_trace() so if the pointer is
-    //     moved or finalized, the data field will still be accurate
-    //   - signal handlers are always in trace mode, so it's not the case that
-    //     JS::PersistentRooted will move the pointer out from under us.
     auto* gc = reinterpret_cast<GjsClosure*>(
-        g_closure_new_simple(sizeof(GjsClosure), callable));
+        g_closure_new_simple(sizeof(GjsClosure), nullptr));
     c = new (&gc->priv) Closure();
 
     /* The saved context is used for lifetime management, so that the closure 
will
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gjs-1.64.1/gi/function.cpp 
new/gjs-1.64.2/gi/function.cpp
--- old/gjs-1.64.1/gi/function.cpp      2020-03-28 06:38:50.000000000 +0100
+++ new/gjs-1.64.2/gi/function.cpp      2020-04-28 04:53:25.000000000 +0200
@@ -153,7 +153,7 @@
 
             if (interface_type == GI_INFO_TYPE_ENUM ||
                 interface_type == GI_INFO_TYPE_FLAGS)
-                *(ffi_sarg *) result = return_value->v_long;
+                *(ffi_sarg *) result = return_value->v_int;
             else
                 *(ffi_arg *) result = (ffi_arg) return_value->v_pointer;
 
@@ -390,6 +390,21 @@
             break;
         }
     } else {
+        bool is_array = rval.isObject();
+        if (!JS_IsArrayObject(context, rval, &is_array))
+            goto out;
+
+        if (!is_array) {
+            JSFunction* fn = gjs_closure_get_callable(trampoline->js_function);
+            gjs_throw(context,
+                      "Function %s (%s.%s) returned unexpected value, "
+                      "expecting an Array",
+                      gjs_debug_string(JS_GetFunctionDisplayId(fn)).c_str(),
+                      g_base_info_get_namespace(trampoline->info),
+                      g_base_info_get_name(trampoline->info));
+            goto out;
+        }
+
         JS::RootedValue elem(context);
         JS::RootedObject out_array(context, rval.toObjectOrNull());
         gsize elem_idx = 0;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gjs-1.64.1/gi/object.cpp new/gjs-1.64.2/gi/object.cpp
--- old/gjs-1.64.1/gi/object.cpp        2020-03-28 06:38:50.000000000 +0100
+++ new/gjs-1.64.2/gi/object.cpp        2020-04-28 04:53:25.000000000 +0200
@@ -1970,9 +1970,9 @@
 
 bool ObjectInstance::signal_match_arguments_from_object(
     JSContext* cx, JS::HandleObject match_obj, GSignalMatchType* mask_out,
-    unsigned* signal_id_out, GQuark* detail_out, void** func_ptr_out) {
-    g_assert(mask_out && signal_id_out && detail_out && func_ptr_out &&
-             "forgot out parameter");
+    unsigned* signal_id_out, GQuark* detail_out,
+    JS::MutableHandleFunction func_out) {
+    g_assert(mask_out && signal_id_out && detail_out && "forgot out 
parameter");
 
     int mask = 0;
     const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
@@ -2014,11 +2014,11 @@
     }
 
     bool has_func;
-    void* func_ptr = nullptr;
+    JS::RootedFunction func(cx);
     if (!JS_HasOwnPropertyById(cx, match_obj, atoms.func(), &has_func))
         return false;
     if (has_func) {
-        mask |= G_SIGNAL_MATCH_DATA;
+        mask |= G_SIGNAL_MATCH_CLOSURE;
 
         JS::RootedValue value(cx);
         if (!JS_GetPropertyById(cx, match_obj, atoms.func(), &value))
@@ -2029,7 +2029,7 @@
             return false;
         }
 
-        func_ptr = JS_GetObjectFunction(&value.toObject());
+        func = JS_GetObjectFunction(&value.toObject());
     }
 
     if (!has_id && !has_detail && !has_func) {
@@ -2043,7 +2043,7 @@
     if (has_detail)
         *detail_out = detail;
     if (has_func)
-        *func_ptr_out = func_ptr;
+        func_out.set(func);
     return true;
 }
 
@@ -2070,13 +2070,25 @@
     GSignalMatchType mask;
     unsigned signal_id;
     GQuark detail;
-    void* func_ptr;
+    JS::RootedFunction func(cx);
     if (!signal_match_arguments_from_object(cx, match, &mask, &signal_id,
-                                            &detail, &func_ptr))
+                                            &detail, &func))
         return false;
 
-    uint64_t handler = g_signal_handler_find(m_ptr, mask, signal_id, detail,
-                                             nullptr, nullptr, func_ptr);
+    uint64_t handler = 0;
+    if (!func) {
+        handler = g_signal_handler_find(m_ptr, mask, signal_id, detail, 
nullptr,
+                                        nullptr, nullptr);
+    } else {
+        for (GClosure* candidate : m_closures) {
+            if (gjs_closure_get_callable(candidate) == func) {
+                handler = g_signal_handler_find(m_ptr, mask, signal_id, detail,
+                                                candidate, nullptr, nullptr);
+                if (handler != 0)
+                    break;
+            }
+        }
+    }
 
     args.rval().setNumber(static_cast<double>(handler));
     return true;
@@ -2109,13 +2121,27 @@
         GSignalMatchType mask;                                                \
         unsigned signal_id;                                                   \
         GQuark detail;                                                        \
-        void* func_ptr;                                                       \
+        JS::RootedFunction func(cx);                                          \
         if (!signal_match_arguments_from_object(cx, match, &mask, &signal_id, \
-                                                &detail, &func_ptr)) {        \
+                                                &detail, &func)) {            \
             return false;                                                     \
         }                                                                     \
-        unsigned n_matched = g_signal_handlers_##action##_matched(            \
-            m_ptr, mask, signal_id, detail, nullptr, nullptr, func_ptr);      \
+        unsigned n_matched = 0;                                               \
+        if (!func) {                                                          \
+            n_matched = g_signal_handlers_##action##_matched(                 \
+                m_ptr, mask, signal_id, detail, nullptr, nullptr, nullptr);   \
+        } else {                                                              \
+            std::vector<GClosure*> candidates;                                \
+            for (GClosure* candidate : m_closures) {                          \
+                if (gjs_closure_get_callable(candidate) == func)              \
+                    candidates.push_back(candidate);                          \
+            }                                                                 \
+            for (GClosure* candidate : candidates) {                          \
+                n_matched += g_signal_handlers_##action##_matched(            \
+                    m_ptr, mask, signal_id, detail, candidate, nullptr,       \
+                    nullptr);                                                 \
+            }                                                                 \
+        }                                                                     \
                                                                               \
         args.rval().setNumber(n_matched);                                     \
         return true;                                                          \
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gjs-1.64.1/gi/object.h new/gjs-1.64.2/gi/object.h
--- old/gjs-1.64.1/gi/object.h  2020-03-28 06:38:50.000000000 +0100
+++ new/gjs-1.64.2/gi/object.h  2020-04-28 04:53:25.000000000 +0200
@@ -405,9 +405,12 @@
     void ensure_uses_toggle_ref(JSContext* cx);
     GJS_USE bool check_gobject_disposed(const char* for_what) const;
     GJS_JSAPI_RETURN_CONVENTION
-    bool signal_match_arguments_from_object(
-        JSContext* cx, JS::HandleObject props_obj, GSignalMatchType* mask_out,
-        unsigned* signal_id_out, GQuark* detail_out, void** func_ptr_out);
+    bool signal_match_arguments_from_object(JSContext* cx,
+                                            JS::HandleObject props_obj,
+                                            GSignalMatchType* mask_out,
+                                            unsigned* signal_id_out,
+                                            GQuark* detail_out,
+                                            JS::MutableHandleFunction 
func_out);
 
  public:
     static GObject* copy_ptr(JSContext* G_GNUC_UNUSED, GType G_GNUC_UNUSED,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gjs-1.64.1/gi/value.cpp new/gjs-1.64.2/gi/value.cpp
--- old/gjs-1.64.1/gi/value.cpp 2020-03-28 06:38:50.000000000 +0100
+++ new/gjs-1.64.2/gi/value.cpp 2020-04-28 04:53:25.000000000 +0200
@@ -488,11 +488,11 @@
 
         g_value_set_object(gvalue, gobj);
     } else if (gtype == G_TYPE_STRV) {
-        bool found_length;
-
         if (value.isNull()) {
             /* do nothing */
-        } else {
+        } else if (value.isObject()) {
+            bool found_length;
+
             const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
             JS::RootedObject array_obj(context, &value.toObject());
             if (JS_HasPropertyById(context, array_obj, atoms.length(),
@@ -519,6 +519,8 @@
             } else {
                 return throw_expect_type(context, value, "strv");
             }
+        } else {
+            return throw_expect_type(context, value, "strv");
         }
     } else if (g_type_is_a(gtype, G_TYPE_BOXED)) {
         void *gboxed;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gjs-1.64.1/gjs/jsapi-util.cpp 
new/gjs-1.64.2/gjs/jsapi-util.cpp
--- old/gjs-1.64.1/gjs/jsapi-util.cpp   2020-03-28 06:38:50.000000000 +0100
+++ new/gjs-1.64.2/gjs/jsapi-util.cpp   2020-04-28 04:53:25.000000000 +0200
@@ -400,25 +400,36 @@
                        JS::HandleValue  exc,
                        JS::HandleString message)
 {
-    bool is_syntax;
+    JS::AutoSaveExceptionState saved_exc(context);
     const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
 
     JS::RootedObject exc_obj(context);
-    JS::RootedString exc_str(context, JS::ToString(context, exc));
-    JS::UniqueChars utf8_exception;
-    if (exc_str)
-        utf8_exception = JS_EncodeStringToUTF8(context, exc_str);
-    if (!utf8_exception)
-        JS_ClearPendingException(context);
-
-    is_syntax = false;
+    JS::RootedString exc_str(context);
+    bool is_syntax = false, is_internal = false;
     if (exc.isObject()) {
         exc_obj = &exc.toObject();
         const JSClass* syntax_error =
             js::Jsvalify(js::ProtoKeyToClass(JSProto_SyntaxError));
         is_syntax = JS_InstanceOf(context, exc_obj, syntax_error, nullptr);
+
+        const JSClass* internal_error =
+            js::Jsvalify(js::ProtoKeyToClass(JSProto_InternalError));
+        is_internal = JS_InstanceOf(context, exc_obj, internal_error, nullptr);
     }
 
+    if (is_internal) {
+        JSErrorReport* report = JS_ErrorFromException(context, exc_obj);
+        if (!report->message())
+            exc_str = JS_NewStringCopyZ(context, "(unknown internal error)");
+        else
+            exc_str = JS_NewStringCopyUTF8Z(context, report->message());
+    } else {
+        exc_str = JS::ToString(context, exc);
+    }
+    JS::UniqueChars utf8_exception;
+    if (exc_str)
+        utf8_exception = JS_EncodeStringToUTF8(context, exc_str);
+
     JS::UniqueChars utf8_message;
     if (message)
         utf8_message = JS_EncodeStringToUTF8(context, message);
@@ -457,13 +468,23 @@
 
     } else {
         JS::UniqueChars utf8_stack;
-        JS::RootedValue stack(context);
-
-        if (exc.isObject() &&
-            JS_GetPropertyById(context, exc_obj, atoms.stack(), &stack) &&
-            stack.isString()) {
-            JS::RootedString str(context, stack.toString());
-            utf8_stack = JS_EncodeStringToUTF8(context, str);
+        if (exc.isObject()) {
+            // Check both the internal SavedFrame object and the stack 
property.
+            // GErrors will not have the former, and internal errors will not
+            // have the latter.
+            JS::RootedObject saved_frame(context,
+                                         JS::ExceptionStackOrNull(exc_obj));
+            JS::RootedString str(context);
+            if (saved_frame) {
+                JS::BuildStackString(context, nullptr, saved_frame, &str, 0);
+            } else {
+                JS::RootedValue stack(context);
+                JS_GetPropertyById(context, exc_obj, atoms.stack(), &stack);
+                if (stack.isString())
+                    str = stack.toString();
+            }
+            if (str)
+                utf8_stack = JS_EncodeStringToUTF8(context, str);
         }
 
         if (message) {
@@ -482,6 +503,8 @@
         }
     }
 
+    saved_exc.restore();
+
     return true;
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gjs-1.64.1/installed-tests/js/complex.ui 
new/gjs-1.64.2/installed-tests/js/complex.ui
--- old/gjs-1.64.1/installed-tests/js/complex.ui        2020-03-28 
06:38:50.000000000 +0100
+++ new/gjs-1.64.2/installed-tests/js/complex.ui        1970-01-01 
01:00:00.000000000 +0100
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<interface>
-  <template class="Gjs_MyComplexGtkSubclassFromResource" parent="GtkGrid">
-    <property name="margin_top">10</property>
-    <property name="margin_bottom">10</property>
-    <property name="margin_start">10</property>
-    <property name="margin_end">10</property>
-    <property name="visible">True</property>
-    <child>
-      <object class="GtkLabel" id="label-child">
-        <property name="label">Complex!</property>
-        <property name="visible">True</property>
-        <signal name="grab-focus" handler="templateCallback" swapped="no"/>
-      </object>
-    </child>
-    <child>
-      <object class="GtkLabel" id="label-child2">
-        <property name="label">Complex as well!</property>
-        <property name="visible">True</property>
-        <signal name="grab-focus" handler="boundCallback" object="label-child" 
swapped="no"/>
-      </object>
-    </child>
-    <child>
-      <object class="GtkLabel" id="internal-label-child">
-        <property name="label">Complex and internal!</property>
-        <property name="visible">True</property>
-      </object>
-    </child>
-  </template>
-</interface>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gjs-1.64.1/installed-tests/js/complex3.ui 
new/gjs-1.64.2/installed-tests/js/complex3.ui
--- old/gjs-1.64.1/installed-tests/js/complex3.ui       1970-01-01 
01:00:00.000000000 +0100
+++ new/gjs-1.64.2/installed-tests/js/complex3.ui       2020-04-28 
04:53:25.000000000 +0200
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="Gjs_MyComplexGtkSubclassFromResource" parent="GtkGrid">
+    <property name="margin_top">10</property>
+    <property name="margin_bottom">10</property>
+    <property name="margin_start">10</property>
+    <property name="margin_end">10</property>
+    <property name="visible">True</property>
+    <child>
+      <object class="GtkLabel" id="label-child">
+        <property name="label">Complex!</property>
+        <property name="visible">True</property>
+        <signal name="grab-focus" handler="templateCallback" swapped="no"/>
+      </object>
+    </child>
+    <child>
+      <object class="GtkLabel" id="label-child2">
+        <property name="label">Complex as well!</property>
+        <property name="visible">True</property>
+        <signal name="grab-focus" handler="boundCallback" object="label-child" 
swapped="no"/>
+      </object>
+    </child>
+    <child>
+      <object class="GtkLabel" id="internal-label-child">
+        <property name="label">Complex and internal!</property>
+        <property name="visible">True</property>
+      </object>
+    </child>
+  </template>
+</interface>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gjs-1.64.1/installed-tests/js/complex4.ui 
new/gjs-1.64.2/installed-tests/js/complex4.ui
--- old/gjs-1.64.1/installed-tests/js/complex4.ui       1970-01-01 
01:00:00.000000000 +0100
+++ new/gjs-1.64.2/installed-tests/js/complex4.ui       2020-04-28 
04:53:25.000000000 +0200
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="Gjs_MyComplexGtkSubclassFromResource" parent="GtkGrid">
+    <property name="margin_top">10</property>
+    <property name="margin_bottom">10</property>
+    <property name="margin_start">10</property>
+    <property name="margin_end">10</property>
+    <child>
+      <object class="GtkLabel" id="label-child">
+        <property name="label">Complex!</property>
+        <signal name="copy-clipboard" handler="templateCallback" swapped="no"/>
+      </object>
+    </child>
+    <child>
+      <object class="GtkLabel" id="label-child2">
+        <property name="label">Complex as well!</property>
+        <signal name="copy-clipboard" handler="boundCallback" 
object="label-child" swapped="no"/>
+      </object>
+    </child>
+    <child>
+      <object class="GtkLabel" id="internal-label-child">
+        <property name="label">Complex and internal!</property>
+      </object>
+    </child>
+  </template>
+</interface>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gjs-1.64.1/installed-tests/js/jsunit.gresources.xml 
new/gjs-1.64.2/installed-tests/js/jsunit.gresources.xml
--- old/gjs-1.64.1/installed-tests/js/jsunit.gresources.xml     2020-03-28 
06:38:50.000000000 +0100
+++ new/gjs-1.64.2/installed-tests/js/jsunit.gresources.xml     2020-04-28 
04:53:25.000000000 +0200
@@ -1,7 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <gresources>
   <gresource prefix="/org/gjs/jsunit">
-    <file preprocess="xml-stripblanks">complex.ui</file>
+    <file preprocess="xml-stripblanks">complex3.ui</file>
+    <file preprocess="xml-stripblanks">complex4.ui</file>
     <file>jasmine.js</file>
     <file>minijasmine.js</file>
     <file>modules/alwaysThrows.js</file>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gjs-1.64.1/installed-tests/js/meson.build 
new/gjs-1.64.2/installed-tests/js/meson.build
--- old/gjs-1.64.1/installed-tests/js/meson.build       2020-03-28 
06:38:50.000000000 +0100
+++ new/gjs-1.64.2/installed-tests/js/meson.build       2020-04-28 
04:53:25.000000000 +0200
@@ -122,10 +122,14 @@
 
 if not get_option('skip_gtk_tests')
     jasmine_tests += [
-        'Gtk',
+        'Gtk3',
         'GObjectDestructionAccess',
         'LegacyGtk',
     ]
+
+    if have_gtk4
+        jasmine_tests += 'Gtk4'
+    endif
 endif
 
 installed_js_tests_dir = installed_tests_execdir / 'js'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gjs-1.64.1/installed-tests/js/testCairo.js 
new/gjs-1.64.2/installed-tests/js/testCairo.js
--- old/gjs-1.64.1/installed-tests/js/testCairo.js      2020-03-28 
06:38:50.000000000 +0100
+++ new/gjs-1.64.2/installed-tests/js/testCairo.js      2020-04-28 
04:53:25.000000000 +0200
@@ -2,17 +2,13 @@
 imports.gi.versions.Gtk = '3.0';
 
 const Cairo = imports.cairo;
-const {Gdk, GIMarshallingTests, Gtk, Regress} = imports.gi;
+const {Gdk, GIMarshallingTests, GLib, Gtk, Regress} = imports.gi;
 
 function _ts(obj) {
     return obj.toString().slice(8, -1);
 }
 
 describe('Cairo', function () {
-    beforeAll(function () {
-        Gtk.init(null);
-    });
-
     let cr, surface;
     beforeEach(function () {
         surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, 10, 10);
@@ -166,6 +162,11 @@
         });
 
         it('has methods when created from a C function', function () {
+            if (GLib.getenv('ENABLE_GTK') !== 'yes') {
+                pending('GTK disabled');
+                return;
+            }
+            Gtk.init(null);
             let win = new Gtk.OffscreenWindow();
             let da = new Gtk.DrawingArea();
             win.add(da);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gjs-1.64.1/installed-tests/js/testGtk.js 
new/gjs-1.64.2/installed-tests/js/testGtk.js
--- old/gjs-1.64.1/installed-tests/js/testGtk.js        2020-03-28 
06:38:50.000000000 +0100
+++ new/gjs-1.64.2/installed-tests/js/testGtk.js        1970-01-01 
01:00:00.000000000 +0100
@@ -1,242 +0,0 @@
-imports.gi.versions.Gtk = '3.0';
-
-const ByteArray = imports.byteArray;
-const {GLib, Gio, GObject, Gtk} = imports.gi;
-const System = imports.system;
-
-// This is ugly here, but usually it would be in a resource
-function createTemplate(className) {
-    return `
-<interface>
-  <template class="${className}" parent="GtkGrid">
-    <property name="margin_top">10</property>
-    <property name="margin_bottom">10</property>
-    <property name="margin_start">10</property>
-    <property name="margin_end">10</property>
-    <property name="visible">True</property>
-    <child>
-      <object class="GtkLabel" id="label-child">
-        <property name="label">Complex!</property>
-        <property name="visible">True</property>
-        <signal name="grab-focus" handler="templateCallback" swapped="no"/>
-      </object>
-    </child>
-    <child>
-      <object class="GtkLabel" id="label-child2">
-        <property name="label">Complex as well!</property>
-        <property name="visible">True</property>
-        <signal name="grab-focus" handler="boundCallback" object="label-child" 
swapped="no"/>
-      </object>
-    </child>
-    <child>
-      <object class="GtkLabel" id="internal-label-child">
-        <property name="label">Complex and internal!</property>
-        <property name="visible">True</property>
-      </object>
-    </child>
-  </template>
-</interface>`;
-}
-
-const MyComplexGtkSubclass = GObject.registerClass({
-    Template: ByteArray.fromString(createTemplate('Gjs_MyComplexGtkSubclass')),
-    Children: ['label-child', 'label-child2'],
-    InternalChildren: ['internal-label-child'],
-    CssName: 'complex-subclass',
-}, class MyComplexGtkSubclass extends Gtk.Grid {
-    templateCallback(widget) {
-        this.callbackEmittedBy = widget;
-    }
-
-    boundCallback(widget) {
-        widget.callbackBoundTo = this;
-    }
-});
-
-// Sadly, putting this in the body of the class will prevent calling
-// get_template_child, since MyComplexGtkSubclass will be bound to the ES6
-// class name without the GObject goodies in it
-MyComplexGtkSubclass.prototype.testChildrenExist = function () {
-    this._internalLabel = this.get_template_child(MyComplexGtkSubclass, 
'label-child');
-    expect(this._internalLabel).toEqual(jasmine.anything());
-
-    expect(this.label_child2).toEqual(jasmine.anything());
-    expect(this._internal_label_child).toEqual(jasmine.anything());
-};
-
-const MyComplexGtkSubclassFromResource = GObject.registerClass({
-    Template: 'resource:///org/gjs/jsunit/complex.ui',
-    Children: ['label-child', 'label-child2'],
-    InternalChildren: ['internal-label-child'],
-}, class MyComplexGtkSubclassFromResource extends Gtk.Grid {
-    testChildrenExist() {
-        expect(this.label_child).toEqual(jasmine.anything());
-        expect(this.label_child2).toEqual(jasmine.anything());
-        expect(this._internal_label_child).toEqual(jasmine.anything());
-    }
-
-    templateCallback(widget) {
-        this.callbackEmittedBy = widget;
-    }
-
-    boundCallback(widget) {
-        widget.callbackBoundTo = this;
-    }
-});
-
-const [templateFile, stream] = Gio.File.new_tmp(null);
-const baseStream = stream.get_output_stream();
-const out = new Gio.DataOutputStream({baseStream});
-out.put_string(createTemplate('Gjs_MyComplexGtkSubclassFromFile'), null);
-out.close(null);
-
-const MyComplexGtkSubclassFromFile = GObject.registerClass({
-    Template: templateFile.get_uri(),
-    Children: ['label-child', 'label-child2'],
-    InternalChildren: ['internal-label-child'],
-}, class MyComplexGtkSubclassFromFile extends Gtk.Grid {
-    testChildrenExist() {
-        expect(this.label_child).toEqual(jasmine.anything());
-        expect(this.label_child2).toEqual(jasmine.anything());
-        expect(this._internal_label_child).toEqual(jasmine.anything());
-    }
-
-    templateCallback(widget) {
-        this.callbackEmittedBy = widget;
-    }
-
-    boundCallback(widget) {
-        widget.callbackBoundTo = this;
-    }
-});
-
-const SubclassSubclass = GObject.registerClass(
-    class SubclassSubclass extends MyComplexGtkSubclass {});
-
-function validateTemplate(description, ClassName, pending = false) {
-    let suite = pending ? xdescribe : describe;
-    suite(description, function () {
-        let win, content;
-        beforeEach(function () {
-            win = new Gtk.Window({type: Gtk.WindowType.TOPLEVEL});
-            content = new ClassName();
-            content.label_child.emit('grab-focus');
-            content.label_child2.emit('grab-focus');
-            win.add(content);
-        });
-
-        it('sets up internal and public template children', function () {
-            content.testChildrenExist();
-        });
-
-        it('sets up public template children with the correct widgets', 
function () {
-            expect(content.label_child.get_label()).toEqual('Complex!');
-            expect(content.label_child2.get_label()).toEqual('Complex as 
well!');
-        });
-
-        it('sets up internal template children with the correct widgets', 
function () {
-            expect(content._internal_label_child.get_label())
-                .toEqual('Complex and internal!');
-        });
-
-        it('connects template callbacks to the correct handler', function () {
-            expect(content.callbackEmittedBy).toBe(content.label_child);
-        });
-
-        it('binds template callbacks to the correct object', function () {
-            
expect(content.label_child2.callbackBoundTo).toBe(content.label_child);
-        });
-
-        afterEach(function () {
-            win.destroy();
-        });
-    });
-}
-
-describe('Gtk overrides', function () {
-    beforeAll(function () {
-        Gtk.init(null);
-    });
-
-    afterAll(function () {
-        templateFile.delete(null);
-    });
-
-    validateTemplate('UI template', MyComplexGtkSubclass);
-    validateTemplate('UI template from resource', 
MyComplexGtkSubclassFromResource);
-    validateTemplate('UI template from file', MyComplexGtkSubclassFromFile);
-    validateTemplate('Class inheriting from template class', SubclassSubclass, 
true);
-
-    it('sets CSS names on classes', function () {
-        
expect(Gtk.Widget.get_css_name.call(MyComplexGtkSubclass)).toEqual('complex-subclass');
-    });
-
-    it('avoid crashing when GTK vfuncs are called in garbage collection', 
function () {
-        GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL,
-            '*during garbage collection*');
-        GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL,
-            '*destroy*');
-
-        let BadLabel = GObject.registerClass(class BadLabel extends Gtk.Label {
-            vfunc_destroy() {}
-        });
-
-        let w = new Gtk.Window();
-        w.add(new BadLabel());
-
-        w.destroy();
-        System.gc();
-
-        GLib.test_assert_expected_messages_internal('Gjs', 'testGtk.js', 0,
-            'Gtk overrides avoid crashing and print a stack trace');
-    });
-
-    it('accepts string in place of GdkAtom', function () {
-        expect(() => Gtk.Clipboard.get(1)).toThrow();
-        expect(() => Gtk.Clipboard.get(true)).toThrow();
-        expect(() => Gtk.Clipboard.get(() => undefined)).toThrow();
-
-        const clipboard = Gtk.Clipboard.get('CLIPBOARD');
-        const primary = Gtk.Clipboard.get('PRIMARY');
-        const anotherClipboard = Gtk.Clipboard.get('CLIPBOARD');
-
-        expect(clipboard).toBeTruthy();
-        expect(primary).toBeTruthy();
-        expect(clipboard).not.toBe(primary);
-        expect(clipboard).toBe(anotherClipboard);
-    });
-
-    it('accepts null in place of GdkAtom as GDK_NONE', function () {
-        /**
-         * When you pass GDK_NONE (an atom, interned from the 'NONE' string)
-         * to Gtk.Clipboard.get(), it throws an error, mentioning null in
-         * its message.
-         */
-        expect(() => Gtk.Clipboard.get('NONE')).toThrowError(/null/);
-
-        /**
-         * Null is converted to GDK_NONE, so you get the same message. If you
-         * know an API function that accepts GDK_NONE without throwing, and
-         * returns something different when passed another atom, consider
-         * adding a less confusing example here.
-         */
-        expect(() => Gtk.Clipboard.get(null)).toThrowError(/null/);
-    });
-
-    it('uses the correct GType for null child properties', function () {
-        let s = new Gtk.Stack();
-        let p = new Gtk.Box();
-
-        s.add_named(p, 'foo');
-        expect(s.get_child_by_name('foo')).toBe(p);
-
-        s.child_set_property(p, 'name', null);
-        expect(s.get_child_by_name('foo')).toBeNull();
-    });
-
-    it('can create a Gtk.TreeIter with accessible stamp field', function () {
-        const iter = new Gtk.TreeIter();
-        iter.stamp = 42;
-        expect(iter.stamp).toEqual(42);
-    });
-});
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gjs-1.64.1/installed-tests/js/testGtk3.js 
new/gjs-1.64.2/installed-tests/js/testGtk3.js
--- old/gjs-1.64.1/installed-tests/js/testGtk3.js       1970-01-01 
01:00:00.000000000 +0100
+++ new/gjs-1.64.2/installed-tests/js/testGtk3.js       2020-04-28 
04:53:25.000000000 +0200
@@ -0,0 +1,242 @@
+imports.gi.versions.Gtk = '3.0';
+
+const ByteArray = imports.byteArray;
+const {GLib, Gio, GObject, Gtk} = imports.gi;
+const System = imports.system;
+
+// This is ugly here, but usually it would be in a resource
+function createTemplate(className) {
+    return `
+<interface>
+  <template class="${className}" parent="GtkGrid">
+    <property name="margin_top">10</property>
+    <property name="margin_bottom">10</property>
+    <property name="margin_start">10</property>
+    <property name="margin_end">10</property>
+    <property name="visible">True</property>
+    <child>
+      <object class="GtkLabel" id="label-child">
+        <property name="label">Complex!</property>
+        <property name="visible">True</property>
+        <signal name="grab-focus" handler="templateCallback" swapped="no"/>
+      </object>
+    </child>
+    <child>
+      <object class="GtkLabel" id="label-child2">
+        <property name="label">Complex as well!</property>
+        <property name="visible">True</property>
+        <signal name="grab-focus" handler="boundCallback" object="label-child" 
swapped="no"/>
+      </object>
+    </child>
+    <child>
+      <object class="GtkLabel" id="internal-label-child">
+        <property name="label">Complex and internal!</property>
+        <property name="visible">True</property>
+      </object>
+    </child>
+  </template>
+</interface>`;
+}
+
+const MyComplexGtkSubclass = GObject.registerClass({
+    Template: ByteArray.fromString(createTemplate('Gjs_MyComplexGtkSubclass')),
+    Children: ['label-child', 'label-child2'],
+    InternalChildren: ['internal-label-child'],
+    CssName: 'complex-subclass',
+}, class MyComplexGtkSubclass extends Gtk.Grid {
+    templateCallback(widget) {
+        this.callbackEmittedBy = widget;
+    }
+
+    boundCallback(widget) {
+        widget.callbackBoundTo = this;
+    }
+});
+
+// Sadly, putting this in the body of the class will prevent calling
+// get_template_child, since MyComplexGtkSubclass will be bound to the ES6
+// class name without the GObject goodies in it
+MyComplexGtkSubclass.prototype.testChildrenExist = function () {
+    this._internalLabel = this.get_template_child(MyComplexGtkSubclass, 
'label-child');
+    expect(this._internalLabel).toEqual(jasmine.anything());
+
+    expect(this.label_child2).toEqual(jasmine.anything());
+    expect(this._internal_label_child).toEqual(jasmine.anything());
+};
+
+const MyComplexGtkSubclassFromResource = GObject.registerClass({
+    Template: 'resource:///org/gjs/jsunit/complex3.ui',
+    Children: ['label-child', 'label-child2'],
+    InternalChildren: ['internal-label-child'],
+}, class MyComplexGtkSubclassFromResource extends Gtk.Grid {
+    testChildrenExist() {
+        expect(this.label_child).toEqual(jasmine.anything());
+        expect(this.label_child2).toEqual(jasmine.anything());
+        expect(this._internal_label_child).toEqual(jasmine.anything());
+    }
+
+    templateCallback(widget) {
+        this.callbackEmittedBy = widget;
+    }
+
+    boundCallback(widget) {
+        widget.callbackBoundTo = this;
+    }
+});
+
+const [templateFile, stream] = Gio.File.new_tmp(null);
+const baseStream = stream.get_output_stream();
+const out = new Gio.DataOutputStream({baseStream});
+out.put_string(createTemplate('Gjs_MyComplexGtkSubclassFromFile'), null);
+out.close(null);
+
+const MyComplexGtkSubclassFromFile = GObject.registerClass({
+    Template: templateFile.get_uri(),
+    Children: ['label-child', 'label-child2'],
+    InternalChildren: ['internal-label-child'],
+}, class MyComplexGtkSubclassFromFile extends Gtk.Grid {
+    testChildrenExist() {
+        expect(this.label_child).toEqual(jasmine.anything());
+        expect(this.label_child2).toEqual(jasmine.anything());
+        expect(this._internal_label_child).toEqual(jasmine.anything());
+    }
+
+    templateCallback(widget) {
+        this.callbackEmittedBy = widget;
+    }
+
+    boundCallback(widget) {
+        widget.callbackBoundTo = this;
+    }
+});
+
+const SubclassSubclass = GObject.registerClass(
+    class SubclassSubclass extends MyComplexGtkSubclass {});
+
+function validateTemplate(description, ClassName, pending = false) {
+    let suite = pending ? xdescribe : describe;
+    suite(description, function () {
+        let win, content;
+        beforeEach(function () {
+            win = new Gtk.Window({type: Gtk.WindowType.TOPLEVEL});
+            content = new ClassName();
+            content.label_child.emit('grab-focus');
+            content.label_child2.emit('grab-focus');
+            win.add(content);
+        });
+
+        it('sets up internal and public template children', function () {
+            content.testChildrenExist();
+        });
+
+        it('sets up public template children with the correct widgets', 
function () {
+            expect(content.label_child.get_label()).toEqual('Complex!');
+            expect(content.label_child2.get_label()).toEqual('Complex as 
well!');
+        });
+
+        it('sets up internal template children with the correct widgets', 
function () {
+            expect(content._internal_label_child.get_label())
+                .toEqual('Complex and internal!');
+        });
+
+        it('connects template callbacks to the correct handler', function () {
+            expect(content.callbackEmittedBy).toBe(content.label_child);
+        });
+
+        it('binds template callbacks to the correct object', function () {
+            
expect(content.label_child2.callbackBoundTo).toBe(content.label_child);
+        });
+
+        afterEach(function () {
+            win.destroy();
+        });
+    });
+}
+
+describe('Gtk overrides', function () {
+    beforeAll(function () {
+        Gtk.init(null);
+    });
+
+    afterAll(function () {
+        templateFile.delete(null);
+    });
+
+    validateTemplate('UI template', MyComplexGtkSubclass);
+    validateTemplate('UI template from resource', 
MyComplexGtkSubclassFromResource);
+    validateTemplate('UI template from file', MyComplexGtkSubclassFromFile);
+    validateTemplate('Class inheriting from template class', SubclassSubclass, 
true);
+
+    it('sets CSS names on classes', function () {
+        
expect(Gtk.Widget.get_css_name.call(MyComplexGtkSubclass)).toEqual('complex-subclass');
+    });
+
+    it('avoid crashing when GTK vfuncs are called in garbage collection', 
function () {
+        GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL,
+            '*during garbage collection*');
+        GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL,
+            '*destroy*');
+
+        let BadLabel = GObject.registerClass(class BadLabel extends Gtk.Label {
+            vfunc_destroy() {}
+        });
+
+        let w = new Gtk.Window();
+        w.add(new BadLabel());
+
+        w.destroy();
+        System.gc();
+
+        GLib.test_assert_expected_messages_internal('Gjs', 'testGtk3.js', 0,
+            'Gtk overrides avoid crashing and print a stack trace');
+    });
+
+    it('accepts string in place of GdkAtom', function () {
+        expect(() => Gtk.Clipboard.get(1)).toThrow();
+        expect(() => Gtk.Clipboard.get(true)).toThrow();
+        expect(() => Gtk.Clipboard.get(() => undefined)).toThrow();
+
+        const clipboard = Gtk.Clipboard.get('CLIPBOARD');
+        const primary = Gtk.Clipboard.get('PRIMARY');
+        const anotherClipboard = Gtk.Clipboard.get('CLIPBOARD');
+
+        expect(clipboard).toBeTruthy();
+        expect(primary).toBeTruthy();
+        expect(clipboard).not.toBe(primary);
+        expect(clipboard).toBe(anotherClipboard);
+    });
+
+    it('accepts null in place of GdkAtom as GDK_NONE', function () {
+        /**
+         * When you pass GDK_NONE (an atom, interned from the 'NONE' string)
+         * to Gtk.Clipboard.get(), it throws an error, mentioning null in
+         * its message.
+         */
+        expect(() => Gtk.Clipboard.get('NONE')).toThrowError(/null/);
+
+        /**
+         * Null is converted to GDK_NONE, so you get the same message. If you
+         * know an API function that accepts GDK_NONE without throwing, and
+         * returns something different when passed another atom, consider
+         * adding a less confusing example here.
+         */
+        expect(() => Gtk.Clipboard.get(null)).toThrowError(/null/);
+    });
+
+    it('uses the correct GType for null child properties', function () {
+        let s = new Gtk.Stack();
+        let p = new Gtk.Box();
+
+        s.add_named(p, 'foo');
+        expect(s.get_child_by_name('foo')).toBe(p);
+
+        s.child_set_property(p, 'name', null);
+        expect(s.get_child_by_name('foo')).toBeNull();
+    });
+
+    it('can create a Gtk.TreeIter with accessible stamp field', function () {
+        const iter = new Gtk.TreeIter();
+        iter.stamp = 42;
+        expect(iter.stamp).toEqual(42);
+    });
+});
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gjs-1.64.1/installed-tests/js/testGtk4.js 
new/gjs-1.64.2/installed-tests/js/testGtk4.js
--- old/gjs-1.64.1/installed-tests/js/testGtk4.js       1970-01-01 
01:00:00.000000000 +0100
+++ new/gjs-1.64.2/installed-tests/js/testGtk4.js       2020-04-28 
04:53:25.000000000 +0200
@@ -0,0 +1,195 @@
+imports.gi.versions.Gtk = '4.0';
+
+const ByteArray = imports.byteArray;
+const {GLib, Gio, GObject, Gtk} = imports.gi;
+const System = imports.system;
+
+// This is ugly here, but usually it would be in a resource
+function createTemplate(className) {
+    return `
+<interface>
+  <template class="${className}" parent="GtkGrid">
+    <property name="margin_top">10</property>
+    <property name="margin_bottom">10</property>
+    <property name="margin_start">10</property>
+    <property name="margin_end">10</property>
+    <child>
+      <object class="GtkLabel" id="label-child">
+        <property name="label">Complex!</property>
+        <signal name="copy-clipboard" handler="templateCallback" swapped="no"/>
+      </object>
+    </child>
+    <child>
+      <object class="GtkLabel" id="label-child2">
+        <property name="label">Complex as well!</property>
+        <signal name="copy-clipboard" handler="boundCallback" 
object="label-child" swapped="no"/>
+      </object>
+    </child>
+    <child>
+      <object class="GtkLabel" id="internal-label-child">
+        <property name="label">Complex and internal!</property>
+      </object>
+    </child>
+  </template>
+</interface>`;
+}
+
+const MyComplexGtkSubclass = GObject.registerClass({
+    Template: ByteArray.fromString(createTemplate('Gjs_MyComplexGtkSubclass')),
+    Children: ['label-child', 'label-child2'],
+    InternalChildren: ['internal-label-child'],
+    CssName: 'complex-subclass',
+}, class MyComplexGtkSubclass extends Gtk.Grid {
+    templateCallback(widget) {
+        this.callbackEmittedBy = widget;
+    }
+
+    boundCallback(widget) {
+        widget.callbackBoundTo = this;
+    }
+});
+
+// Sadly, putting this in the body of the class will prevent calling
+// get_template_child, since MyComplexGtkSubclass will be bound to the ES6
+// class name without the GObject goodies in it
+MyComplexGtkSubclass.prototype.testChildrenExist = function () {
+    this._internalLabel = this.get_template_child(MyComplexGtkSubclass, 
'label-child');
+    expect(this._internalLabel).toEqual(jasmine.anything());
+
+    expect(this.label_child2).toEqual(jasmine.anything());
+    expect(this._internal_label_child).toEqual(jasmine.anything());
+};
+
+const MyComplexGtkSubclassFromResource = GObject.registerClass({
+    Template: 'resource:///org/gjs/jsunit/complex4.ui',
+    Children: ['label-child', 'label-child2'],
+    InternalChildren: ['internal-label-child'],
+}, class MyComplexGtkSubclassFromResource extends Gtk.Grid {
+    testChildrenExist() {
+        expect(this.label_child).toEqual(jasmine.anything());
+        expect(this.label_child2).toEqual(jasmine.anything());
+        expect(this._internal_label_child).toEqual(jasmine.anything());
+    }
+
+    templateCallback(widget) {
+        this.callbackEmittedBy = widget;
+    }
+
+    boundCallback(widget) {
+        widget.callbackBoundTo = this;
+    }
+});
+
+const [templateFile, stream] = Gio.File.new_tmp(null);
+const baseStream = stream.get_output_stream();
+const out = new Gio.DataOutputStream({baseStream});
+out.put_string(createTemplate('Gjs_MyComplexGtkSubclassFromFile'), null);
+out.close(null);
+
+const MyComplexGtkSubclassFromFile = GObject.registerClass({
+    Template: templateFile.get_uri(),
+    Children: ['label-child', 'label-child2'],
+    InternalChildren: ['internal-label-child'],
+}, class MyComplexGtkSubclassFromFile extends Gtk.Grid {
+    testChildrenExist() {
+        expect(this.label_child).toEqual(jasmine.anything());
+        expect(this.label_child2).toEqual(jasmine.anything());
+        expect(this._internal_label_child).toEqual(jasmine.anything());
+    }
+
+    templateCallback(widget) {
+        this.callbackEmittedBy = widget;
+    }
+
+    boundCallback(widget) {
+        widget.callbackBoundTo = this;
+    }
+});
+
+const SubclassSubclass = GObject.registerClass(
+    class SubclassSubclass extends MyComplexGtkSubclass {});
+
+function validateTemplate(description, ClassName, pending = false) {
+    let suite = pending ? xdescribe : describe;
+    suite(description, function () {
+        let win, content;
+        beforeEach(function () {
+            win = new Gtk.Window();
+            content = new ClassName();
+            content.label_child.emit('copy-clipboard');
+            content.label_child2.emit('copy-clipboard');
+            win.add(content);
+        });
+
+        it('sets up internal and public template children', function () {
+            content.testChildrenExist();
+        });
+
+        it('sets up public template children with the correct widgets', 
function () {
+            expect(content.label_child.get_label()).toEqual('Complex!');
+            expect(content.label_child2.get_label()).toEqual('Complex as 
well!');
+        });
+
+        it('sets up internal template children with the correct widgets', 
function () {
+            expect(content._internal_label_child.get_label())
+                .toEqual('Complex and internal!');
+        });
+
+        it('connects template callbacks to the correct handler', function () {
+            expect(content.callbackEmittedBy).toBe(content.label_child);
+        });
+
+        it('binds template callbacks to the correct object', function () {
+            
expect(content.label_child2.callbackBoundTo).toBe(content.label_child);
+        });
+
+        afterEach(function () {
+            win.destroy();
+        });
+    });
+}
+
+describe('Gtk overrides', function () {
+    beforeAll(function () {
+        Gtk.init();
+    });
+
+    afterAll(function () {
+        templateFile.delete(null);
+    });
+
+    validateTemplate('UI template', MyComplexGtkSubclass);
+    validateTemplate('UI template from resource', 
MyComplexGtkSubclassFromResource);
+    validateTemplate('UI template from file', MyComplexGtkSubclassFromFile);
+    validateTemplate('Class inheriting from template class', SubclassSubclass, 
true);
+
+    it('sets CSS names on classes', function () {
+        
expect(Gtk.Widget.get_css_name.call(MyComplexGtkSubclass)).toEqual('complex-subclass');
+    });
+
+    it('avoid crashing when GTK vfuncs are called in garbage collection', 
function () {
+        GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL,
+            '*during garbage collection*');
+        GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL,
+            '*destroy*');
+
+        let BadLabel = GObject.registerClass(class BadLabel extends Gtk.Label {
+            vfunc_destroy() {}
+        });
+
+        let w = new Gtk.Window();
+        w.add(new BadLabel());
+
+        w.destroy();
+        System.gc();
+
+        GLib.test_assert_expected_messages_internal('Gjs', 'testGtk4.js', 0,
+            'Gtk overrides avoid crashing and print a stack trace');
+    });
+
+    it('can create a Gtk.TreeIter with accessible stamp field', function () {
+        const iter = new Gtk.TreeIter();
+        iter.stamp = 42;
+        expect(iter.stamp).toEqual(42);
+    });
+});
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gjs-1.64.1/installed-tests/js/testIntrospection.js 
new/gjs-1.64.2/installed-tests/js/testIntrospection.js
--- old/gjs-1.64.1/installed-tests/js/testIntrospection.js      2020-03-28 
06:38:50.000000000 +0100
+++ new/gjs-1.64.2/installed-tests/js/testIntrospection.js      2020-04-28 
04:53:25.000000000 +0200
@@ -31,6 +31,10 @@
 describe('Marshalling empty flat arrays of structs', function () {
     let widget;
     beforeAll(function () {
+        if (GLib.getenv('ENABLE_GTK') !== 'yes') {
+            pending('GTK disabled');
+            return;
+        }
         Gtk.init(null);
     });
 
@@ -73,6 +77,10 @@
 describe('Object properties on GtkBuilder-constructed objects', function () {
     let o1;
     beforeAll(function () {
+        if (GLib.getenv('ENABLE_GTK') !== 'yes') {
+            pending('GTK disabled');
+            return;
+        }
         Gtk.init(null);
     });
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gjs-1.64.1/installed-tests/js/testLegacyGtk.js 
new/gjs-1.64.2/installed-tests/js/testLegacyGtk.js
--- old/gjs-1.64.1/installed-tests/js/testLegacyGtk.js  2020-03-28 
06:38:50.000000000 +0100
+++ new/gjs-1.64.2/installed-tests/js/testLegacyGtk.js  2020-04-28 
04:53:25.000000000 +0200
@@ -56,7 +56,7 @@
 const MyComplexGtkSubclassFromResource = new Lang.Class({
     Name: 'MyComplexGtkSubclassFromResource',
     Extends: Gtk.Grid,
-    Template: 'resource:///org/gjs/jsunit/complex.ui',
+    Template: 'resource:///org/gjs/jsunit/complex3.ui',
     Children: ['label-child', 'label-child2'],
     InternalChildren: ['internal-label-child'],
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gjs-1.64.1/installed-tests/js/testRegress.js 
new/gjs-1.64.2/installed-tests/js/testRegress.js
--- old/gjs-1.64.1/installed-tests/js/testRegress.js    2020-03-28 
06:38:50.000000000 +0100
+++ new/gjs-1.64.2/installed-tests/js/testRegress.js    2020-04-28 
04:53:25.000000000 +0200
@@ -226,6 +226,10 @@
         it('marshalling in', function () {
             expect(Regress.test_strv_in(['1', '2', '3'])).toBeTruthy();
             expect(Regress.test_strv_in(['4', '5', '6'])).toBeFalsy();
+            // Ensure that primitives throw without SEGFAULT
+            expect(() => Regress.test_strv_in(1)).toThrow();
+            expect(() => Regress.test_strv_in('')).toThrow();
+            expect(() => Regress.test_strv_in(false)).toThrow();
             // Second two are deliberately not strings
             expect(() => Regress.test_strv_in(['1', 2, 3])).toThrow();
         });
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gjs-1.64.1/meson.build new/gjs-1.64.2/meson.build
--- old/gjs-1.64.1/meson.build  2020-03-28 06:38:50.000000000 +0100
+++ new/gjs-1.64.2/meson.build  2020-04-28 04:53:25.000000000 +0200
@@ -1,4 +1,4 @@
-project('gjs', 'cpp', 'c', version: '1.64.1', license: ['MIT', 'LGPL2+'],
+project('gjs', 'cpp', 'c', version: '1.64.2', license: ['MIT', 'LGPL2+'],
     meson_version: '>= 0.50.0',
     default_options: ['cpp_std=c++14', 'c_std=c99', 'warning_level=2'])
 
@@ -606,6 +606,10 @@
     subdir('test')
 endif
 
+if not get_option('skip_gtk_tests')
+    have_gtk4 = dependency('gtk4', required: false).found()
+endif
+
 subdir('installed-tests')
 
 valgrind_environment = environment()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/gjs-1.64.1/modules/core/overrides/Gtk.js 
new/gjs-1.64.2/modules/core/overrides/Gtk.js
--- old/gjs-1.64.1/modules/core/overrides/Gtk.js        2020-03-28 
06:38:50.000000000 +0100
+++ new/gjs-1.64.2/modules/core/overrides/Gtk.js        2020-04-28 
04:53:25.000000000 +0200
@@ -23,6 +23,7 @@
 const {Gio, GjsPrivate, GObject} = imports.gi;
 
 let Gtk;
+let BuilderScope;
 
 function _init() {
 
@@ -44,20 +45,22 @@
 
     Gtk.Widget.prototype._init = function (params) {
         if (this.constructor[Gtk.template]) {
-            Gtk.Widget.set_connect_func.call(this.constructor, (builder, obj, 
signalName, handlerName, connectObj, flags) => {
-                connectObj = connectObj || this;
-
-                if (flags & GObject.ConnectFlags.SWAPPED) {
-                    throw new Error('Unsupported template signal flag 
"swapped"');
-                } else if (typeof this[handlerName] === 'undefined') {
-                    throw new Error(`A handler called ${handlerName} was not ` 
+
-                        `defined for signal ${signalName} on ${this}`);
-                } else if (flags & GObject.ConnectFlags.AFTER) {
-                    obj.connect_after(signalName, 
this[handlerName].bind(connectObj));
-                } else {
-                    obj.connect(signalName, 
this[handlerName].bind(connectObj));
-                }
-            });
+            if (BuilderScope) {
+                Gtk.Widget.set_template_scope.call(this.constructor,
+                    new BuilderScope(this));
+            } else {
+                Gtk.Widget.set_connect_func.call(this.constructor,
+                    (builder, obj, signalName, handlerName, connectObj, flags) 
=> {
+                        const swapped = flags & GObject.ConnectFlags.SWAPPED;
+                        const closure = _createClosure(
+                            builder, this, handlerName, swapped, connectObj);
+
+                        if (flags & GObject.ConnectFlags.AFTER)
+                            obj.connect_after(signalName, closure);
+                        else
+                            obj.connect(signalName, closure);
+                    });
+            }
         }
 
         GObject.Object.prototype._init.call(this, params);
@@ -121,4 +124,34 @@
 
         return klass;
     };
+
+    if (Gtk.BuilderScope) {
+        BuilderScope = GObject.registerClass({
+            Implements: [Gtk.BuilderScope],
+        }, class extends GObject.Object {
+            _init(thisArg) {
+                super._init();
+                this._this = thisArg;
+            }
+
+            vfunc_create_closure(builder, handlerName, flags, connectObject) {
+                const swapped = flags & Gtk.BuilderClosureFlags.SWAPPED;
+                return _createClosure(
+                    builder, this._this, handlerName, swapped, connectObject);
+            }
+        });
+    }
+}
+
+function _createClosure(builder, thisArg, handlerName, swapped, connectObject) 
{
+    connectObject = connectObject || thisArg;
+
+    if (swapped) {
+        throw new Error('Unsupported template signal flag "swapped"');
+    } else if (typeof thisArg[handlerName] === 'undefined') {
+        throw new Error(`A handler called ${handlerName} was not ` +
+            `defined on ${thisArg}`);
+    }
+
+    return thisArg[handlerName].bind(connectObject);
 }


Reply via email to