[gjs/ewlsh/glogfield-support] add support for glogfield
- From: Evan Welsh <ewlsh src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gjs/ewlsh/glogfield-support] add support for glogfield
- Date: Fri, 2 Jul 2021 20:54:14 +0000 (UTC)
commit af9ccf46390b06e77a23326b3087ee4a6a4cb2d1
Author: Evan Welsh <contact evanwelsh com>
Date: Fri Jul 2 13:45:43 2021 -0700
add support for glogfield
glogfield is a struct with defined behavior that can't be expressed in GI (a pointer that is a string
sometimes, raw binary data other times)
let's create a custom object wrapper for it so we can support the full GLib log APIs.
gi/arg.cpp | 6 +-
gi/glogfield.cpp | 230 ++++++++++++++++++++++++++++++++++++++
gi/glogfield.h | 136 ++++++++++++++++++++++
gi/repo.cpp | 7 +-
gi/value.cpp | 5 +-
gjs/mem-private.h | 2 +
installed-tests/js/meson.build | 1 +
installed-tests/js/testConsole.js | 41 +++++++
meson.build | 1 +
9 files changed, 426 insertions(+), 3 deletions(-)
---
diff --git a/gi/arg.cpp b/gi/arg.cpp
index 2a82b07b..67721c34 100644
--- a/gi/arg.cpp
+++ b/gi/arg.cpp
@@ -34,6 +34,7 @@
#include "gi/foreign.h"
#include "gi/fundamental.h"
#include "gi/gerror.h"
+#include "gi/glogfield.h"
#include "gi/gtype.h"
#include "gi/interface.h"
#include "gi/js-value-inl.h"
@@ -2655,7 +2656,10 @@ gjs_value_from_g_argument (JSContext *context,
JSObject *obj;
- if (copy_structs || g_type_is_a(gtype, G_TYPE_VARIANT))
+ if (gjs_struct_info_is_log_field(interface_info))
+ obj = GjsGLogFieldInstance::new_for_c_struct(
+ context, interface_info, gjs_arg_get<void*>(arg));
+ else if (copy_structs || g_type_is_a(gtype, G_TYPE_VARIANT))
obj = BoxedInstance::new_for_c_struct(
context, interface_info, gjs_arg_get<GVariant*>(arg));
else
diff --git a/gi/glogfield.cpp b/gi/glogfield.cpp
new file mode 100644
index 00000000..da75cf2b
--- /dev/null
+++ b/gi/glogfield.cpp
@@ -0,0 +1,230 @@
+// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021 Evan Welsh <contact evanwelsh com>
+
+#include "gi/glogfield.h"
+#include <glib.h>
+#include <js/CallArgs.h> // for CallArgs, CallArgsBase
+#include <js/Class.h> // for JSCLASS_HAS_RESERVED_SLOTS, JSCLA...
+#include <js/PropertyDescriptor.h> // for JSPROP_ENUMERATE, JSPROP_PERMANENT
+#include <js/RootingAPI.h> // for Rooted, Handle, MutableHandle
+#include <js/TypeDecls.h>
+#include <js/Utility.h> // for UniqueChars
+#include <js/Value.h> // for Value
+#include <jsapi.h> // for JS_DefineProperty
+#include <utility> // for forward
+#include "gi/repo.h"
+#include "gjs/jsapi-util-args.h"
+#include "gjs/jsapi-util.h" // for gjs_string_from_utf8, gjs_throw
+#include "gjs/mem-private.h"
+
+bool gjs_struct_info_is_log_field(GIStructInfo* info) {
+ // TODO(ewlsh): There is perhaps a better way to do this.
+ return g_strcmp0(g_base_info_get_namespace(info), "GLib") == 0 &&
+ g_strcmp0(g_base_info_get_name(info), "LogField") == 0;
+}
+
+// clang-format off
+const struct JSClassOps GjsGLogFieldBase::class_ops = {
+ nullptr, // addProperty
+ nullptr, // deleteProperty
+ nullptr, // enumerate
+ nullptr,
+ nullptr,
+ nullptr, // mayResolve
+ &GjsGLogFieldBase::finalize,
+ nullptr, // call
+ nullptr, // hasInstance
+ nullptr, // construct
+ nullptr,
+};
+
+const struct JSClass GjsGLogFieldBase::klass = {
+ "GObject_GLogField",
+ JSCLASS_HAS_PRIVATE | JSCLASS_FOREGROUND_FINALIZE |
+ JSCLASS_HAS_RESERVED_SLOTS(1),
+ &GjsGLogFieldBase::class_ops
+};
+
+[[nodiscard]] GjsGLogFieldBase* GjsGLogFieldBase::get_copy_source(JSContext* cx,
+ JS::Value value) const {
+ if (!value.isObject())
+ return nullptr;
+
+ JS::RootedObject object(cx, &value.toObject());
+ GjsGLogFieldBase* source_priv = GjsGLogFieldBase::for_js(cx, object);
+ if (!source_priv || !g_base_info_equal(info(), source_priv->info()))
+ return nullptr;
+
+ return source_priv;
+}
+
+bool GjsGLogFieldPrototype::key_getter(JSContext* cx, unsigned int argc,
+ JS::Value* vp) {
+ GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, this_obj, GjsGLogFieldBase,
+ priv);
+ if (!priv->check_is_instance(cx, "field getter"))
+ return false;
+
+ GLogField* field = priv->to_instance()->ptr();
+ if (!gjs_string_from_utf8(cx, field->key, args.rval()))
+ return false;
+
+ return true;
+}
+
+bool GjsGLogFieldPrototype::value_getter(JSContext* cx, unsigned int argc,
+ JS::Value* vp) {
+ GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, this_obj, GjsGLogFieldBase,
+ priv);
+ if (!priv->check_is_instance(cx, "field getter"))
+ return false;
+
+ GLogField* field = priv->to_instance()->ptr();
+ if (field->length != -1) {
+ gjs_throw(cx, "Cannot get non-string value with length %li.", field->length);
+ return false;
+ }
+
+ const char* str = static_cast<const char*>(field->value);
+ return gjs_string_from_utf8(cx, str, args.rval());
+}
+
+GjsGLogFieldPrototype::GjsGLogFieldPrototype(GIStructInfo* info, GType gtype) : GIWrapperPrototype(info,
gtype) {
+ GJS_INC_COUNTER(logfield_prototype);
+}
+
+bool GjsGLogFieldPrototype::define_class(JSContext* cx,
+ JS::HandleObject in_object,
+ GIStructInfo* info) {
+ JS::RootedObject prototype(cx), unused_constructor(cx);
+ GType gtype = g_registered_type_info_get_g_type(info);
+ GjsGLogFieldPrototype* priv = GjsGLogFieldPrototype::create_class(
+ cx, in_object, info, gtype, &unused_constructor, &prototype);
+ if (!priv)
+ return false;
+
+ if (!JS_DefineProperty(cx, prototype, "key",
+ &GjsGLogFieldPrototype::key_getter, nullptr,
+ JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY) ||
+ !JS_DefineProperty(cx, prototype, "value",
+ &GjsGLogFieldPrototype::value_getter, nullptr,
+ JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool GjsGLogFieldPrototype::init(JSContext*) {
+ return true;
+}
+
+GjsGLogFieldInstance::GjsGLogFieldInstance(JSContext* cx, JS::HandleObject obj)
+ : GIWrapperInstance(cx, obj),
+ m_owning_ptr(false) {
+ GJS_INC_COUNTER(logfield_instance);
+}
+
+bool GjsGLogFieldInstance::init_from_c_struct(JSContext*, GLogField* glogfield,
+ NoCopy) {
+ // We need to create a JS Boxed which references the original C struct, not
+ // a copy of it. Used for G_SIGNAL_TYPE_STATIC_SCOPE.
+ share_ptr(glogfield);
+ debug_lifecycle("Boxed pointer acquired, memory not owned");
+ return true;
+}
+
+bool GjsGLogFieldInstance::init_from_c_struct(JSContext*,
+ GLogField* glogfield) {
+ own_ptr(g_malloc0(sizeof(GLogField)));
+
+ ptr()->key = g_strdup(glogfield->key);
+
+ if (glogfield->length != -1) {
+ ptr()->value = nullptr;
+ ptr()->length = 0;
+ } else if (glogfield->length < 0) {
+ ptr()->value = g_strdup(static_cast<const char*>(glogfield->value));
+ ptr()->length = -1;
+ } else {
+
+ }
+
+
+ debug_lifecycle("GLogField pointer allocated");
+ return true;
+}
+
+template <typename... Args>
+JSObject* GjsGLogFieldInstance::new_for_c_struct_impl(JSContext* cx,
+ GIStructInfo* info,
+ void* glogfield,
+ Args&&... args) {
+ if (glogfield == nullptr)
+ return nullptr;
+
+ gjs_debug_marshal(GJS_DEBUG_GBOXED,
+ "Wrapping GLogField struct %s %p with JSObject",
+ g_base_info_get_name((GIBaseInfo*)info), glogfield);
+
+ JS::RootedObject obj(cx, gjs_new_object_with_generic_prototype(cx, info));
+ if (!obj)
+ return nullptr;
+
+ GjsGLogFieldInstance* priv =
+ GjsGLogFieldInstance::new_for_js_object(cx, obj);
+ if (!priv || !priv->init_from_c_struct(cx, static_cast<GLogField*>(glogfield),
std::forward<Args>(args)...))
+ return nullptr;
+
+ return obj;
+}
+
+JSObject* GjsGLogFieldInstance::new_for_c_struct(JSContext* cx, GIStructInfo* info,
+ void* gboxed) {
+ return new_for_c_struct_impl(cx, info, gboxed);
+}
+
+JSObject* GjsGLogFieldInstance::new_for_c_struct(JSContext* cx, GIStructInfo* info,
+ void* gboxed, NoCopy no_copy) {
+ return new_for_c_struct_impl(cx, info, gboxed, no_copy);
+}
+
+// See GIWrapperBase::constructor().
+bool GjsGLogFieldInstance::constructor_impl(JSContext* cx,
+ JS::HandleObject,
+ const JS::CallArgs& args) {
+ // Short-circuit copy-construction in the case where we can use copy_boxed()
+ // or copy_memory()
+ GjsGLogFieldBase* source_priv;
+
+ if (args.length() == 1 &&
+ (source_priv = get_copy_source(cx, args[0]))) {
+ if (!source_priv->check_is_instance(cx, "construct logfield object"))
+ return false;
+
+ return init_from_c_struct(cx, source_priv->to_instance()->ptr());
+ }
+
+ JS::UniqueChars key, value;
+ if (!gjs_parse_call_args(cx, "GLib.FieldInstance()", args, "ss", "key", &key, "value", &value))
+ return false;
+
+ GLogField log_field {};
+ log_field.key = key.get();
+ log_field.value = value.get();
+ log_field.length = -1;
+
+ return init_from_c_struct(cx, &log_field);
+}
+
+GjsGLogFieldInstance::~GjsGLogFieldInstance() {
+ if (m_owning_ptr) {
+ g_free(m_ptr.release());
+ }
+
+ GJS_DEC_COUNTER(logfield_instance);
+}
+
+GjsGLogFieldPrototype::~GjsGLogFieldPrototype(void) {
+ GJS_DEC_COUNTER(logfield_prototype);
+}
\ No newline at end of file
diff --git a/gi/glogfield.h b/gi/glogfield.h
new file mode 100644
index 00000000..8be864fb
--- /dev/null
+++ b/gi/glogfield.h
@@ -0,0 +1,136 @@
+// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021 Evan Welsh <contact evanwelsh com>
+
+#ifndef GI_GLOGFIELD_H_
+#define GI_GLOGFIELD_H_
+
+#include <glib.h>
+
+#include <girepository.h> // for GIStructInfo
+#include <glib-object.h> // for GType
+#include <js/TypeDecls.h> // for HandleObject
+#include "gi/cwrapper.h" // for CWrapperPointerOps
+#include "gi/wrapperutils.h" // for GjsSmartPointer, GIWrapperBase, GIWrapp...
+#include "gjs/macros.h" // for GJS_JSAPI_RETURN_CONVENTION
+#include "util/log.h" // for GJS_DEBUG_GBOXED, GjsDebugTopic
+
+namespace JS {
+class CallArgs;
+}
+
+class GjsGLogFieldInstance;
+class GjsGLogFieldPrototype;
+
+bool gjs_struct_info_is_log_field(GIStructInfo* info);
+
+class GjsGLogFieldBase
+ : public GIWrapperBase<GjsGLogFieldBase, GjsGLogFieldPrototype,
+ GjsGLogFieldInstance> {
+ friend class CWrapperPointerOps<GjsGLogFieldBase>;
+ friend class GIWrapperBase<GjsGLogFieldBase, GjsGLogFieldPrototype,
+ GjsGLogFieldInstance>;
+
+ protected:
+ explicit GjsGLogFieldBase(GjsGLogFieldPrototype* proto = nullptr)
+ : GIWrapperBase(proto) {}
+
+ static constexpr GjsDebugTopic DEBUG_TOPIC = GJS_DEBUG_GBOXED;
+ static constexpr const char* DEBUG_TAG = "logfield";
+
+ static const struct JSClassOps class_ops;
+ static const struct JSClass klass;
+
+ public:
+ [[nodiscard]] GjsGLogFieldBase* get_copy_source(JSContext* cx,
+ JS::Value value) const;
+};
+
+class GjsGLogFieldPrototype
+ : public GIWrapperPrototype<GjsGLogFieldBase, GjsGLogFieldPrototype,
+ GjsGLogFieldInstance, GIStructInfo> {
+ friend class GIWrapperPrototype<GjsGLogFieldBase, GjsGLogFieldPrototype,
+ GjsGLogFieldInstance, GIStructInfo>;
+ friend class GIWrapperBase<GjsGLogFieldBase, GjsGLogFieldPrototype,
+ GjsGLogFieldInstance>;
+
+ explicit GjsGLogFieldPrototype(GIStructInfo* info, GType gtype);
+ ~GjsGLogFieldPrototype(void);
+
+ GJS_JSAPI_RETURN_CONVENTION bool init(JSContext* cx);
+
+ static constexpr InfoType::Tag info_type_tag = InfoType::Struct;
+
+ // Accessors
+ bool define_fields(JSContext* cx, JS::HandleObject prototype);
+
+ static bool key_getter(JSContext* cx, unsigned int argc, JS::Value* vp);
+ static bool value_getter(JSContext* cx, unsigned int argc, JS::Value* vp);
+
+ public:
+ GJS_JSAPI_RETURN_CONVENTION
+ static bool define_class(JSContext* cx, JS::HandleObject in_object,
+ GIStructInfo* info);
+};
+
+class GjsGLogFieldInstance
+ : public GIWrapperInstance<GjsGLogFieldBase, GjsGLogFieldPrototype,
+ GjsGLogFieldInstance> {
+ friend class GIWrapperInstance<GjsGLogFieldBase, GjsGLogFieldPrototype,
+ GjsGLogFieldInstance>;
+ friend class GIWrapperBase<GjsGLogFieldBase, GjsGLogFieldPrototype,
+ GjsGLogFieldInstance>;
+ friend class GjsGLogFieldBase; // for field_getter, etc.
+
+ bool m_owning_ptr : 1; // if set, the JS wrapper owns the C memory referred
+ // to by m_ptr.
+
+ explicit GjsGLogFieldInstance(JSContext* cx, JS::HandleObject obj);
+ ~GjsGLogFieldInstance(void);
+
+ // Don't set GIWrapperBase::m_ptr directly. Instead, use one of these
+ // setters to express your intention to own the pointer or not.
+ void own_ptr(void* boxed_ptr) {
+ g_assert(!m_ptr);
+ m_ptr = boxed_ptr;
+ m_owning_ptr = true;
+ }
+
+ void share_ptr(void* unowned_boxed_ptr) {
+ g_assert(!m_ptr);
+ m_ptr = unowned_boxed_ptr;
+ m_owning_ptr = false;
+ }
+
+ // JS constructor
+
+ GJS_JSAPI_RETURN_CONVENTION
+ bool constructor_impl(JSContext* cx, JS::HandleObject obj,
+ const JS::CallArgs& args);
+
+ public:
+ [[nodiscard]] GLogField* ptr() const {
+ return static_cast<GLogField*>(m_ptr.get());
+ }
+
+ struct NoCopy {};
+
+ private:
+ GJS_JSAPI_RETURN_CONVENTION
+ bool init_from_c_struct(JSContext* cx, GLogField* glogfield);
+ GJS_JSAPI_RETURN_CONVENTION
+ bool init_from_c_struct(JSContext* cx, GLogField* glogfield, NoCopy);
+
+ template <typename... Args>
+ GJS_JSAPI_RETURN_CONVENTION static JSObject* new_for_c_struct_impl(
+ JSContext* cx, GIStructInfo* info, void* glogfield, Args&&... args);
+
+ public:
+ GJS_JSAPI_RETURN_CONVENTION
+ static JSObject* new_for_c_struct(JSContext* cx, GIStructInfo* info,
+ void* gboxed);
+ GJS_JSAPI_RETURN_CONVENTION
+ static JSObject* new_for_c_struct(JSContext* cx, GIStructInfo* info,
+ void* gboxed, NoCopy);
+};
+
+#endif
\ No newline at end of file
diff --git a/gi/repo.cpp b/gi/repo.cpp
index 87030fd4..414bc6a3 100644
--- a/gi/repo.cpp
+++ b/gi/repo.cpp
@@ -33,6 +33,7 @@
#include "gi/function.h"
#include "gi/fundamental.h"
#include "gi/gerror.h"
+#include "gi/glogfield.h"
#include "gi/interface.h"
#include "gi/ns.h"
#include "gi/object.h"
@@ -395,8 +396,12 @@ gjs_define_info(JSContext *context,
/* Fall through */
case GI_INFO_TYPE_BOXED:
- if (!BoxedPrototype::define_class(context, in_object, info))
+ if (gjs_struct_info_is_log_field(info)) {
+ if (!GjsGLogFieldPrototype::define_class(context, in_object, info))
+ return false;
+ } else if (!BoxedPrototype::define_class(context, in_object, info)) {
return false;
+ }
break;
case GI_INFO_TYPE_UNION:
if (!gjs_define_union_class(context, in_object, (GIUnionInfo*) info))
diff --git a/gi/value.cpp b/gi/value.cpp
index df427464..5bb8b40d 100644
--- a/gi/value.cpp
+++ b/gi/value.cpp
@@ -31,6 +31,7 @@
#include "gi/foreign.h"
#include "gi/fundamental.h"
#include "gi/gerror.h"
+#include "gi/glogfield.h"
#include "gi/gtype.h"
#include "gi/js-value-inl.h"
#include "gi/object.h"
@@ -963,7 +964,9 @@ gjs_value_from_g_value_internal(JSContext *context,
}
GIInfoType type = info.type();
- if (type == GI_INFO_TYPE_BOXED || type == GI_INFO_TYPE_STRUCT) {
+ if (gjs_struct_info_is_log_field(info)) {
+ obj = GjsGLogFieldInstance::new_for_c_struct(context, info, gboxed);
+ } else if (type == GI_INFO_TYPE_BOXED || type == GI_INFO_TYPE_STRUCT) {
if (no_copy)
obj = BoxedInstance::new_for_c_struct(context, info, gboxed,
BoxedInstance::NoCopy());
diff --git a/gjs/mem-private.h b/gjs/mem-private.h
index e4bdc750..2ec46525 100644
--- a/gjs/mem-private.h
+++ b/gjs/mem-private.h
@@ -12,6 +12,8 @@
#define GJS_FOR_EACH_COUNTER(macro) \
macro(boxed_instance) \
macro(boxed_prototype) \
+ macro(logfield_instance) \
+ macro(logfield_prototype) \
macro(closure) \
macro(function) \
macro(fundamental_instance) \
diff --git a/installed-tests/js/meson.build b/installed-tests/js/meson.build
index e11f1418..615fc967 100644
--- a/installed-tests/js/meson.build
+++ b/installed-tests/js/meson.build
@@ -94,6 +94,7 @@ subdir('libgjstesttools')
jasmine_tests = [
'self',
'ByteArray',
+ 'Console',
'Exceptions',
'Format',
'Fundamental',
diff --git a/installed-tests/js/testConsole.js b/installed-tests/js/testConsole.js
new file mode 100644
index 00000000..5daa942c
--- /dev/null
+++ b/installed-tests/js/testConsole.js
@@ -0,0 +1,41 @@
+// / <reference types="jasmine" />
+
+const {GLib} = imports.gi;
+
+describe('Console log levels', function () {
+ /** @type {jasmine.Spy<(_level: any, _fields: any) => any>} */
+ let writer_func;
+
+ beforeAll(function () {
+ writer_func = jasmine.createSpy(
+ 'Log test writer func',
+ function (_level, _fields) {
+ return GLib.LogWriterOutput.HANDLED;
+ }
+ );
+
+ writer_func.and.callThrough();
+
+ GLib.log_set_writer_func(writer_func);
+ });
+
+ beforeEach(function () {
+ writer_func.calls.reset();
+ });
+
+ it('logs a message', function () {
+ console.log('a message');
+
+ expect(writer_func.calls.mostRecent().args).toEqual(
+ [GLib.LogLevelFlags.LEVEL_MESSAGE, jasmine.arrayContaining([jasmine.objectContaining({key:
'MESSAGE', value: 'a message'})])]
+ );
+ });
+
+ it('logs a warning', function () {
+ console.warn('a warning');
+
+ expect(writer_func.calls.mostRecent().args).toEqual(
+ [GLib.LogLevelFlags.LEVEL_WARNING, jasmine.arrayContaining([jasmine.objectContaining({key:
'MESSAGE', value: 'a warning'})])]
+ );
+ });
+});
diff --git a/meson.build b/meson.build
index 3280f4e8..77a90b99 100644
--- a/meson.build
+++ b/meson.build
@@ -383,6 +383,7 @@ libgjs_sources = [
'gi/function.cpp', 'gi/function.h',
'gi/gerror.cpp', 'gi/gerror.h',
'gi/gjs_gi_trace.h',
+ 'gi/glogfield.cpp', 'gi/glogfield.h',
'gi/gobject.cpp', 'gi/gobject.h',
'gi/gtype.cpp', 'gi/gtype.h',
'gi/interface.cpp', 'gi/interface.h',
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]