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); }